add substep to upload delivery confirmation document and sync in document page
This commit is contained in:
parent
faec16f6fe
commit
45c816616f
|
|
@ -2598,6 +2598,7 @@ public class Controller : ControllerBase
|
||||||
[FromQuery] Int64? ticketId,
|
[FromQuery] Int64? ticketId,
|
||||||
[FromQuery] Int64? ticketCommentId,
|
[FromQuery] Int64? ticketCommentId,
|
||||||
[FromQuery] Int64? installationId,
|
[FromQuery] Int64? installationId,
|
||||||
|
[FromQuery] Int64? checklistItemId,
|
||||||
[FromQuery] Token authToken)
|
[FromQuery] Token authToken)
|
||||||
{
|
{
|
||||||
var user = Db.GetSession(authToken)?.User;
|
var user = Db.GetSession(authToken)?.User;
|
||||||
|
|
@ -2676,6 +2677,7 @@ public class Controller : ControllerBase
|
||||||
TicketId = ticketId,
|
TicketId = ticketId,
|
||||||
TicketCommentId = ticketCommentId,
|
TicketCommentId = ticketCommentId,
|
||||||
InstallationId = installationId,
|
InstallationId = installationId,
|
||||||
|
ChecklistItemId = checklistItemId,
|
||||||
Scope = scope,
|
Scope = scope,
|
||||||
S3Key = s3Key,
|
S3Key = s3Key,
|
||||||
OriginalName = safeFileName,
|
OriginalName = safeFileName,
|
||||||
|
|
@ -2725,6 +2727,7 @@ public class Controller : ControllerBase
|
||||||
[FromQuery] Int64? ticketId,
|
[FromQuery] Int64? ticketId,
|
||||||
[FromQuery] Int64? ticketCommentId,
|
[FromQuery] Int64? ticketCommentId,
|
||||||
[FromQuery] Int64? installationId,
|
[FromQuery] Int64? installationId,
|
||||||
|
[FromQuery] Int64? checklistItemId,
|
||||||
[FromQuery] Token authToken)
|
[FromQuery] Token authToken)
|
||||||
{
|
{
|
||||||
var user = Db.GetSession(authToken)?.User;
|
var user = Db.GetSession(authToken)?.User;
|
||||||
|
|
@ -2736,6 +2739,12 @@ public class Controller : ControllerBase
|
||||||
if (ticketCommentId.HasValue)
|
if (ticketCommentId.HasValue)
|
||||||
return Ok(Db.GetDocumentsForComment(ticketCommentId.Value));
|
return Ok(Db.GetDocumentsForComment(ticketCommentId.Value));
|
||||||
|
|
||||||
|
if (checklistItemId.HasValue)
|
||||||
|
{
|
||||||
|
if (user.UserType != 2) return Unauthorized();
|
||||||
|
return Ok(Db.GetDocumentsForChecklistItem(checklistItemId.Value));
|
||||||
|
}
|
||||||
|
|
||||||
if (installationId.HasValue)
|
if (installationId.HasValue)
|
||||||
{
|
{
|
||||||
// Access control: admin can list all; others need installation access
|
// Access control: admin can list all; others need installation access
|
||||||
|
|
@ -2747,7 +2756,7 @@ public class Controller : ControllerBase
|
||||||
return Ok(Db.GetDocumentsForInstallation(installationId.Value));
|
return Ok(Db.GetDocumentsForInstallation(installationId.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
return BadRequest("Provide ticketId, ticketCommentId, or installationId.");
|
return BadRequest("Provide ticketId, ticketCommentId, installationId, or checklistItemId.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete(nameof(DeleteDocument))]
|
[HttpDelete(nameof(DeleteDocument))]
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,12 @@ public static class ChecklistStepDefinitions
|
||||||
{"text":"checklistStep7Sub4","checked":false}
|
{"text":"checklistStep7Sub4","checked":false}
|
||||||
]
|
]
|
||||||
"""),
|
"""),
|
||||||
new( 8, "Installation delivered to customer site", NoSubtasks),
|
new( 8, "Installation delivered to customer site",
|
||||||
|
"""
|
||||||
|
[
|
||||||
|
{"text":"checklistStep8Sub1","checked":false}
|
||||||
|
]
|
||||||
|
"""),
|
||||||
new( 9, "Installation connected to grid", NoSubtasks),
|
new( 9, "Installation connected to grid", NoSubtasks),
|
||||||
new(10, "Hardware verified on site", NoSubtasks),
|
new(10, "Hardware verified on site", NoSubtasks),
|
||||||
new(11, "Software verified on site", NoSubtasks),
|
new(11, "Software verified on site", NoSubtasks),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ public class Document
|
||||||
[Indexed] public Int64? TicketId { get; set; }
|
[Indexed] public Int64? TicketId { get; set; }
|
||||||
[Indexed] public Int64? TicketCommentId { get; set; }
|
[Indexed] public Int64? TicketCommentId { get; set; }
|
||||||
[Indexed] public Int64? InstallationId { get; set; }
|
[Indexed] public Int64? InstallationId { get; set; }
|
||||||
|
[Indexed] public Int64? ChecklistItemId { get; set; }
|
||||||
|
|
||||||
public Int32 Scope { get; set; } = (Int32)DocumentScope.TicketAttachment;
|
public Int32 Scope { get; set; } = (Int32)DocumentScope.TicketAttachment;
|
||||||
public String S3Key { get; set; } = "";
|
public String S3Key { get; set; } = "";
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,12 @@ public static partial class Db
|
||||||
$"\"{oldText}\"", $"\"{key}\"", $"%\"{oldText}\"%");
|
$"\"{oldText}\"", $"\"{key}\"", $"%\"{oldText}\"%");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One-time backfill: step 8 originally had no subtasks; add the delivery-receipt subtask
|
||||||
|
// to existing rows so already-seeded installations pick up the new subtask after deploy.
|
||||||
|
Connection.Execute(
|
||||||
|
"UPDATE ChecklistItem SET Subtasks = ? WHERE StepNumber = 8 AND (Subtasks IS NULL OR Subtasks = '')",
|
||||||
|
"[{\"text\":\"checklistStep8Sub1\",\"checked\":false}]");
|
||||||
|
|
||||||
//UpdateKeys();
|
//UpdateKeys();
|
||||||
CleanupSessions().SupressAwaitWarning();
|
CleanupSessions().SupressAwaitWarning();
|
||||||
DeleteSnapshots().SupressAwaitWarning();
|
DeleteSnapshots().SupressAwaitWarning();
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,13 @@ public static partial class Db
|
||||||
.OrderBy(d => d.CreatedAt)
|
.OrderBy(d => d.CreatedAt)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
public static List<Document> GetDocumentsForChecklistItem(Int64 checklistItemId)
|
||||||
|
=> Documents
|
||||||
|
.Where(d => d.ChecklistItemId == checklistItemId
|
||||||
|
&& d.Scope == (Int32)DocumentScope.InstallationDocument)
|
||||||
|
.OrderBy(d => d.CreatedAt)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
// ── Checklist Queries ───────────────────────────────────────────────
|
// ── Checklist Queries ───────────────────────────────────────────────
|
||||||
|
|
||||||
public static List<ChecklistItem> GetChecklistForInstallation(Int64 installationId)
|
public static List<ChecklistItem> GetChecklistForInstallation(Int64 installationId)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ interface FileUploadButtonProps {
|
||||||
ticketId?: number;
|
ticketId?: number;
|
||||||
ticketCommentId?: number;
|
ticketCommentId?: number;
|
||||||
installationId?: number;
|
installationId?: number;
|
||||||
|
checklistItemId?: number;
|
||||||
onUploaded?: (doc: UploadedDocument) => void;
|
onUploaded?: (doc: UploadedDocument) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
@ -41,6 +42,7 @@ function FileUploadButton({
|
||||||
ticketId,
|
ticketId,
|
||||||
ticketCommentId,
|
ticketCommentId,
|
||||||
installationId,
|
installationId,
|
||||||
|
checklistItemId,
|
||||||
onUploaded,
|
onUploaded,
|
||||||
disabled = false
|
disabled = false
|
||||||
}: FileUploadButtonProps) {
|
}: FileUploadButtonProps) {
|
||||||
|
|
@ -87,7 +89,7 @@ function FileUploadButton({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axiosConfig.post('/UploadDocument', formData, {
|
const res = await axiosConfig.post('/UploadDocument', formData, {
|
||||||
params: { scope, ticketId, ticketCommentId, installationId },
|
params: { scope, ticketId, ticketCommentId, installationId, checklistItemId },
|
||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
onUploadProgress: (e) => {
|
onUploadProgress: (e) => {
|
||||||
if (e.total) setProgress(Math.round((e.loaded * 100) / e.total));
|
if (e.total) setProgress(Math.round((e.loaded * 100) / e.total));
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,17 @@ import {
|
||||||
ChecklistStatus,
|
ChecklistStatus,
|
||||||
ChecklistSubtask,
|
ChecklistSubtask,
|
||||||
parseSubtasks,
|
parseSubtasks,
|
||||||
serializeSubtasks
|
serializeSubtasks,
|
||||||
|
UPLOADABLE_SUBTASK_KEYS
|
||||||
} from 'src/interfaces/ChecklistTypes';
|
} from 'src/interfaces/ChecklistTypes';
|
||||||
import { AdminUser } from 'src/interfaces/TicketTypes';
|
import { AdminUser } from 'src/interfaces/TicketTypes';
|
||||||
|
import SubtaskDocumentUpload from './SubtaskDocumentUpload';
|
||||||
|
|
||||||
type EmailIconState = 'idle' | 'loading' | 'success' | 'error';
|
type EmailIconState = 'idle' | 'loading' | 'success' | 'error';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: ChecklistItem;
|
item: ChecklistItem;
|
||||||
|
installationId: number;
|
||||||
adminUsers: AdminUser[];
|
adminUsers: AdminUser[];
|
||||||
onUpdate: (
|
onUpdate: (
|
||||||
id: number,
|
id: number,
|
||||||
|
|
@ -54,7 +57,7 @@ const statusColors: Record<number, string> = {
|
||||||
[ChecklistStatus.Done]: '#2e7d32'
|
[ChecklistStatus.Done]: '#2e7d32'
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChecklistStepRow({ item, adminUsers, onUpdate, onNotify }: Props) {
|
function ChecklistStepRow({ item, installationId, adminUsers, onUpdate, onNotify }: Props) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [comments, setComments] = useState(item.comments ?? '');
|
const [comments, setComments] = useState(item.comments ?? '');
|
||||||
const [emailState, setEmailState] = useState<EmailIconState>('idle');
|
const [emailState, setEmailState] = useState<EmailIconState>('idle');
|
||||||
|
|
@ -282,22 +285,29 @@ function ChecklistStepRow({ item, adminUsers, onUpdate, onNotify }: Props) {
|
||||||
<TableCell colSpan={6} sx={{ backgroundColor: '#fafafa', py: 1 }}>
|
<TableCell colSpan={6} sx={{ backgroundColor: '#fafafa', py: 1 }}>
|
||||||
<Box pl={6} display="flex" flexDirection="column">
|
<Box pl={6} display="flex" flexDirection="column">
|
||||||
{subtasks.map((s, i) => (
|
{subtasks.map((s, i) => (
|
||||||
<FormControlLabel
|
<Box key={i}>
|
||||||
key={i}
|
<FormControlLabel
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
size="small"
|
size="small"
|
||||||
checked={s.checked}
|
checked={s.checked}
|
||||||
onChange={() => handleSubtaskToggle(i)}
|
onChange={() => handleSubtaskToggle(i)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label={
|
||||||
|
<Typography variant="body2" component="span">
|
||||||
|
{intl.formatMessage({ id: s.text, defaultMessage: s.text })}
|
||||||
|
</Typography>
|
||||||
|
}
|
||||||
|
sx={{ ml: 0 }}
|
||||||
|
/>
|
||||||
|
{UPLOADABLE_SUBTASK_KEYS.has(s.text) && (
|
||||||
|
<SubtaskDocumentUpload
|
||||||
|
installationId={installationId}
|
||||||
|
checklistItemId={item.id}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
label={
|
</Box>
|
||||||
<Typography variant="body2" component="span">
|
|
||||||
{intl.formatMessage({ id: s.text, defaultMessage: s.text })}
|
|
||||||
</Typography>
|
|
||||||
}
|
|
||||||
sx={{ ml: 0 }}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
|
||||||
|
|
@ -235,6 +235,7 @@ function InstallationChecklistTab({ installationId }: Props) {
|
||||||
<ChecklistStepRow
|
<ChecklistStepRow
|
||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
|
installationId={installationId}
|
||||||
adminUsers={adminUsers}
|
adminUsers={adminUsers}
|
||||||
onUpdate={handleUpdate}
|
onUpdate={handleUpdate}
|
||||||
onNotify={handleNotify}
|
onNotify={handleNotify}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { Box, IconButton, Stack, Typography } from '@mui/material';
|
||||||
|
import DownloadIcon from '@mui/icons-material/Download';
|
||||||
|
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
||||||
|
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
|
import FileUploadButton, {
|
||||||
|
UploadedDocument
|
||||||
|
} from 'src/components/FileUploadButton';
|
||||||
|
|
||||||
|
interface DocumentItem {
|
||||||
|
id: number;
|
||||||
|
originalName: string;
|
||||||
|
contentType: string;
|
||||||
|
sizeBytes: number;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
installationId: number;
|
||||||
|
checklistItemId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileIcon(contentType: string) {
|
||||||
|
if (contentType === 'application/pdf')
|
||||||
|
return <PictureAsPdfIcon fontSize="small" color="error" />;
|
||||||
|
return <InsertDriveFileIcon fontSize="small" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SubtaskDocumentUpload({ installationId, checklistItemId }: Props) {
|
||||||
|
const [docs, setDocs] = useState<DocumentItem[]>([]);
|
||||||
|
const [refreshKey, setRefreshKey] = useState(0);
|
||||||
|
|
||||||
|
const fetchDocs = useCallback(() => {
|
||||||
|
axiosConfig
|
||||||
|
.get('/GetDocuments', { params: { checklistItemId } })
|
||||||
|
.then((res) => {
|
||||||
|
if (Array.isArray(res.data)) setDocs(res.data);
|
||||||
|
})
|
||||||
|
.catch(() => setDocs([]));
|
||||||
|
}, [checklistItemId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchDocs();
|
||||||
|
}, [fetchDocs, refreshKey]);
|
||||||
|
|
||||||
|
const handleDownload = (doc: DocumentItem) => {
|
||||||
|
axiosConfig
|
||||||
|
.get('/DownloadDocument', {
|
||||||
|
params: { id: doc.id },
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
const url = window.URL.createObjectURL(new Blob([res.data]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', doc.originalName);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUploaded = (_doc: UploadedDocument) => {
|
||||||
|
setRefreshKey((k) => k + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box mt={0.5} mb={1}>
|
||||||
|
{docs.length > 0 && (
|
||||||
|
<Stack spacing={0.25} mb={0.75}>
|
||||||
|
{docs.map((d) => (
|
||||||
|
<Stack
|
||||||
|
key={d.id}
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
spacing={1}
|
||||||
|
sx={{ pl: 4 }}
|
||||||
|
>
|
||||||
|
{fileIcon(d.contentType)}
|
||||||
|
<Typography variant="body2" sx={{ flex: 1 }} noWrap>
|
||||||
|
{d.originalName}
|
||||||
|
</Typography>
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleDownload(d)}
|
||||||
|
aria-label="download"
|
||||||
|
>
|
||||||
|
<DownloadIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
<Box sx={{ pl: 4 }}>
|
||||||
|
<FileUploadButton
|
||||||
|
scope={1}
|
||||||
|
installationId={installationId}
|
||||||
|
checklistItemId={checklistItemId}
|
||||||
|
onUploaded={handleUploaded}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{docs.length === 0 && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ pl: 4, display: 'block', mt: 0.5 }}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="checklistNoAttachments"
|
||||||
|
defaultMessage="No file attached yet."
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SubtaskDocumentUpload;
|
||||||
|
|
@ -75,7 +75,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
setProgressMap(map);
|
setProgressMap(map);
|
||||||
})
|
})
|
||||||
.catch(() => setProgressMap(new Map()));
|
.catch(() => setProgressMap(new Map()));
|
||||||
}, [showChecklistColumn]);
|
}, [showChecklistColumn, currentLocation.pathname]);
|
||||||
|
|
||||||
const HoverableTableRow = styled(TableRow)(({ theme }) => ({
|
const HoverableTableRow = styled(TableRow)(({ theme }) => ({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,15 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
const showChecklistColumn =
|
const showChecklistColumn =
|
||||||
currentUser?.userType === UserType.admin &&
|
currentUser?.userType === UserType.admin &&
|
||||||
CHECKLIST_ENABLED_PRODUCTS.has(props.product ?? -1);
|
CHECKLIST_ENABLED_PRODUCTS.has(props.product ?? -1);
|
||||||
|
const isListViewPath =
|
||||||
|
currentLocation.pathname === baseRoute + 'list' ||
|
||||||
|
currentLocation.pathname === baseRoute + routes.list;
|
||||||
const [progressMap, setProgressMap] = useState<Map<number, ChecklistSummary>>(
|
const [progressMap, setProgressMap] = useState<Map<number, ChecklistSummary>>(
|
||||||
new Map()
|
new Map()
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!showChecklistColumn) return;
|
if (!showChecklistColumn || !isListViewPath) return;
|
||||||
axiosConfig
|
axiosConfig
|
||||||
.get('/GetChecklistSummary')
|
.get('/GetChecklistSummary')
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
|
@ -61,7 +64,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
setProgressMap(map);
|
setProgressMap(map);
|
||||||
})
|
})
|
||||||
.catch(() => setProgressMap(new Map()));
|
.catch(() => setProgressMap(new Map()));
|
||||||
}, [showChecklistColumn]);
|
}, [showChecklistColumn, isListViewPath]);
|
||||||
|
|
||||||
const sortedInstallations = useMemo(() => {
|
const sortedInstallations = useMemo(() => {
|
||||||
return [...props.installations].sort((a, b) => {
|
return [...props.installations].sort((a, b) => {
|
||||||
|
|
@ -114,9 +117,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const isListView =
|
const isListView = isListViewPath;
|
||||||
currentLocation.pathname === baseRoute + 'list' ||
|
|
||||||
currentLocation.pathname === baseRoute + routes.list;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
|
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ export type ChecklistItem = {
|
||||||
|
|
||||||
export const CHECKLIST_ENABLED_PRODUCTS: ReadonlySet<number> = new Set([2, 4, 5]);
|
export const CHECKLIST_ENABLED_PRODUCTS: ReadonlySet<number> = new Set([2, 4, 5]);
|
||||||
|
|
||||||
|
export const UPLOADABLE_SUBTASK_KEYS: ReadonlySet<string> = new Set([
|
||||||
|
'checklistStep8Sub1'
|
||||||
|
]);
|
||||||
|
|
||||||
export type ChecklistSummary = {
|
export type ChecklistSummary = {
|
||||||
installationId: number;
|
installationId: number;
|
||||||
done: number;
|
done: number;
|
||||||
|
|
|
||||||
|
|
@ -743,6 +743,8 @@
|
||||||
"checklistStep7Sub2": "Produkt-ID in config.json konfiguriert",
|
"checklistStep7Sub2": "Produkt-ID in config.json konfiguriert",
|
||||||
"checklistStep7Sub3": "USB-ID in config.json konfiguriert",
|
"checklistStep7Sub3": "USB-ID in config.json konfiguriert",
|
||||||
"checklistStep7Sub4": "Datenlesung vom Wechselrichter getestet",
|
"checklistStep7Sub4": "Datenlesung vom Wechselrichter getestet",
|
||||||
|
"checklistStep8Sub1": "Lieferschein mit Kundenunterschrift erhalten und hochgeladen",
|
||||||
|
"checklistNoAttachments": "Noch keine Datei angehängt.",
|
||||||
"setupProgress": "Setup-Fortschritt",
|
"setupProgress": "Setup-Fortschritt",
|
||||||
"checklistPhaseEmpty": "Nicht gestartet",
|
"checklistPhaseEmpty": "Nicht gestartet",
|
||||||
"checklistPhasePreparation": "Vorbereitung",
|
"checklistPhasePreparation": "Vorbereitung",
|
||||||
|
|
|
||||||
|
|
@ -491,6 +491,8 @@
|
||||||
"checklistStep7Sub2": "Product ID configured in config.json",
|
"checklistStep7Sub2": "Product ID configured in config.json",
|
||||||
"checklistStep7Sub3": "USB ID configured in config.json",
|
"checklistStep7Sub3": "USB ID configured in config.json",
|
||||||
"checklistStep7Sub4": "Inverter data reading from inverter tested",
|
"checklistStep7Sub4": "Inverter data reading from inverter tested",
|
||||||
|
"checklistStep8Sub1": "Delivery receipt with customer signature received and uploaded",
|
||||||
|
"checklistNoAttachments": "No file attached yet.",
|
||||||
"setupProgress": "Setup Progress",
|
"setupProgress": "Setup Progress",
|
||||||
"checklistPhaseEmpty": "Not started",
|
"checklistPhaseEmpty": "Not started",
|
||||||
"checklistPhasePreparation": "Preparation",
|
"checklistPhasePreparation": "Preparation",
|
||||||
|
|
|
||||||
|
|
@ -743,6 +743,8 @@
|
||||||
"checklistStep7Sub2": "ID produit configuré dans config.json",
|
"checklistStep7Sub2": "ID produit configuré dans config.json",
|
||||||
"checklistStep7Sub3": "ID USB configuré dans config.json",
|
"checklistStep7Sub3": "ID USB configuré dans config.json",
|
||||||
"checklistStep7Sub4": "Lecture des données de l'onduleur testée",
|
"checklistStep7Sub4": "Lecture des données de l'onduleur testée",
|
||||||
|
"checklistStep8Sub1": "Bon de livraison signé par le client reçu et téléversé",
|
||||||
|
"checklistNoAttachments": "Aucun fichier joint pour le moment.",
|
||||||
"setupProgress": "Progression installation",
|
"setupProgress": "Progression installation",
|
||||||
"checklistPhaseEmpty": "Non commencé",
|
"checklistPhaseEmpty": "Non commencé",
|
||||||
"checklistPhasePreparation": "Préparation",
|
"checklistPhasePreparation": "Préparation",
|
||||||
|
|
|
||||||
|
|
@ -743,6 +743,8 @@
|
||||||
"checklistStep7Sub2": "ID prodotto configurato in config.json",
|
"checklistStep7Sub2": "ID prodotto configurato in config.json",
|
||||||
"checklistStep7Sub3": "ID USB configurato in config.json",
|
"checklistStep7Sub3": "ID USB configurato in config.json",
|
||||||
"checklistStep7Sub4": "Lettura dati inverter testata",
|
"checklistStep7Sub4": "Lettura dati inverter testata",
|
||||||
|
"checklistStep8Sub1": "Bolla di consegna con firma del cliente ricevuta e caricata",
|
||||||
|
"checklistNoAttachments": "Nessun file allegato.",
|
||||||
"setupProgress": "Avanzamento installazione",
|
"setupProgress": "Avanzamento installazione",
|
||||||
"checklistPhaseEmpty": "Non avviato",
|
"checklistPhaseEmpty": "Non avviato",
|
||||||
"checklistPhasePreparation": "Preparazione",
|
"checklistPhasePreparation": "Preparazione",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue