Просмотр исходного кода

WorkbenchGoodPickExecutionDetail UI 大改

WorkbenchTicketReleaseTable 加 user filter

DO release / FloorLanePanel:Truck X 票 + 搜尋依樓層
production
CANCERYS\kw093 6 часов назад
Родитель
Сommit
475857092d
9 измененных файлов: 304 добавлений и 82 удалений
  1. +8
    -2
      src/app/api/doworkbench/actions.ts
  2. +34
    -8
      src/components/DoWorkbench/WorkbenchFloorLanePanel.tsx
  3. +115
    -48
      src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx
  4. +86
    -1
      src/components/DoWorkbench/WorkbenchTicketReleaseTable.tsx
  5. +17
    -5
      src/components/FinishedGoodSearch/ReleasedDoPickOrderSelectModal.tsx
  6. +21
    -2
      src/components/PickOrderSearch/WorkbenchPickExecution.tsx
  7. +3
    -0
      src/i18n/en/ticketReleaseTable.json
  8. +17
    -16
      src/i18n/zh/pickOrder.json
  9. +3
    -0
      src/i18n/zh/ticketReleaseTable.json

+ 8
- 2
src/app/api/doworkbench/actions.ts Просмотреть файл

@@ -242,13 +242,16 @@ export async function fetchWorkbenchReleasedDoPickOrdersForSelection(
shopName?: string, shopName?: string,
storeId?: string, storeId?: string,
truck?: string, truck?: string,
releaseType?: string
releaseType?: string,
/** Optional `2F`/`4F`: Truck X split by DO supplier preferred floor (storeId stays null). */
floor?: string
): Promise<ReleasedDoPickOrderListItem[]> { ): Promise<ReleasedDoPickOrderListItem[]> {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (shopName?.trim()) params.append("shopName", shopName.trim()); if (shopName?.trim()) params.append("shopName", shopName.trim());
if (storeId?.trim()) params.append("storeId", storeId.trim()); if (storeId?.trim()) params.append("storeId", storeId.trim());
if (truck?.trim()) params.append("truck", truck.trim()); if (truck?.trim()) params.append("truck", truck.trim());
if (releaseType?.trim()) params.append("releaseType", releaseType.trim()); if (releaseType?.trim()) params.append("releaseType", releaseType.trim());
if (floor?.trim()) params.append("floor", floor.trim().replace("/", "").toUpperCase());
const query = params.toString(); const query = params.toString();
const url = `${BASE_API_URL}/doPickOrder/workbench/released${query ? `?${query}` : ""}`; const url = `${BASE_API_URL}/doPickOrder/workbench/released${query ? `?${query}` : ""}`;
const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" }); const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" });
@@ -261,7 +264,9 @@ export async function fetchWorkbenchReleasedDoPickOrdersForSelectionToday(
storeId?: string, storeId?: string,
truck?: string, truck?: string,
requiredDeliveryDate?: string, requiredDeliveryDate?: string,
releaseType?: string
releaseType?: string,
/** Optional `2F`/`4F`: Truck X split by DO supplier preferred floor (storeId stays null). */
floor?: string
): Promise<ReleasedDoPickOrderListItem[]> { ): Promise<ReleasedDoPickOrderListItem[]> {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (shopName?.trim()) params.append("shopName", shopName.trim()); if (shopName?.trim()) params.append("shopName", shopName.trim());
@@ -269,6 +274,7 @@ export async function fetchWorkbenchReleasedDoPickOrdersForSelectionToday(
if (truck?.trim()) params.append("truck", truck.trim()); if (truck?.trim()) params.append("truck", truck.trim());
if (requiredDeliveryDate?.trim()) params.append("requiredDate", requiredDeliveryDate.trim()); if (requiredDeliveryDate?.trim()) params.append("requiredDate", requiredDeliveryDate.trim());
if (releaseType?.trim()) params.append("releaseType", releaseType.trim()); if (releaseType?.trim()) params.append("releaseType", releaseType.trim());
if (floor?.trim()) params.append("floor", floor.trim().replace("/", "").toUpperCase());
const query = params.toString(); const query = params.toString();
const url = `${BASE_API_URL}/doPickOrder/workbench/released-today${query ? `?${query}` : ""}`; const url = `${BASE_API_URL}/doPickOrder/workbench/released-today${query ? `?${query}` : ""}`;
const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" }); const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { method: "GET" });


+ 34
- 8
src/components/DoWorkbench/WorkbenchFloorLanePanel.tsx Просмотреть файл

@@ -85,7 +85,13 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({
const [modalReleaseTypeFilter, setModalReleaseTypeFilter] = useState<string | undefined>(undefined); const [modalReleaseTypeFilter, setModalReleaseTypeFilter] = useState<string | undefined>(undefined);
const [modalFilterRequiredDeliveryDate, setModalFilterRequiredDeliveryDate] = useState<string | undefined>(undefined); const [modalFilterRequiredDeliveryDate, setModalFilterRequiredDeliveryDate] = useState<string | undefined>(undefined);
const [modalInitialShopSearch, setModalInitialShopSearch] = useState<string | undefined>(undefined); const [modalInitialShopSearch, setModalInitialShopSearch] = useState<string | undefined>(undefined);
const defaultTruckCount = summary4F?.defaultTruckCount ?? 0;
const [modalTruckXFloor, setModalTruckXFloor] = useState<string | undefined>(undefined);
const ticketFloorApiKey = useMemo(
() => ticketFloor.replace("/", "").trim().toUpperCase(),
[ticketFloor],
);
const defaultTruckCount =
(ticketFloor === "2/F" ? summary2F?.defaultTruckCount : summary4F?.defaultTruckCount) ?? 0;
const etraEnterInFlightRef = useRef(false); const etraEnterInFlightRef = useRef(false);
const [etraMergeDialogOpen, setEtraMergeDialogOpen] = useState(false); const [etraMergeDialogOpen, setEtraMergeDialogOpen] = useState(false);


@@ -108,16 +114,25 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({
shopName?: string, shopName?: string,
storeId?: string, storeId?: string,
truck?: string, truck?: string,
releaseType?: string
) => fetchWorkbenchReleasedDoPickOrdersForSelection(shopName, storeId, truck, releaseType),
releaseType?: string,
floor?: string
) => fetchWorkbenchReleasedDoPickOrdersForSelection(shopName, storeId, truck, releaseType, floor),
loadToday: ( loadToday: (
shopName?: string, shopName?: string,
storeId?: string, storeId?: string,
truck?: string, truck?: string,
requiredDeliveryDate?: string, requiredDeliveryDate?: string,
releaseType?: string
releaseType?: string,
floor?: string
) => ) =>
fetchWorkbenchReleasedDoPickOrdersForSelectionToday(shopName, storeId, truck, requiredDeliveryDate, releaseType),
fetchWorkbenchReleasedDoPickOrdersForSelectionToday(
shopName,
storeId,
truck,
requiredDeliveryDate,
releaseType,
floor
),
assignByListItemId: assignByDeliveryOrderPickOrderId, assignByListItemId: assignByDeliveryOrderPickOrderId,
}), }),
[], [],
@@ -234,7 +249,14 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({
pendingRef.current += 1; pendingRef.current += 1;
startFullTimer(); startFullTimer();
try { try {
const list = await fetchWorkbenchReleasedDoPickOrdersForSelection(undefined, undefined, "車線-X");
// storeId stays undefined (Truck X); floor splits by DO supplier preferred floor.
const list = await fetchWorkbenchReleasedDoPickOrdersForSelection(
undefined,
undefined,
"車線-X",
undefined,
ticketFloorApiKey
);
setBeforeTodayTruckXCount(list.length); setBeforeTodayTruckXCount(list.length);
} catch { } catch {
setBeforeTodayTruckXCount(0); setBeforeTodayTruckXCount(0);
@@ -244,12 +266,13 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({
} }
}; };
void loadBeforeTodayTruckX(); void loadBeforeTodayTruckX();
}, [inEtraUi]);
}, [inEtraUi, ticketFloorApiKey]);


