Bladeren bron

update putaway scan

master
kelvinsuen 3 dagen geleden
bovenliggende
commit
a51e5f9dd5
10 gewijzigde bestanden met toevoegingen van 654 en 17 verwijderingen
  1. +1
    -1
      src/app/(main)/putAway/page.tsx
  2. +1
    -1
      src/components/PoDetail/PutAwayForm.tsx
  3. +12
    -0
      src/components/PoDetail/QcComponent.tsx
  4. +4
    -4
      src/components/PoDetail/QcStockInModal.tsx
  5. +419
    -0
      src/components/PutAwayScan/PutAwayModal.tsx
  6. +190
    -4
      src/components/PutAwayScan/PutAwayScan.tsx
  7. +8
    -1
      src/components/PutAwayScan/PutAwayScanWrapper.tsx
  8. +2
    -2
      src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx
  9. +1
    -1
      src/i18n/zh/purchaseOrder.json
  10. +16
    -3
      src/i18n/zh/putAway.json

+ 1
- 1
src/app/(main)/putAway/page.tsx Bestand weergeven

@@ -28,7 +28,7 @@ const PutAway: React.FC = async () => {
{t("Put Away")}
</Typography>
</Stack>
<I18nProvider namespaces={["putAway", "common"]}>
<I18nProvider namespaces={["putAway", "purchaseOrder", "common"]}>
<Suspense fallback={<PutAwayScan.Loading />}>
<PutAwayScan />
</Suspense>


+ 1
- 1
src/components/PoDetail/PutAwayForm.tsx Bestand weergeven

@@ -540,7 +540,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, setRowM
_formKey={"putAwayLines"}
columns={columns}
validateRow={validation}
needAdd={true}
needAdd={false}
showRemoveBtn={false}
addRowDefaultValue={addRowDefaultValue}
_setRowModesModel={setRowModesModel}


+ 12
- 0
src/components/PoDetail/QcComponent.tsx Bestand weergeven

@@ -527,6 +527,18 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
<Grid item xs={12}>
<EscalationLogTable type="qc" items={itemDetail.escResult || []}/>
<CollapsibleCard title={t("QC Record")}>
<Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}>
<Typography variant="h5" component="h2" sx={{ fontWeight: 'bold', color: '#333' }}>
Group A - 急凍貨類 (QCA1-MEAT01)
</Typography>
<Typography variant="subtitle1" sx={{ color: '#666' }}>
<b>品檢類型</b>:IQC
</Typography>
<Typography variant="subtitle2" sx={{ color: '#666' }}>
記錄探測溫度的時間,請在1小時内完成卸貨盤點入庫,以保障食品安全<br/>
監察方法:目視檢查、嗅覺檢查和使用適當的食物溫度計,檢查食物溫度是否符合指標
</Typography>
</Box>
<StyledDataGrid
columns={qcColumns}
rows={qcHistory}


+ 4
- 4
src/components/PoDetail/QcStockInModal.tsx Bestand weergeven

@@ -404,10 +404,10 @@ const [qcItems, setQcItems] = useState(dummyQCData)
console.log("Putaway Data:", putawayData);