const clearModalEtraContext = useCallback(() => { const clearModalEtraContext = useCallback(() => {
setModalReleaseTypeFilter(undefined); setModalReleaseTypeFilter(undefined);
setModalFilterRequiredDeliveryDate(undefined); setModalFilterRequiredDeliveryDate(undefined);
setModalInitialShopSearch(undefined); setModalInitialShopSearch(undefined);
setModalTruckXFloor(undefined);
}, []); }, []);


const openEnterEtraView = useCallback(async () => { const openEnterEtraView = useCallback(async () => {
@@ -538,6 +561,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({
setSelectedTruck("車線-X"); setSelectedTruck("車線-X");
setIsDefaultTruck(true); setIsDefaultTruck(true);
setDefaultDateScope("today"); setDefaultDateScope("today");
setModalTruckXFloor(ticketFloorApiKey);
setModalOpen(true); setModalOpen(true);
}} }}
> >
@@ -638,10 +662,11 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({
variant="outlined" variant="outlined"
onClick={() => { onClick={() => {
clearModalEtraContext(); clearModalEtraContext();
setSelectedStore("4/F");
setSelectedStore("");
setSelectedTruck("車線-X"); setSelectedTruck("車線-X");
setIsDefaultTruck(true); setIsDefaultTruck(true);
setDefaultDateScope("before"); setDefaultDateScope("before");
setModalTruckXFloor(ticketFloorApiKey);
setModalOpen(true); setModalOpen(true);
}} }}
> >
@@ -746,6 +771,7 @@ const WorkbenchFloorLanePanel: React.FC<Props> = ({
releaseTypeFilter={modalReleaseTypeFilter} releaseTypeFilter={modalReleaseTypeFilter}
filterRequiredDeliveryDate={modalFilterRequiredDeliveryDate} filterRequiredDeliveryDate={modalFilterRequiredDeliveryDate}
initialShopSearch={modalInitialShopSearch} initialShopSearch={modalInitialShopSearch}
truckXFloor={modalTruckXFloor}
onClose={() => { onClose={() => {
setModalOpen(false); setModalOpen(false);
clearModalEtraContext(); clearModalEtraContext();


+ 115
- 48
src/components/DoWorkbench/WorkbenchGoodPickExecutionDetail.tsx Просмотреть файл

@@ -1223,13 +1223,21 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
}, [fetchAllCombinedLotData]); }, [fetchAllCombinedLotData]);


const openWorkbenchLotLabelModalForLot = useCallback( const openWorkbenchLotLabelModalForLot = useCallback(
(lot: any, reminderText?: string | null) => {
(
lot: any,
reminderText?: string | null,
options?: { markAsScanIssue?: boolean },
) => {
const itemId = Number(lot?.itemId); const itemId = Number(lot?.itemId);
const stockInLineId = Number(lot?.stockInLineId); const stockInLineId = Number(lot?.stockInLineId);
const solId = Number(lot?.stockOutLineId); const solId = Number(lot?.stockOutLineId);
if (!Number.isFinite(itemId) || itemId <= 0 || !Number.isFinite(solId) || solId <= 0) { if (!Number.isFinite(itemId) || itemId <= 0 || !Number.isFinite(solId) || solId <= 0) {
return; return;
} }
// 掃碼 issue 才標記:手動「批號二維碼」不傳 markAsScanIssue,批號不變紅
if (options?.markAsScanIssue) {
rememberWorkbenchScanReject(solId, reminderText);
}
setWorkbenchLotLabelContextLot(lot); setWorkbenchLotLabelContextLot(lot);
if (Number.isFinite(stockInLineId) && stockInLineId > 0) { if (Number.isFinite(stockInLineId) && stockInLineId > 0) {
setWorkbenchLotLabelInitialPayload({ itemId, stockInLineId }); setWorkbenchLotLabelInitialPayload({ itemId, stockInLineId });
@@ -1241,7 +1249,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
setQrScanSuccess(false); setQrScanSuccess(false);
setWorkbenchLotLabelModalOpen(true); setWorkbenchLotLabelModalOpen(true);
}, },
[],
[rememberWorkbenchScanReject],
); );


const shouldOpenWorkbenchLotLabelModalForFailure = useCallback( const shouldOpenWorkbenchLotLabelModalForFailure = useCallback(
@@ -1826,6 +1834,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
openWorkbenchLotLabelModalForLot( openWorkbenchLotLabelModalForLot(
scannedLot, scannedLot,
t("This lot is not available, please scan another lot."), t("This lot is not available, please scan another lot."),
{ markAsScanIssue: true },
); );
return; return;
} }
@@ -1841,6 +1850,7 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
openWorkbenchLotLabelModalForLot( openWorkbenchLotLabelModalForLot(
scannedLot, scannedLot,
`Lot is expired (expiry=${scannedLot.expiryDate || "-"})`, `Lot is expired (expiry=${scannedLot.expiryDate || "-"})`,
{ markAsScanIssue: true },
); );
return; return;
} }
@@ -1963,7 +1973,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
expectedLot expectedLot
) { ) {
openWorkbenchLotLabelModalForLot(expectedLot, failMsg);
openWorkbenchLotLabelModalForLot(expectedLot, failMsg, {
markAsScanIssue: true,
});
return; return;
} }
if (workbenchMode && expectedLot.stockOutLineId != null) { if (workbenchMode && expectedLot.stockOutLineId != null) {
@@ -2135,7 +2147,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
expectedLot expectedLot
) { ) {
openWorkbenchLotLabelModalForLot(expectedLot, failMsg);
openWorkbenchLotLabelModalForLot(expectedLot, failMsg, {
markAsScanIssue: true,
});
return; return;
} }
if (workbenchMode && expectedLot.stockOutLineId != null) { if (workbenchMode && expectedLot.stockOutLineId != null) {
@@ -2330,7 +2344,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
exactMatch exactMatch
) { ) {
openWorkbenchLotLabelModalForLot(exactMatch, failMsg);
openWorkbenchLotLabelModalForLot(exactMatch, failMsg, {
markAsScanIssue: true,
});
return; return;
} }
if (workbenchMode && exactMatch.stockOutLineId != null) { if (workbenchMode && exactMatch.stockOutLineId != null) {
@@ -2474,7 +2490,9 @@ const fetchAllCombinedLotData = useCallback(async (userId?: number, pickOrderIdO
shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) && shouldOpenWorkbenchLotLabelModalForFailure(res.code, failMsg) &&
expectedLot expectedLot
) { ) {
openWorkbenchLotLabelModalForLot(expectedLot, failMsg);
openWorkbenchLotLabelModalForLot(expectedLot, failMsg, {
markAsScanIssue: true,
});
return; return;
} }
if (workbenchMode && expectedLot.stockOutLineId != null) { if (workbenchMode && expectedLot.stockOutLineId != null) {
@@ -4154,10 +4172,21 @@ const handleSubmitAllScanned = useCallback(async () => {
<TableCell>{t("Item Code")}</TableCell> <TableCell>{t("Item Code")}</TableCell>
<TableCell>{t("Item Name")}</TableCell> <TableCell>{t("Item Name")}</TableCell>
<TableCell>{t("Route")}</TableCell>
<TableCell>{t("Suggest Lot No.")}</TableCell>
<TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
<TableCell align="center">{t("Scan Result")}</TableCell>
<TableCell
align="center"
sx={{ minWidth: 220, whiteSpace: "nowrap" }}
>
{`${t("Route")} / ${t("Suggest Lot No.")}`}
</TableCell>
<TableCell align="right" sx={{ whiteSpace: "nowrap", width: 88, px: 1 }}>
{t("Lot Required Pick Qty")}
</TableCell>
<TableCell
align="center"
sx={{ width: 88, minWidth: 88, px: 0.5, whiteSpace: "nowrap" }}
>
{t("Scan Result")}
</TableCell>
{/*<TableCell align="center">{t("Qty will submit")}</TableCell>*/} {/*<TableCell align="center">{t("Qty will submit")}</TableCell>*/}
<TableCell align="center">{t("Submit Required Pick Qty")}</TableCell> <TableCell align="center">{t("Submit Required Pick Qty")}</TableCell>
</TableRow> </TableRow>
@@ -4165,7 +4194,7 @@ const handleSubmitAllScanned = useCallback(async () => {
<TableBody> <TableBody>
{paginatedData.length === 0 ? ( {paginatedData.length === 0 ? (
<TableRow> <TableRow>
<TableCell colSpan={11} align="center">
<TableCell colSpan={7} align="center">
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{t("No data available")} {t("No data available")}
</Typography> </Typography>
@@ -4226,36 +4255,49 @@ paginatedData.map((row, index) => {
{row.isGroupFirst ? lot.itemName + '(' + lot.stockUnit + ')' : ""} {row.isGroupFirst ? lot.itemName + '(' + lot.stockUnit + ')' : ""}
</Typography> </Typography>
</TableCell> </TableCell>
<TableCell>
<Typography variant="body2">
{lot.routerRoute || '-'}
</Typography>
</TableCell>
<TableCell>
<Stack direction="row" spacing={1} alignItems="center">
<Box sx={{ flex: 1, minWidth: 0 }}>
<TableCell align="center" sx={{ minWidth: 220 }}>
<Stack
direction="row"
spacing={1}
alignItems="center"
justifyContent="center"
sx={{ width: "100%" }}
>
<Box sx={{ textAlign: "center" }}>
<Typography variant="body2" sx={{ whiteSpace: "nowrap", textAlign: "center" }}>
{lot.routerRoute || "-"}
</Typography>
{(() => { {(() => {
const hasLotNo = Boolean(lot.lotNo); const hasLotNo = Boolean(lot.lotNo);
const isExpired = lot.lotAvailability === 'expired'; const isExpired = lot.lotAvailability === 'expired';
const isUnavailable = isInventoryLotLineUnavailable(lot); const isUnavailable = isInventoryLotLineUnavailable(lot);
const isNoLotHint = !hasLotNo && !rejectDisplay; // 顯示「請檢查周圍…」那種 const isNoLotHint = !hasLotNo && !rejectDisplay; // 顯示「請檢查周圍…」那種
const isIssueText =
Boolean(rejectDisplay) || !hasLotNo || isExpired || isUnavailable;
const textColor =
isNoLotHint
? 'error.main' // 或 'text.primary':固定黑,不受 handled / unavailable 影響
: rejectDisplay || isSolRejected || isUnavailable
? 'error.main'
: isExpired
? 'warning.main'
: 'inherit';
const solStatus = String(lot.stockOutLineStatus ?? "").toLowerCase();
const isComplete =
solStatus === "completed" ||
solStatus === "checked" ||
solStatus === "partially_completed" ||
solStatus === "partially_complete";
const textColor =
isNoLotHint
? "error.main"
: rejectDisplay || isSolRejected || isUnavailable
? "error.main"
: isExpired
? "warning.main"
: isComplete
? "success.main"
: "inherit";
const lotOnly =
hasLotNo && !rejectDisplay && !isExpired && !isUnavailable;
return ( return (
<Typography <Typography
variant={isIssueText ? "body2" : "body2"}
variant="body2"
sx={{ sx={{
color: textColor, color: textColor,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
whiteSpace: lotOnly ? "nowrap" : "pre-wrap",
wordBreak: lotOnly ? "normal" : "break-word",
textAlign: "center",
}} }}
> >
{hasLotNo ? ( {hasLotNo ? (
@@ -4299,7 +4341,6 @@ paginatedData.map((row, index) => {
sx={{ sx={{
flexShrink: 0, flexShrink: 0,
fontSize: "0.75rem", fontSize: "0.75rem",
//py: 0.25,
minWidth: "auto", minWidth: "auto",
px: 1, px: 1,
whiteSpace: "nowrap", whiteSpace: "nowrap",
@@ -4310,23 +4351,34 @@ paginatedData.map((row, index) => {
)} )}
</Stack> </Stack>
</TableCell> </TableCell>
<TableCell align="right">
{(() => {
const requiredQty = lot.requiredQty || 0;
return requiredQty.toLocaleString() + '(' + lot.uomShortDesc + ')';
})()}

<TableCell align="right" sx={{ whiteSpace: "nowrap", width: 88, px: 1 }}>
<Typography variant="body1">
{(() => {
const requiredQty = lot.requiredQty || 0;
return requiredQty.toLocaleString() + '(' + lot.uomShortDesc + ')';
})()}
</Typography>
</TableCell> </TableCell>
<TableCell align="center">
<TableCell align="center" sx={{ width: 88, minWidth: 88, px: 0.5 }}>
{(() => { {(() => {
const status = lot.stockOutLineStatus?.toLowerCase(); const status = lot.stockOutLineStatus?.toLowerCase();
const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected'; const isRejected = status === 'rejected' || lot.lotAvailability === 'rejected';
const isNoLot = !lot.lotNo; const isNoLot = !lot.lotNo;
const scanResultSlotSx = {
width: 42,
height: 42,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mx: 'auto',
} as const;
// rejected lot:显示红色勾选(已扫描但被拒绝) // rejected lot:显示红色勾选(已扫描但被拒绝)
if (isRejected && !isNoLot) { if (isRejected && !isNoLot) {
return ( return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Box sx={scanResultSlotSx}>
<Checkbox <Checkbox
checked={true} checked={true}
disabled={true} disabled={true}
@@ -4350,13 +4402,13 @@ paginatedData.map((row, index) => {
status !== "partially_completed" && status !== "partially_completed" &&
status !== "partially_complete" status !== "partially_complete"
) { ) {
return null;
return <Box sx={scanResultSlotSx} />;
} }


// 正常 lot:已扫描(checked/partially_completed/completed) // 正常 lot:已扫描(checked/partially_completed/completed)
if (!isNoLot && status !== 'pending' && status !== 'rejected') { if (!isNoLot && status !== 'pending' && status !== 'rejected') {
return ( return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Box sx={scanResultSlotSx}>
<Checkbox <Checkbox
checked={true} checked={true}
disabled={true} disabled={true}
@@ -4375,7 +4427,7 @@ paginatedData.map((row, index) => {
// noLot 且已完成/部分完成:显示红色勾选 // noLot 且已完成/部分完成:显示红色勾选
if (isNoLot && (status === 'partially_completed' || status === 'completed')) { if (isNoLot && (status === 'partially_completed' || status === 'completed')) {
return ( return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Box sx={scanResultSlotSx}>
<Checkbox <Checkbox
checked={true} checked={true}
disabled={true} disabled={true}
@@ -4391,7 +4443,7 @@ paginatedData.map((row, index) => {
); );
} }
return null;
return <Box sx={scanResultSlotSx} />;
})()} })()}
</TableCell> </TableCell>
{/* {/*
@@ -4477,11 +4529,10 @@ paginatedData.map((row, index) => {
<Stack direction="row" spacing={1} alignItems="center" justifyContent="center"> <Stack direction="row" spacing={1} alignItems="center" justifyContent="center">
{isRowPicked ? ( {isRowPicked ? (
<Typography <Typography
variant="body2"
variant="body1"
sx={{ sx={{
width: 96, width: 96,
textAlign: "center", textAlign: "center",
fontSize: "1rem",
fontWeight: 500, fontWeight: 500,
}} }}
> >
@@ -4512,7 +4563,16 @@ paginatedData.map((row, index) => {
inputProps={{ min: 0, step: 1 }} inputProps={{ min: 0, step: 1 }}
sx={{ sx={{
width: 96, width: 96,
"& .MuiInputBase-input": { fontSize: "0.75rem", py: 0.5, textAlign: "center" },
"& .MuiInputBase-input": {
typography: "body1",
py: 0.5,
textAlign: "center",
"&::-webkit-outer-spin-button, &::-webkit-inner-spin-button": {
WebkitAppearance: "none",
margin: 0,
},
MozAppearance: "textfield",
},
}} }}
/> />
)} )}
@@ -4557,7 +4617,14 @@ paginatedData.map((row, index) => {
} }
sx={{ fontSize: '0.7rem', py: 0.5, minHeight: '28px', minWidth: '60px' }}
sx={{
fontSize: "0.7rem",
py: 0.5,
minHeight: "28px",
minWidth: "72px",
whiteSpace: "nowrap",
flexShrink: 0,
}}
> >
{t("Just Completed")} {t("Just Completed")}
</Button> </Button>


+ 86
- 1
src/components/DoWorkbench/WorkbenchTicketReleaseTable.tsx Просмотреть файл

@@ -5,13 +5,16 @@ import {
Box, Box,
Card, Card,
CardContent, CardContent,
Checkbox,
Chip, Chip,
CircularProgress, CircularProgress,
FormControl, FormControl,
InputLabel, InputLabel,
ListItemText,
MenuItem, MenuItem,
Paper, Paper,
Select, Select,
SelectChangeEvent,
Stack, Stack,
Table, Table,
TableBody, TableBody,
@@ -65,6 +68,14 @@ function showDoPickOpsButtons(row: WorkbenchTicketReleaseTable): boolean {
); );
} }


const HANDLER_UNASSIGNED = "__UNASSIGNED__";
const HANDLER_SELECT_ALL = "__ALL__";

function handlerFilterKey(handlerName: string | null | undefined): string {
const name = handlerName?.trim() ?? "";
return name || HANDLER_UNASSIGNED;
}

const WorkbenchTicketReleaseTableTab: React.FC = () => { const WorkbenchTicketReleaseTableTab: React.FC = () => {
const { t } = useTranslation("ticketReleaseTable"); const { t } = useTranslation("ticketReleaseTable");
const { data: session } = useSession() as { data: SessionWithTokens | null }; const { data: session } = useSession() as { data: SessionWithTokens | null };
@@ -73,6 +84,7 @@ const WorkbenchTicketReleaseTableTab: React.FC = () => {
const [queryDate, setQueryDate] = useState<Dayjs>(() => dayjs()); const [queryDate, setQueryDate] = useState<Dayjs>(() => dayjs());
const [selectedFloor, setSelectedFloor] = useState<string>(""); const [selectedFloor, setSelectedFloor] = useState<string>("");
const [selectedStatus, setSelectedStatus] = useState<string>("released"); const [selectedStatus, setSelectedStatus] = useState<string>("released");
const [selectedHandlers, setSelectedHandlers] = useState<string[]>([]);
const [data, setData] = useState<WorkbenchTicketReleaseTable[]>([]); const [data, setData] = useState<WorkbenchTicketReleaseTable[]>([]);
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const [now, setNow] = useState(dayjs()); const [now, setNow] = useState(dayjs());
@@ -111,6 +123,28 @@ const WorkbenchTicketReleaseTableTab: React.FC = () => {
}, []); }, []);


const dayStr = queryDate.format("YYYY-MM-DD"); const dayStr = queryDate.format("YYYY-MM-DD");

const handlerOptions = useMemo(() => {
const names = new Set<string>();
let hasUnassigned = false;
for (const item of data) {
if (selectedFloor && item.storeId !== selectedFloor) continue;
const name = item.handlerName?.trim();
if (name) names.add(name);
else hasUnassigned = true;
}
const sorted = Array.from(names).sort((a, b) => a.localeCompare(b, "zh-Hant"));
return hasUnassigned ? [HANDLER_UNASSIGNED, ...sorted] : sorted;
}, [data, selectedFloor]);

useEffect(() => {
setSelectedHandlers((prev) => {
if (prev.length === 0) return prev;
const next = prev.filter((name) => handlerOptions.includes(name));
return next.length === prev.length ? prev : next;
});
}, [handlerOptions]);

const filteredData = useMemo(() => { const filteredData = useMemo(() => {
return data.filter((item) => { return data.filter((item) => {
if (selectedFloor && item.storeId !== selectedFloor) return false; if (selectedFloor && item.storeId !== selectedFloor) return false;
@@ -119,9 +153,27 @@ const WorkbenchTicketReleaseTableTab: React.FC = () => {
if (itemDate !== dayStr) return false; if (itemDate !== dayStr) return false;
} }
if (selectedStatus && item.ticketStatus?.toLowerCase() !== selectedStatus.toLowerCase()) return false; if (selectedStatus && item.ticketStatus?.toLowerCase() !== selectedStatus.toLowerCase()) return false;
if (selectedHandlers.length > 0 && !selectedHandlers.includes(handlerFilterKey(item.handlerName))) {
return false;
}
return true; return true;
}); });
}, [data, dayStr, selectedFloor, selectedStatus]);
}, [data, dayStr, selectedFloor, selectedStatus, selectedHandlers]);

const allHandlersSelected =
handlerOptions.length > 0 && selectedHandlers.length === handlerOptions.length;

const handleHandlersChange = (event: SelectChangeEvent<string[]>) => {
const value = event.target.value;
const next = typeof value === "string" ? value.split(",") : value;
if (next.includes(HANDLER_SELECT_ALL)) {
setSelectedHandlers(allHandlersSelected ? [] : handlerOptions);
setPaginationController((prev) => ({ ...prev, pageNum: 0 }));
return;
}
setSelectedHandlers(next.filter((v) => v !== HANDLER_SELECT_ALL));
setPaginationController((prev) => ({ ...prev, pageNum: 0 }));
};


const paginatedData = useMemo(() => { const paginatedData = useMemo(() => {
const startIndex = paginationController.pageNum * paginationController.pageSize; const startIndex = paginationController.pageNum * paginationController.pageSize;
@@ -247,6 +299,39 @@ const WorkbenchTicketReleaseTableTab: React.FC = () => {
<MenuItem value="completed">{t("completed")}</MenuItem> <MenuItem value="completed">{t("completed")}</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
<FormControl sx={{ minWidth: 200 }} size="small">
<InputLabel id="workbench-handler-select-label" shrink>
{t("Handler Name")}
</InputLabel>
<Select
labelId="workbench-handler-select-label"
multiple
value={selectedHandlers}
label={t("Handler Name")}
onChange={handleHandlersChange}
displayEmpty
renderValue={(selected) => {
if (selected.length === 0) return t("All Handlers");
return selected
.map((v) => (v === HANDLER_UNASSIGNED ? t("Unassigned") : v))
.join(", ");
}}
>
<MenuItem value={HANDLER_SELECT_ALL}>
<Checkbox
checked={allHandlersSelected}
indeterminate={selectedHandlers.length > 0 && !allHandlersSelected}
/>
<ListItemText primary={t("Select All")} />
</MenuItem>
{handlerOptions.map((name) => (
<MenuItem key={name} value={name}>
<Checkbox checked={selectedHandlers.includes(name)} />
<ListItemText primary={name === HANDLER_UNASSIGNED ? t("Unassigned") : name} />
</MenuItem>
))}
</Select>
</FormControl>
<Box sx={{ flexGrow: 1 }} /> <Box sx={{ flexGrow: 1 }} />
<Stack direction="row" spacing={2} sx={{ flexShrink: 0, alignSelf: "center" }}> <Stack direction="row" spacing={2} sx={{ flexShrink: 0, alignSelf: "center" }}>
<Typography variant="body2" sx={{ color: "text.secondary" }} suppressHydrationWarning> <Typography variant="body2" sx={{ color: "text.secondary" }} suppressHydrationWarning>


+ 17
- 5
src/components/FinishedGoodSearch/ReleasedDoPickOrderSelectModal.tsx Просмотреть файл

@@ -36,7 +36,9 @@ export type ReleasedDoPickListBridge = {
shopName?: string, shopName?: string,
storeId?: string, storeId?: string,
truck?: string, truck?: string,
releaseType?: string
releaseType?: string,
/** Optional `2F`/`4F` for Truck X supplier-floor split */
floor?: string
) => Promise<ReleasedDoPickOrderListItem[]>; ) => Promise<ReleasedDoPickOrderListItem[]>;
/** Optional 4th arg: workbench `requiredDeliveryDate` (YYYY-MM-DD) for default truck list; omit = calendar today. */ /** Optional 4th arg: workbench `requiredDeliveryDate` (YYYY-MM-DD) for default truck list; omit = calendar today. */
loadToday: ( loadToday: (
@@ -44,7 +46,9 @@ export type ReleasedDoPickListBridge = {
storeId?: string, storeId?: string,
truck?: string, truck?: string,
requiredDeliveryDate?: string, requiredDeliveryDate?: string,
releaseType?: string
releaseType?: string,
/** Optional `2F`/`4F` for Truck X supplier-floor split */
floor?: string
) => Promise<ReleasedDoPickOrderListItem[]>; ) => Promise<ReleasedDoPickOrderListItem[]>;
assignByListItemId: (userId: number, id: number) => Promise<PostPickOrderResponse>; assignByListItemId: (userId: number, id: number) => Promise<PostPickOrderResponse>;
}; };
@@ -70,6 +74,10 @@ interface Props {
* requiredDate instead of historical released (delivery date before calendar today). * requiredDate instead of historical released (delivery date before calendar today).
*/ */
filterRequiredDeliveryDate?: string; filterRequiredDeliveryDate?: string;
/**
* Truck X only: `2F`/`4F` (or `2/F`/`4/F`) — filter by DO supplier preferred floor; storeId stays null.
*/
truckXFloor?: string;
} }


const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
@@ -85,6 +93,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
releaseTypeFilter, releaseTypeFilter,
initialShopSearch, initialShopSearch,
filterRequiredDeliveryDate, filterRequiredDeliveryDate,
truckXFloor,
}) => { }) => {
const { t } = useTranslation("pickOrder"); const { t } = useTranslation("pickOrder");
const { data: session } = useSession() as { data: SessionWithTokens | null }; const { data: session } = useSession() as { data: SessionWithTokens | null };
@@ -102,6 +111,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
const loadReleased = listBridge?.loadBeforeToday ?? fetchReleasedDoPickOrdersForSelection; const loadReleased = listBridge?.loadBeforeToday ?? fetchReleasedDoPickOrdersForSelection;
const loadTodayFn = listBridge?.loadToday ?? fetchReleasedDoPickOrdersForSelectionToday; const loadTodayFn = listBridge?.loadToday ?? fetchReleasedDoPickOrdersForSelectionToday;


const floorArg = truckXFloor?.trim() || undefined;
if (isDefaultTruck) { if (isDefaultTruck) {
if (defaultDateScopeProp === "today") { if (defaultDateScopeProp === "today") {
data = await loadTodayFn( data = await loadTodayFn(
@@ -109,14 +119,16 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
undefined, undefined,
"車線-X", "車線-X",
defaultTruckRequiredDeliveryDate?.trim() || undefined, defaultTruckRequiredDeliveryDate?.trim() || undefined,
releaseTypeFilter?.trim() || undefined
releaseTypeFilter?.trim() || undefined,
floorArg
); );
} else { } else {
data = await loadReleased( data = await loadReleased(
undefined, undefined,
undefined, undefined,
"車線-X", "車線-X",
releaseTypeFilter?.trim() || undefined
releaseTypeFilter?.trim() || undefined,
floorArg
); );
} }
} else if (filterRequiredDeliveryDate?.trim() && listBridge?.loadToday) { } else if (filterRequiredDeliveryDate?.trim() && listBridge?.loadToday) {
@@ -143,7 +155,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [open, shopSearch, storeId, truck, isDefaultTruck, defaultDateScopeProp, listBridge, defaultTruckRequiredDeliveryDate, releaseTypeFilter, filterRequiredDeliveryDate]);
}, [open, shopSearch, storeId, truck, isDefaultTruck, defaultDateScopeProp, listBridge, defaultTruckRequiredDeliveryDate, releaseTypeFilter, filterRequiredDeliveryDate, truckXFloor]);


useEffect(() => { useEffect(() => {
loadList(); loadList();


+ 21
- 2
src/components/PickOrderSearch/WorkbenchPickExecution.tsx Просмотреть файл

@@ -1655,6 +1655,11 @@ const WorkbenchPickExecution: React.FC<Props> = ({ filterArgs }) => {
const isRowExpired = const isRowExpired =
isWorkbenchSourceLotExpired(r) && !isRowRejected; isWorkbenchSourceLotExpired(r) && !isRowRejected;
const isRowUnavailable = isInventoryLotLineUnavailable(r); const isRowUnavailable = isInventoryLotLineUnavailable(r);
const isRowComplete =
rowStatus === "completed" ||
rowStatus === "checked" ||
rowStatus === "partially_completed" ||
rowStatus === "partially_complete";


return ( return (
<TableRow key={r.key}> <TableRow key={r.key}>
@@ -1678,7 +1683,9 @@ const WorkbenchPickExecution: React.FC<Props> = ({ filterArgs }) => {
? "error.main" ? "error.main"
: isRowExpired || isLotAvailabilityExpired(r) : isRowExpired || isLotAvailabilityExpired(r)
? "warning.main" ? "warning.main"
: "inherit",
: isRowComplete
? "success.main"
: "inherit",
}} }}
> >
{r.lotNo ? ( {r.lotNo ? (
@@ -1833,7 +1840,19 @@ const WorkbenchPickExecution: React.FC<Props> = ({ filterArgs }) => {
return { ...prev, [r.stockOutLineId]: n }; return { ...prev, [r.stockOutLineId]: n };
}); });
}} }}
sx={{ width: 96 }}
sx={{
width: 96,
"& .MuiInputBase-input": {
typography: "body1",
py: 0.5,
textAlign: "center",
"&::-webkit-outer-spin-button, &::-webkit-inner-spin-button": {
WebkitAppearance: "none",
margin: 0,
},
MozAppearance: "textfield",
},
}}
disabled={!r.stockOutLineId || qtyEditableBySolId[r.stockOutLineId] !== true} disabled={!r.stockOutLineId || qtyEditableBySolId[r.stockOutLineId] !== true}
inputProps={{ min: 0, step: 1 }} inputProps={{ min: 0, step: 1 }}
/> />


+ 3
- 0
src/i18n/en/ticketReleaseTable.json Просмотреть файл

@@ -1,6 +1,7 @@
{ {
"Actions": "Actions", "Actions": "Actions",
"All Floors": "All Floors", "All Floors": "All Floors",
"All Handlers": "All Handlers",
"All Statuses": "All Statuses", "All Statuses": "All Statuses",
"Auto-refresh every 1 minute": "Auto-refresh every 1 minute", "Auto-refresh every 1 minute": "Auto-refresh every 1 minute",
"Auto-refresh every 10 minutes": "Auto-refresh every 10 minutes", "Auto-refresh every 10 minutes": "Auto-refresh every 10 minutes",
@@ -30,9 +31,11 @@
"Revert assignment": "Revert assignment", "Revert assignment": "Revert assignment",
"Revert assignment hint": "Revert assignment hint", "Revert assignment hint": "Revert assignment hint",
"Rows per page": "Rows per page", "Rows per page": "Rows per page",
"Select All": "Select All",
"Select Date": "Select Date", "Select Date": "Select Date",
"Shop Name": "Shop Name", "Shop Name": "Shop Name",
"Status": "Status", "Status": "Status",
"Unassigned": "Unassigned",
"Store ID": "Store ID", "Store ID": "Store ID",
"Target Date": "Target Date", "Target Date": "Target Date",
"Ticket Information": "Ticket Information", "Ticket Information": "Ticket Information",


+ 17
- 16
src/i18n/zh/pickOrder.json Просмотреть файл

@@ -12,7 +12,7 @@
"N/A": "不適用", "N/A": "不適用",
"Release Pick Orders": "放單", "Release Pick Orders": "放單",
"released": "已放單", "released": "已放單",
"is unavailable. Please check around have available QR code or not.": "不可用。請檢查周圍是否有可用的 QR 碼。",
"is unavailable. Please check around have available QR code or not.": "不可用。請檢查周圍是否有可用的二維碼。",
"No lot rows. Select a pick order above.": "沒有批次行。請選擇一個提料單。", "No lot rows. Select a pick order above.": "沒有批次行。請選擇一個提料單。",
"Loading...": "載入中...", "Loading...": "載入中...",
"Suggestion success": "建議成功", "Suggestion success": "建議成功",
@@ -65,7 +65,7 @@
"items": "項目", "items": "項目",
"Select Pick Order:": "選擇提料單:", "Select Pick Order:": "選擇提料單:",
"No Stock Available": "沒有庫存", "No Stock Available": "沒有庫存",
"is expired. Please check around have available QR code or not.": "已過期。請檢查周圍是否有可用的 QR 碼。",
"is expired. Please check around have available QR code or not.": "已過期。請檢查周圍是否有可用的二維碼。",
"Start Fail": "開始失敗", "Start Fail": "開始失敗",
"Start PO": "開始採購訂單", "Start PO": "開始採購訂單",
"Do you want to complete?": "確定完成嗎?", "Do you want to complete?": "確定完成嗎?",
@@ -136,7 +136,7 @@
"LotNo": "批號", "LotNo": "批號",
"Po Code": "採購訂單編號", "Po Code": "採購訂單編號",
"No Warehouse": "沒有倉庫", "No Warehouse": "沒有倉庫",
"Please scan warehouse qr code.": "請掃描倉庫 QR 碼。",
"Please scan warehouse qr code.": "請掃描倉庫二維碼。",


"Reject": "拒絕", "Reject": "拒絕",
"submit": "確認提交", "submit": "確認提交",
@@ -270,7 +270,7 @@
"Pick order completed successfully!": "提料單完成成功!", "Pick order completed successfully!": "提料單完成成功!",
"Lot has been rejected and marked as unavailable.": "批號已拒絕並標記為不可用。", "Lot has been rejected and marked as unavailable.": "批號已拒絕並標記為不可用。",
"This order is insufficient, please pick another lot.": "此訂單不足,請選擇其他批號。", "This order is insufficient, please pick another lot.": "此訂單不足,請選擇其他批號。",
"Please finish QR code scan, QC check and pick order.": "請完成 QR 碼掃描、QC 檢查和提料。",
"Please finish QR code scan, QC check and pick order.": "請完成二維碼掃描、QC 檢查和提料。",
"No data available": "沒有資料", "No data available": "沒有資料",
"Please submit the pick order.": "請提交提料單。", "Please submit the pick order.": "請提交提料單。",
"Item lot to be Pick:": "批次貨品提料:", "Item lot to be Pick:": "批次貨品提料:",
@@ -293,13 +293,13 @@
"Original Available Qty": "原可用數", "Original Available Qty": "原可用數",
"Remaining Available Qty": "剩餘可用數", "Remaining Available Qty": "剩餘可用數",
"Please submit pick order.": "請提交提料單。", "Please submit pick order.": "請提交提料單。",
"Please finish QR code scan and pick order.": "請完成 QR 碼掃描和提料。",
"Please finish QR code scanand pick order.": "請完成 QR 碼掃描和提料。",
"Please finish QR code scan and pick order.": "請完成二維碼掃描和提料。",
"Please finish QR code scanand pick order.": "請完成二維碼掃描和提料。",
"First created group": "首次建立分組", "First created group": "首次建立分組",
"Latest created group": "最新建立分組", "Latest created group": "最新建立分組",
"Manual Input": "手動輸入", "Manual Input": "手動輸入",
"QR Code Scan for Lot": " QR 碼掃描批次",
"Processing QR code...": "處理 QR 碼...",
"QR Code Scan for Lot": "二維碼掃描批次",
"Processing QR code...": "處理二維碼...",
"The input is not the same as the expected lot number.": "輸入的批次號碼與預期的不符。", "The input is not the same as the expected lot number.": "輸入的批次號碼與預期的不符。",
"Verified successfully!": "驗證成功!", "Verified successfully!": "驗證成功!",
"Cancel": "取消", "Cancel": "取消",
@@ -370,7 +370,7 @@
"Scanning...":"掃描中...", "Scanning...":"掃描中...",
"Print DN/Label":"列印送貨單/標籤", "Print DN/Label":"列印送貨單/標籤",
"Store ID":"儲存編號", "Store ID":"儲存編號",
"QR code does not match any item in current orders.":"QR 碼不符合當前訂單中的任何貨品。",
"QR code does not match any item in current orders.":"二維碼不符合當前訂單中的任何貨品。",
"Lot Number Mismatch":"批次號碼不符", "Lot Number Mismatch":"批次號碼不符",
"The scanned item matches the expected item, but the lot number is different. Do you want to proceed with this different lot?":"掃描的貨品與預期的貨品相同,但批次號碼不同。您是否要繼續使用不同的批次?", "The scanned item matches the expected item, but the lot number is different. Do you want to proceed with this different lot?":"掃描的貨品與預期的貨品相同,但批次號碼不同。您是否要繼續使用不同的批次?",
"The scanned item matches the expected item, but the lot number is different. Scan again to confirm: scan the expected lot QR to keep the suggested lot, or scan the other lot QR again to switch.":"掃描貨品相同但批次不同。請再掃描一次以確認:掃描「建議批次」的 QR 可沿用該批次;再掃描「另一批次」的 QR 則切換為該批次。", "The scanned item matches the expected item, but the lot number is different. Scan again to confirm: scan the expected lot QR to keep the suggested lot, or scan the other lot QR again to switch.":"掃描貨品相同但批次不同。請再掃描一次以確認:掃描「建議批次」的 QR 可沿用該批次;再掃描「另一批次」的 QR 則切換為該批次。",
@@ -389,7 +389,7 @@
"Lot switch failed":"批次切換失敗", "Lot switch failed":"批次切換失敗",
"The system could not switch to the scanned lot. Review the lots below, then tap Confirm to retry.":"系統無法切換至掃描的批次。請核對下方批次後按「確認」重試。", "The system could not switch to the scanned lot. Review the lots below, then tap Confirm to retry.":"系統無法切換至掃描的批次。請核對下方批次後按「確認」重試。",
"You can also scan again: expected lot QR keeps the suggested line; scanned lot QR retries the switch.":"您也可以再掃描:掃描建議批次 QR 可保留該行;掃描欲切換批次 QR 可再次嘗試切換。", "You can also scan again: expected lot QR keeps the suggested line; scanned lot QR retries the switch.":"您也可以再掃描:掃描建議批次 QR 可保留該行;掃描欲切換批次 QR 可再次嘗試切換。",
"QR code verified.":"QR 碼驗證成功。",
"QR code verified.":"二維碼驗證成功。",
"Order Finished":"訂單完成", "Order Finished":"訂單完成",
"Submitted Status":"提交狀態", "Submitted Status":"提交狀態",
"Pick Execution Record":"提料執行記錄", "Pick Execution Record":"提料執行記錄",
@@ -507,16 +507,17 @@
"packaging": "提料中", "packaging": "提料中",
"No Stock Available": "沒有庫存可用", "No Stock Available": "沒有庫存可用",
"This lot is not available, please scan another lot.": "此批號不可用,請掃描其他批號。", "This lot is not available, please scan another lot.": "此批號不可用,請掃描其他批號。",
"This lot UOM does not match the pick line. Please scan another lot.": "此批號單位與提貨行不符,請掃描其他批號。",
"Scanned lot UOM does not match pick line UOM": "此批號單位與提貨行不符,請掃描其他批號。",
"This lot UOM does not match the pick line. Please scan another lot.": "此批號單位不符,請掃描其他批號。",
"Scanned lot UOM does not match pick line UOM": "此批號單位不符,請掃描其他批號。",
"This lot has already been picked. Please scan another lot.": "此批號已提貨,請掃描其他批號。", "This lot has already been picked. Please scan another lot.": "此批號已提貨,請掃描其他批號。",
"Scanned lot is already completed or checked": "此物料已提貨,請掃描其他批號。", "Scanned lot is already completed or checked": "此物料已提貨,請掃描其他批號。",
"Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.": "請檢查周圍是否有其他可用 QR 碼。",
"Please check around have QR code or not, may be have just now stock in or transfer in or transfer out.": "請檢查周圍是否有其他可用二維碼。",
"Lot is expired (expiry={{expiry}})": "掃描批號已過期(到期日={{expiry}})", "Lot is expired (expiry={{expiry}})": "掃描批號已過期(到期日={{expiry}})",
"Day After Tomorrow": "後日", "Day After Tomorrow": "後日",
"Lot line is unavailable": "掃描批次不可用", "Lot line is unavailable": "掃描批次不可用",
"Select Date": "請選擇日期", "Select Date": "請選擇日期",
"Suggest Lot No.": "推薦批號",
"Suggest Lot No.": "推薦批號/實際批號",
"Search by Shop": "搜索商店", "Search by Shop": "搜索商店",
"Search by Truck": "搜索貨車", "Search by Truck": "搜索貨車",
"Print DN & Label": "列印提料單和送貨單標籤", "Print DN & Label": "列印提料單和送貨單標籤",
@@ -545,13 +546,13 @@
"4F ticket": "4/F 票", "4F ticket": "4/F 票",
"4F lane panel legend": "貨車班次 — 裝載序(未撳數/總單數)", "4F lane panel legend": "貨車班次 — 裝載序(未撳數/總單數)",
"Loading sequence n": "板{{n}}", "Loading sequence n": "板{{n}}",
"lot QR code": "批號 QR 碼",
"lot QR code": "批號二維碼",
"label Printer" : "標籤打印機", "label Printer" : "標籤打印機",
"A4 Printer" : "A4 打印機", "A4 Printer" : "A4 打印機",
"Loading Sequence": "裝載序", "Loading Sequence": "裝載序",
"Ticket No": "提票號碼", "Ticket No": "提票號碼",
"The scanned lot inventory line is unavailable. Cannot switch or bind; pick line was not updated.": "掃描的庫存批行為「不可用」,無法換批或綁定;揀貨行未更新。", "The scanned lot inventory line is unavailable. Cannot switch or bind; pick line was not updated.": "掃描的庫存批行為「不可用」,無法換批或綁定;揀貨行未更新。",
"is unavable. Please check around have available QR code or not.": "此批號不可用,請檢查周圍是否有可用的 QR 碼。",
"is unavable. Please check around have available QR code or not.": "此批號不可用,請檢查周圍是否有可用的二維碼。",
"Lot switch failed; pick line was not marked as checked.": "換批失敗;揀貨行未標為已核對。", "Lot switch failed; pick line was not marked as checked.": "換批失敗;揀貨行未標為已核對。",
"Lot confirmation failed. Please try again.": "確認批號失敗,請重試。", "Lot confirmation failed. Please try again.": "確認批號失敗,請重試。",
"Powder Mixture": "箱料粉", "Powder Mixture": "箱料粉",


+ 3
- 0
src/i18n/zh/ticketReleaseTable.json Просмотреть файл

@@ -25,6 +25,9 @@
"Released Time": "開始時間", "Released Time": "開始時間",
"Completed Time": "完成時間", "Completed Time": "完成時間",
"Handler Name": "負責員工", "Handler Name": "負責員工",
"All Handlers": "所有員工",
"Select All": "全選",
"Unassigned": "未分配",
"Number of FG Items (Order Item(s) Count)": "訂單項目數量", "Number of FG Items (Order Item(s) Count)": "訂單項目數量",
"No data available": "沒有資料", "No data available": "沒有資料",
"Rows per page": "每頁行數", "Rows per page": "每頁行數",


Загрузка…
Отмена
Сохранить