console.log("DEBUG",data.putAwayLines);
if (data.putAwayLines!!.filter((line) => line._isNew !== false).length <= 0) {
alert("請新增上架資料!");
return;
}
// if (data.putAwayLines!!.filter((line) => line._isNew !== false).length <= 0) {
// alert("請新增上架資料!");
// return;
// }
if (data.putAwayLines!!.filter((line) => /[^0-9]/.test(String(line.qty))).length > 0) { //TODO Improve
alert("上架數量不正確!");
return;


+ 419
- 0
src/components/PutAwayScan/PutAwayModal.tsx Bestand weergeven

@@ -0,0 +1,419 @@
"use client";

import {
Box,
Button,
Grid,
Modal,
ModalProps,
Stack,
TextField,
Typography,
Paper,
} from "@mui/material";
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react";
import ReactQrCodeScanner, {
ScannerConfig,
} from "../ReactQrCodeScanner/ReactQrCodeScanner";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import {
fetchStockInLineInfo,
ModalFormInput,
StockInLineEntry,
updateStockInLine,
} from "@/app/api/po/actions";
import { StockInLine } from "@/app/api/po";
import { WarehouseResult } from "@/app/api/warehouse";
// import { QrCodeInfo } from "@/app/api/qrcde";
import { Check, QrCode } from "@mui/icons-material";
import { useTranslation } from "react-i18next";
import { useSearchParams } from "next/navigation";
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import LoadingComponent from "../General/LoadingComponent";
import StockInForm from "../PoDetail/StockInForm";
import { arrayToDateString, INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import { QrCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import { msg } from "../Swal/CustomAlerts";


interface Props extends Omit<ModalProps, "children"> {
warehouse: WarehouseResult[];
stockInLineId: number;
warehouseId: number;
scanner: QrCodeScanner;
}
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
bgcolor: "background.paper",
pt: 5,
px: 5,
pb: 10,
// width: "auto",
width: { xs: "90%", sm: "90%", md: "90%" },
};

const scannerStyle = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
bgcolor: "background.paper",
pt: 5,
px: 5,
pb: 10,
// width: "auto",
width: { xs: "60%", sm: "60%", md: "60%" },
};

const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId, warehouseId, scanner }) => {
const { t } = useTranslation("putAway");
const [serverError, setServerError] = useState("");
const params = useSearchParams();

const [isOpenScanner, setIsOpenScanner] = useState<boolean>(false);
const [itemDetail, setItemDetail] = useState<StockInLine>();
const [unavailableText, setUnavailableText] = useState<string | undefined>(
undefined,
);
const [putQty, setPutQty] = useState<number>(itemDetail?.demandQty ?? 0);
const defaultNewValue = useMemo(() => {
// console.log("%c ItemDetail", "color:purple", itemDetail);
return (
{
...itemDetail,
// status: itemDetail.status ?? "pending",
dnDate: arrayToDateString(itemDetail?.dnDate, "input")?? undefined,
// // putAwayLines: dummyPutAwayLine,
// // putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [],
// putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false})) ?? [],
// // qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData],
// escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [],
productionDate: itemDetail?.productionDate ? arrayToDateString(itemDetail?.productionDate, "input") : undefined,
expiryDate: itemDetail?.expiryDate ? arrayToDateString(itemDetail?.expiryDate, "input") : undefined,
receiptDate: itemDetail?.receiptDate ? arrayToDateString(itemDetail?.receiptDate, "input") : undefined,
// acceptQty: itemDetail.demandQty?? itemDetail.acceptedQty,
defaultWarehouseId: itemDetail?.defaultWarehouseId ?? 1,
}
)
}, [itemDetail])

const formProps = useForm<ModalFormInput>({
defaultValues: {
...defaultNewValue,
},
});
const errors = formProps.formState.errors;


const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
(...args) => {
setVerified(false);
setItemDetail(undefined);
onClose?.(...args);
// reset();
},
[onClose],
);

const scannerCloseHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
(...args) => {
setIsOpenScanner(false);
scanner.stopScan();
console.log("%c Scanning stopped ", "color:cyan");
},
[],
);

const openScanner = () => {
setIsOpenScanner(true);
scanner.startScan();
console.log("%c Scanning started ", "color:cyan");
};

useEffect(() => {
if (warehouseId > 0) { // Scanned Warehouse
if (isOpenScanner) {
setIsOpenScanner(false);
setVerified(true);
scanner.stopScan();
console.log("%c Scanner stopped", "color:cyan");
}
}
}, [warehouseId])

useLayoutEffect(() => {
if (itemDetail !== undefined) {
if (itemDetail.status == "received") {
formProps.reset({
...defaultNewValue
})
setPutQty(itemDetail?.demandQty);
console.log("%c Loaded data:", "color:lime", defaultNewValue);
} else {
switch (itemDetail.status) {
case "pending": alert("此貨品有待品檢"); break;
case "rejected": alert("此貨品已被拒收"); break;
case "escalated": alert("此貨品已被上報"); break;
case "partially_completed": alert("此貨品已部分上架"); break;
case "completed": alert("此貨品已上架"); break;
default: alert("此貨品暫時無法上架"); break;
}
closeHandler({}, "backdropClick");
}
}
}, [open, itemDetail, defaultNewValue])

const fetchStockInLine = useCallback(
async (stockInLineId: number) => {
setUnavailableText(undefined);
const res = await fetchStockInLineInfo(stockInLineId);
console.log("%c Fetched Stock In Line Info:", "color:gold", res);
setItemDetail(res);
},
[formProps, itemDetail, fetchStockInLineInfo],
);

useEffect(() => {
if (stockInLineId) { fetchStockInLine(stockInLineId); }
}, [stockInLineId]);

const [verified, setVerified] = useState<boolean>(false);

const [qtyError, setQtyError] = useState<string>("");

const validateQty = useCallback((qty : number = putQty) => {
// if (isNaN(putQty) || putQty === undefined || putQty === null || typeof(putQty) != "number") {
// setQtyError(t("value must be a number"));
// } else
if (!Number.isInteger(qty)) {
setQtyError(t("value must be integer"));
}
if (qty > itemDetail?.acceptedQty!!) {
setQtyError(`${t("putQty must not greater than")} ${
itemDetail?.acceptedQty}` );
} else
if (qty < 1) {
setQtyError(t("minimal value is 1"));
} else {
setQtyError("");
}
console.log("%c Validated putQty:", "color:yellow", putQty);
},[setQtyError, putQty, itemDetail])

const onSubmit = useCallback<SubmitHandler<ModalFormInput>>(
async (data, event) => {
// console.log("errors", errors);
// const lotLine = {
// warehouseId: warehouseId,
// qty: acceptQty;
// }
try {
const args = {
// ...itemDetail,
id: itemDetail?.id,
purchaseOrderId: itemDetail?.purchaseOrderId,
purchaseOrderLineId: itemDetail?.purchaseOrderLineId,
itemId: itemDetail?.itemId,
acceptedQty: itemDetail?.acceptedQty,
acceptQty: itemDetail?.demandQty,
status: "received",
// purchaseOrderId: parseInt(params.get("id")!),
// purchaseOrderLineId: itemDetail?.purchaseOrderLineId,
// itemId: itemDetail?.itemId,
// acceptedQty: data.acceptedQty,
// status: data.status,
// ...data,
// productionDate: productionDate,
// for putaway data
inventoryLotLines: [{
warehouseId: warehouseId,
qty: putQty,
}],
// data.putAwayLines?.filter((line) => line._isNew !== false)

} as StockInLineEntry & ModalFormInput;

console.log(args);
// return
// if (formProps.formState.errors) {
// setServerError(t("An error has occurred. Please try again later."));
// return false;
// }
if (qtyError !== "") {
return;
}
console.log("%c Submitting Data:", "color:blue", args);
const res = await updateStockInLine(args);
if (Boolean(res.id)) {
// update entries
console.log("%c Update Success:", "color:green", res);
// add loading
msg("貨品上架成功!");
closeHandler({}, "backdropClick");
}
console.log(res);
// if (res)
} catch (e) {
// server error
setServerError(t("An error has occurred. Please try again later."));
console.log(e);
}
},
[t, itemDetail, putQty, warehouseId],
);

return (
<FormProvider {...formProps}>
<Modal open={open} onClose={closeHandler}>
<Box
sx={style}
component="form"
onSubmit={formProps.handleSubmit(onSubmit)}
>
<Grid container xs={12}>
<Grid item xs={12}>
{itemDetail != undefined ? (
<>
<Stack direction="column" justifyContent="flex-end" gap={1}>
<Typography variant="h4">
處理上架
</Typography>

<Grid item xs={12}>
<StockInForm itemDetail={itemDetail} disabled={true} putawayMode={true}/>
</Grid>
<Paper sx={{ padding: 2, width: "100%" }}>
<Grid container spacing={2}>
<Grid item xs={3}>
<TextField
type="number" // TODO fix the "e" input
label={t("putQty")}
fullWidth
defaultValue={itemDetail.demandQty}
onChange={(e) => {
const value = e.target.value;
validateQty(Number(value));
setPutQty(Number(value));
}}
// onBlur={(e) => {
// const value = e.target.value;
// setPutQty(Number(value));
// validateQty(Number(value));
// }}
// disabled={true}
// {...register("acceptedQty", {
// required: "acceptedQty required!",
// })}
error={qtyError !== ""}
helperText={qtyError}
/>
</Grid>
<Grid item xs={3}>
<Button
id="scanWarehouse"
variant="contained"
startIcon={<QrCode />}
color="primary"
// sx={{ mx: 3, minWidth : "120px", height: "80px",
// padding: "12px 12px", fontSize: "24px"}}
sx={{
flex: "0 0 auto",
padding: "8px 16px",
fontSize: { xs: "16px", sm: "20px", md: "24px" },
whiteSpace: "nowrap",
textAlign: "center",}}
onClick={openScanner}>

{t("scan warehouse")}
</Button>
</Grid>
<Grid item xs={3}>
<TextField
key={warehouseId}
label={t("warehouse")}
fullWidth
disabled={true}
value={
warehouseId > 0 ? warehouse.find((w) => w.id == warehouseId)?.name
: warehouse.find((w) => w.id == 3)?.name
// : warehouse.find((w) => w.id == itemDetail.defaultWarehouseId)?.name //TODO fix empty
}
// {...register("acceptedQty", {
// required: "acceptedQty required!",
// })}
error={!verified}
helperText={verified ? "掃碼完成" : "等待掃碼"}
/>
</Grid>
{/* <Stack direction="row" justifyContent="flex-end" sx={{ mt: 1 }}> */}
<Grid item xs={3}>
<Button
id="putawaySubmit"
type="submit"
variant="contained"
startIcon={<Check />}
color="primary"
// sx={{ mx: 3, minWidth: "120px", height: "120px",
// padding: "12px 12px", fontSize: "24px"}}
sx={{
flex: "0 0 auto",
padding: "8px 16px",
fontSize: { xs: "16px", sm: "20px", md: "24px" },
whiteSpace: "nowrap",
textAlign: "center",}}
// onClick={formProps.handleSubmit()}
disabled={!verified}
>
{t("confirm putaway")}
</Button>
</Grid>
</Grid>
</Paper>
</Stack>
</>
) : (
// <ReactQrCodeScanner scannerConfig={scannerConfig} />
<>
<Typography variant="h4">
{t("scan loading")}
</Typography>
<LoadingComponent/>
</>
)}
</Grid>
</Grid>

<Modal open={isOpenScanner} onClose={scannerCloseHandler}>
<Box sx={scannerStyle}>
<Typography variant="h4" sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
margin: 0,
alignItems: 'center',
textAlign: 'center',}}
>
{t("Please scan warehouse qr code")}
</Typography>
{/* <ReactQrCodeScanner scannerConfig={scannerConfig} /> */}
</Box>
</Modal>
</Box>
</Modal>
</FormProvider>
);
};

export default PutAwayModal;

+ 190
- 4
src/components/PutAwayScan/PutAwayScan.tsx Bestand weergeven

@@ -1,12 +1,198 @@
import React from "react";
"use client";

import {
Box,
Button,
Paper,
Grid,
Modal,
ModalProps,
Stack,
Typography,
} from "@mui/material";
import { useCallback, useEffect, useMemo, useState } from "react";
import ReactQrCodeScanner, {
ScannerConfig,
} from "../ReactQrCodeScanner/ReactQrCodeScanner";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import {
fetchStockInLineInfo,
ModalFormInput,
StockInLineEntry,
updateStockInLine,
} from "@/app/api/po/actions";
import { StockInLine } from "@/app/api/po";
import { WarehouseResult } from "@/app/api/warehouse";
import { QrCodeInfo } from "@/app/api/qrcode";
import { Check, QrCodeScanner, Warehouse } from "@mui/icons-material";
import { useTranslation } from "react-i18next";
import { useSearchParams } from "next/navigation";
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import PutAwayModal from "./PutAwayModal";

type Props = {

warehouse : WarehouseResult[];
};

const PutAwayScan: React.FC<Props> = ({} ) => {
return <></>
type ScanStatusType = "pending" | "rescan";

const PutAwayScan: React.FC<Props> = ({ warehouse }) => {
const { t } = useTranslation("putAway");
const [scanDisplay, setScanDisplay] = useState<ScanStatusType>("pending");
const [openPutAwayModal, setOpenPutAwayModal] = useState(false);
const [scannedSilId, setScannedSilId] = useState<number>(0); // TODO use QR code info
const [scannedWareHouseId, setScannedWareHouseId] = useState<number>(0); // TODO use QR code info

// QR Code Scanner
const scanner = useQrCodeScannerContext();
useEffect(() => {
if (!scanner.isScanning) {
scanner.startScan();
console.log("%c Scanning started ", "color:cyan");
} else if (scanner.isScanning) {
scanner.stopScan();
console.log("%c Scanning stopped ", "color:cyan");
}
}, []);

const resetScan = (error : string = "") => {
if (error !== "") {
console.log("%c Scan failed, error: ", "color:red", error);
setScanDisplay("rescan");
} else {
console.log("%c Scan reset", "color:red");
}
scanner.resetScan();
};

const openModal = () => {
console.log("Scanned successfully and open modal");
scanner.stopScan();
console.log("%c Scanning stopped ", "color:cyan");
setOpenPutAwayModal(true);
setScanDisplay("pending");
};
const closeModal = () => {
setScannedSilId(0);
setScannedWareHouseId(0);
setScanDisplay("pending");
console.log("Modal Closed");
scanner.startScan();
console.log("%c Scanning started ", "color:cyan");
setOpenPutAwayModal(false);
}


const findIdByRoughMatch = (inputString : string, keyword : string) => {
const keywordIndex = inputString.indexOf(keyword);
if (keywordIndex === -1) {
return {
keywordFound: false,
number: null,
message: `${keyword} not found in the input`,
};
}
const substringAfterKeyword = inputString.slice(keywordIndex + keyword.length);
const numberMatch = substringAfterKeyword.match(/\d+/);
if (!numberMatch) {
return {
keywordFound: true,
number: null,
message: `No valid number found after ${keyword}`,
};
}
return {
keywordFound: true,
number: parseInt(numberMatch[0], 10),
message: `Found ${keyword} at index ${keywordIndex}, first number found after is: ${numberMatch[0]}`,
};
}


useEffect(() => {
if (scannedSilId > 0) {
openModal();
}
}, [scannedSilId])

// Get Scanned Values
useEffect(() => {
if (scanner.values.length > 0) {//} && !Boolean(itemDetail)) {
const scannedValues = scanner.values[0];
console.log("%c Scanned: ", "color:cyan", scannedValues);

if (scannedValues.substring(0, 8) == "{2fitest") { // DEBUGGING
const number = scannedValues.substring(8, scannedValues.length - 1);
if (/^\d+$/.test(number)) { // Check if number contains only digits
console.log("%c DEBUG: Testing SIL ID: ", "color:cyan", number);
if (scannedSilId === 0) {
setScannedSilId(Number(number));
} else setScannedWareHouseId(Number(number));
} else {
console.error("%c DEBUG: Invalid number format: ", "color:red", number);
resetScan();
}
return;
}
try {
const data: QrCodeInfo = JSON.parse(scannedValues);
console.log("%c Scanned with data", "color:green", data);
if (scannedSilId == 0) { // Initial State
if (data.stockInLineId !== undefined) {
setScannedSilId(Number(data.stockInLineId));
} else resetScan("Cannot read Stock In Line Id");
} else { // Processing
if (data.warehouseId !== undefined) {
setScannedWareHouseId(Number(data.warehouseId));
} else resetScan("Cannot read Warehouse Id");
}
} catch (error) { // Rought match for other scanner -- Pending Review
if (scannedSilId == 0) {
const silId = findIdByRoughMatch(scannedValues, "StockInLine").number ?? 0;
setScannedSilId(silId);

} else {
const whId = findIdByRoughMatch(scannedValues, "warehouseId").number ?? 0;
setScannedWareHouseId(whId);
}

resetScan(String(error));
}
scanner.resetScan();
}
}, [scanner.values]);

return (<>
<Paper sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',}}
>
<Typography variant="h4">
{scanDisplay == "pending" ? t("Pending scan") : t("Rescan")}
</Typography>
<QrCodeScanner sx={{padding: "10px", fontSize : "150px"}}/>
</Paper>
<PutAwayModal
open={openPutAwayModal}
onClose={closeModal}
warehouse={warehouse}
stockInLineId={scannedSilId}
warehouseId={scannedWareHouseId}
scanner={scanner}
/>
</>)
}

export default PutAwayScan;

+ 8
- 1
src/components/PutAwayScan/PutAwayScanWrapper.tsx Bestand weergeven

@@ -1,13 +1,20 @@
import React from "react";
import GeneralLoading from "../General/GeneralLoading";
import PutAwayScan from "./PutAwayScan";
import { fetchWarehouseList } from "@/app/api/warehouse";

interface SubComponents {
Loading: typeof GeneralLoading;
}

const PutAwayScanWrapper: React.FC & SubComponents = async () => {
return <PutAwayScan/>
const [
warehouse,
] = await Promise.all([
fetchWarehouseList(),
])

return <PutAwayScan warehouse={warehouse}/>
}

PutAwayScanWrapper.Loading = GeneralLoading;


+ 2
- 2
src/components/QrCodeScannerProvider/QrCodeScannerProvider.tsx Bestand weergeven

@@ -8,7 +8,7 @@ import {
useState,
} from "react";

interface QrCodeScanner {
export interface QrCodeScanner {
values: string[];
isScanning: boolean;
startScan: () => void;
@@ -82,7 +82,7 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({
keys.join("").substring(startBrace, endBrace + 1),
]);
console.log(keys);
console.log(qrCodeScannerValues);
console.log("%c QR Scanner Values:", "color:cyan", qrCodeScannerValues);

// reset
setKeys(() => []);


+ 1
- 1
src/i18n/zh/purchaseOrder.json Bestand weergeven

@@ -120,7 +120,7 @@
"Escalation Result": "上報結果",
"update qc info": "更新品檢資料",
"email supplier": "電郵供應商",
"confirm putaway": "確定及上架",
"confirm putaway": "確定及完成上架",
"confirm qc result": "確定品檢結果",
"warehouse": "倉庫",
"qcItem": "品檢項目",


+ 16
- 3
src/i18n/zh/putAway.json Bestand weergeven

@@ -1,4 +1,17 @@
{
"Put Away": "上架",
"Put Away Scan": "上架掃碼"
}
"Put Away": "上架",
"Put Away Scan": "上架掃碼",
"Pending scan": "等待掃瞄中,請掃瞄貨品二維碼開始上架程序",
"Rescan": "讀取不成功,請重新掃瞄",
"acceptedQty": "是次來貨數量",
"confirm putaway": "確定及上架貨物",
"scan warehouse": "掃瞄倉庫二維碼",
"Please scan warehouse qr code": "請掃瞄倉庫二維碼",
"scan loading": "載入中,請稍候…",
"warehouse": "倉庫",
"putQty": "上架數量",
"minimal value is 1": "最小為1",
"putQty must not greater than": "上架數量不得大於",
"value must be integer": "必須是整數",
"value must be a number": "必須是數字"
}

Laden…
Annuleren
Opslaan