Browse Source

update

master
CANCERYS\kw093 2 weeks ago
parent
commit
cdc7770db0
15 changed files with 310 additions and 424 deletions
  1. +3
    -0
      src/app/api/jo/actions.ts
  2. +26
    -11
      src/app/api/pickOrder/actions.ts
  3. +1
    -1
      src/components/InputDataGrid/InputDataGrid.tsx
  4. +25
    -15
      src/components/JoSearch/JoCreateFormModal.tsx
  5. +2
    -2
      src/components/JoSearch/JoSearch.tsx
  6. +2
    -2
      src/components/Jodetail/JodetailSearch.tsx
  7. +118
    -309
      src/components/PickOrderSearch/LotTable.tsx
  8. +74
    -31
      src/components/PickOrderSearch/PickExecution.tsx
  9. +7
    -6
      src/components/PickOrderSearch/PickExecutionForm.tsx
  10. +5
    -5
      src/components/PickOrderSearch/PickOrderSearch.tsx
  11. +6
    -4
      src/components/PickOrderSearch/PickQcStockInModalVer3.tsx
  12. +22
    -13
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
  13. +6
    -5
      src/i18n/zh/common.json
  14. +12
    -20
      src/i18n/zh/jo.json
  15. +1
    -0
      src/i18n/zh/pickOrder.json

+ 3
- 0
src/app/api/jo/actions.ts View File

@@ -13,6 +13,8 @@ export interface SaveJo {
planEnd: string;
reqQty: number;
type: string;
//jobType?: string;
jobTypeId?: number;
}

export interface SaveJoResponse {
@@ -703,6 +705,7 @@ export const isCorrectMachineUsed = async (machineCode: string) => {

export const fetchJos = cache(async (data?: SearchJoResultRequest) => {
const queryStr = convertObjToURLSearchParams(data)
console.log("queryStr", queryStr)
const response = serverFetchJson<SearchJoResultResponse>(
`${BASE_API_URL}/jo/getRecordByPage?${queryStr}`,
{


+ 26
- 11
src/app/api/pickOrder/actions.ts View File

@@ -116,8 +116,15 @@ export interface GetPickOrderLineInfo {

suggestedList: any[];
pickedQty: number;
noLotLines: NoLotLineDto[];
}
export interface NoLotLineDto {
stockOutLineId: number;
status: string;
qty: number;
created: string;
modified: string;
}

export interface CurrentInventoryItemInfo {
id: number;
code: string;
@@ -743,18 +750,26 @@ export const fetchPickOrderDetails = cache(async (ids: string) => {
);
});
export interface PickOrderLotDetailResponse {
lotId: number;
lotNo: string;
expiryDate: string;
location: string;
stockUnit: string;
inQty: number;
availableQty: number;
lotId: number | null; // ✅ 改为可空
lotNo: string | null; // ✅ 改为可空
expiryDate: string | null; // ✅ 改为可空
location: string | null; // ✅ 改为可空
stockUnit: string | null;
inQty: number | null;
availableQty: number | null; // ✅ 改为可空
requiredQty: number;
actualPickQty: number;
suggestedPickLotId: number;
lotStatus: string;
lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected';
suggestedPickLotId: number | null;
lotStatus: string | null;
lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable' | 'rejected';
stockOutLineId: number | null; // ✅ 添加
stockOutLineStatus: string | null; // ✅ 添加
stockOutLineQty: number | null; // ✅ 添加
totalPickedByAllPickOrders: number | null; // ✅ 添加
remainingAfterAllPickOrders: number | null; // ✅ 添加
noLot: boolean; // ✅ 关键:添加 noLot 字段
outQty?: number; // ✅ 添加
holdQty?: number; // ✅ 添加
}
interface ALLPickOrderLotDetailResponse {
// Pick Order Information


+ 1
- 1
src/components/InputDataGrid/InputDataGrid.tsx View File

@@ -423,7 +423,7 @@ const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
};
const NoRowsOverlay: React.FC = () => {
const { t } = useTranslation("home");
const { t } = useTranslation("purchaseOrder");
return (
<Box
display="flex"


+ 25
- 15
src/components/JoSearch/JoCreateFormModal.tsx View File

@@ -3,7 +3,7 @@ import { JoDetail } from "@/app/api/jo";
import { SaveJo, manualCreateJo } from "@/app/api/jo/actions";
import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dateStringToDayjs, dayjsToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil";
import { Check } from "@mui/icons-material";
import { Autocomplete, Box, Button, Card, Grid, Modal, Stack, TextField, Typography } from "@mui/material";
import { Autocomplete, Box, Button, Card, Grid, Modal, Stack, TextField, Typography ,FormControl, InputLabel, Select, MenuItem} from "@mui/material";
import { DatePicker, DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs, { Dayjs } from "dayjs";
@@ -52,6 +52,10 @@ const JoCreateFormModal: React.FC<Props> = ({

const onSubmit = useCallback<SubmitHandler<SaveJo>>(async (data) => {
data.type = "manual"
if (data.planStart) {
const dateDayjs = dateStringToDayjs(data.planStart)
data.planStart = dayjsToDateTimeString(dateDayjs.startOf('day'))
}
const response = await manualCreateJo(data)
if (response) {
onSearch();
@@ -164,17 +168,20 @@ const JoCreateFormModal: React.FC<Props> = ({
/>
</Grid>
<Grid item xs={12} sm={12} md={6}>
<TextField
{...register("reqQty", {
required: "Req. Qty. required!",
validate: (value) => value > 0
})}
label={t("Job Type")}
fullWidth
error={Boolean(errors.reqQty)}
variant="outlined"
type="number"
/>
<FormControl fullWidth>
<InputLabel>{t("Job Type")}</InputLabel>
<Select
label={t("Job Type")}
defaultValue=""
>
<MenuItem value="1">{t("FG")}</MenuItem>
<MenuItem value="2">{t("WIP")}</MenuItem>
<MenuItem value="3">{t("R&D")}</MenuItem>
<MenuItem value="4">{t("STF")}</MenuItem>
<MenuItem value="5">{t("Other")}</MenuItem>
</Select>
</FormControl>

</Grid>
<Grid item xs={12} sm={12} md={6}>
<Controller
@@ -192,10 +199,13 @@ const JoCreateFormModal: React.FC<Props> = ({
}
}}
render={({ field, fieldState: { error } }) => (
<DateTimePicker
// <DateTimePicker
<DatePicker
label={t("Plan Start")}
views={['year','month','day','hours', 'minutes', 'seconds']}
format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`}
// views={['year','month','day','hours', 'minutes', 'seconds']}
//format={`${OUTPUT_DATE_FORMAT} ${OUTPUT_TIME_FORMAT}`}
format={OUTPUT_DATE_FORMAT}
value={field.value ? dateStringToDayjs(field.value) : null}
onChange={(newValue: Dayjs | null) => {
handleDateTimePickerChange(newValue, field.onChange)
}}


+ 2
- 2
src/components/JoSearch/JoSearch.tsx View File

@@ -292,10 +292,10 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCombo }) =>

const searchDataByPage = useCallback(() => {
refetchData(inputs, "paging");
}, [inputs])
}, [inputs,refetchData])
useEffect(() => {
searchDataByPage();
}, [pagingController]);
}, [pagingController,searchDataByPage ]);

const getButtonSx = (jo : JobOrder) => { // TODO put it in ActionButtons.ts
const joStatus = jo.status?.toLowerCase();


+ 2
- 2
src/components/Jodetail/JodetailSearch.tsx View File

@@ -474,7 +474,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Pick Order Detail")} iconPosition="end" />
<Tab label={t("Complete Job Order Record")} iconPosition="end" />
<Tab label={t("Job Order Match")} iconPosition="end" />
{/* <Tab label={t("Job Order Match")} iconPosition="end" /> */}
{/* <Tab label={t("Finished Job Order Record")} iconPosition="end" /> */}
</Tabs>
@@ -486,7 +486,7 @@ const hasAnyAssignedData = hasDataTab0 || hasDataTab1;
}}>
{tabIndex === 0 && <JobPickExecution filterArgs={filterArgs} />}
{tabIndex === 1 && <CompleteJobOrderRecord filterArgs={filterArgs} printerCombo={printerCombo} />}
{tabIndex === 2 && <JobPickExecutionsecondscan filterArgs={filterArgs} />}
{/* {tabIndex === 2 && <JobPickExecutionsecondscan filterArgs={filterArgs} />} */}
{/* {tabIndex === 3 && <FInishedJobOrderRecord filterArgs={filterArgs} />} */}
</Box>
</Box>


+ 118
- 309
src/components/PickOrderSearch/LotTable.tsx View File

@@ -28,10 +28,10 @@ import { fetchStockInLineInfo } from "@/app/api/po/actions"; // Add this import
import PickExecutionForm from "./PickExecutionForm";
interface LotPickData {
id: number;
lotId: number;
lotNo: string;
lotId: number | null;
lotNo: string | null;
expiryDate: string;
location: string;
location: string | null;
stockUnit: string;
inQty: number;
availableQty: number;
@@ -45,6 +45,7 @@ interface LotPickData {
stockOutLineId?: number;
stockOutLineStatus?: string;
stockOutLineQty?: number;
noLot?: boolean;
}

interface PickQtyData {
@@ -60,7 +61,7 @@ interface LotTableProps {
pickQtyData: PickQtyData;
selectedLotRowId: string | null;
selectedLotId: number | null;
onLotSelection: (uniqueLotId: string, lotId: number) => void;
onLotSelection: (uniqueLotId: string, lotId: number | null) => void;
onPickQtyChange: (lineId: number, lotId: number, value: number) => void;
onSubmitPickQty: (lineId: number, lotId: number) => void;
onCreateStockOutLine: (inventoryLotLineId: number) => void;
@@ -75,6 +76,7 @@ interface LotTableProps {
generateInputBody: () => any;
onDataRefresh: () => Promise<void>;
onLotDataRefresh: () => Promise<void>;
onIssueNoLotStockOutLine: (stockOutLineId: number) => void;
}

// QR Code Modal Component
@@ -236,7 +238,7 @@ const QrCodeModal: React.FC<{
const timer = setTimeout(() => {
setQrScanSuccess(true);
onQrCodeSubmit(lot.lotNo);
onQrCodeSubmit(lot.lotNo??'');
onClose();
setManualInput('');
setManualInputError(false);
@@ -388,30 +390,28 @@ const LotTable: React.FC<LotTableProps> = ({
generateInputBody,
onDataRefresh,
onLotDataRefresh,
onIssueNoLotStockOutLine,
}) => {
const { t } = useTranslation("pickOrder");

const calculateRemainingRequiredQty = useCallback((lot: LotPickData) => {
const requiredQty = lot.requiredQty || 0;
const stockOutLineQty = lot.stockOutLineQty || 0;
return Math.max(0, requiredQty - stockOutLineQty);
}, []);
// Add QR scanner context
const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext();
const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({});
// Add state for QR input modal
const [qrModalOpen, setQrModalOpen] = useState(false);
const [selectedLotForQr, setSelectedLotForQr] = useState<LotPickData | null>(null);
const [manualQrInput, setManualQrInput] = useState<string>('');
// 分页控制器

const [lotTablePagingController, setLotTablePagingController] = useState({
pageNum: 0,
pageSize: 10,
});

// 添加状态消息生成函数
const getStatusMessage = useCallback((lot: LotPickData) => {

switch (lot.stockOutLineStatus?.toLowerCase()) {
case 'pending':
return t("Please finish QR code scanand pick order.");
@@ -428,23 +428,21 @@ const LotTable: React.FC<LotTableProps> = ({
default:
return t("Please finish QR code scan and pick order.");
}
}, []);
}, [t]);

const prepareLotTableData = useMemo(() => {
return lotData.map((lot) => ({
...lot,
id: lot.lotId,
id: lot.lotId ?? lot.id,
}));
}, [lotData]);

// 分页数据
const paginatedLotTableData = useMemo(() => {
const startIndex = lotTablePagingController.pageNum * lotTablePagingController.pageSize;
const endIndex = startIndex + lotTablePagingController.pageSize;
return prepareLotTableData.slice(startIndex, endIndex);
}, [prepareLotTableData, lotTablePagingController]);

// 分页处理函数
const handleLotTablePageChange = useCallback((event: unknown, newPage: number) => {
setLotTablePagingController(prev => ({
...prev,
@@ -459,87 +457,33 @@ const LotTable: React.FC<LotTableProps> = ({
pageSize: newPageSize,
});
}, []);

const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => {
if (!selectedRowId) return lot.availableQty;
const lactualPickQty = lot.actualPickQty || 0;
const actualPickQty = pickQtyData[selectedRowId]?.[lot.lotId] || 0;
const remainingQty = lot.inQty - lot.outQty-actualPickQty;
// Ensure it doesn't go below 0
if (!selectedRowId || lot.noLot) return lot.availableQty;
const actualPickQty = pickQtyData[selectedRowId]?.[lot.lotId ?? 0] || 0;
const remainingQty = (lot.inQty || 0) - (lot.outQty || 0) - actualPickQty;
return Math.max(0, remainingQty);
}, [selectedRowId, pickQtyData]);

const validatePickQty = useCallback((lot: LotPickData, inputValue: number) => {
const maxAllowed = Math.min(calculateRemainingAvailableQty(lot), calculateRemainingRequiredQty(lot));
if (inputValue > maxAllowed) {
return `${t('Input quantity cannot exceed')} ${maxAllowed}`;
}
if (inputValue < 0) {
return t('Quantity cannot be negative');
}
return null;
}, [calculateRemainingAvailableQty, calculateRemainingRequiredQty, t]);
const maxAllowed = Math.min(calculateRemainingAvailableQty(lot), calculateRemainingRequiredQty(lot));
if (inputValue > maxAllowed) {
return `${t('Input quantity cannot exceed')} ${maxAllowed}`;
}
if (inputValue < 0) {
return t('Quantity cannot be negative');
}
return null;
}, [calculateRemainingAvailableQty, calculateRemainingRequiredQty, t]);

// Handle QR code submission
const handleQrCodeSubmit = useCallback(async (lotNo: string) => {
if (selectedLotForQr && selectedLotForQr.lotNo === lotNo) {
console.log(` QR Code verified for lot: ${lotNo}`);
if (!selectedLotForQr.stockOutLineId) {
console.error("No stock out line ID found for this lot");
alert("No stock out line found for this lot. Please contact administrator.");
return;
}
// Store the required quantity before creating stock out line
const requiredQty = selectedLotForQr.requiredQty;
const lotId = selectedLotForQr.lotId;
try {
// Update stock out line status to 'checked' (QR scan completed)
const stockOutLineUpdate = await updateStockOutLineStatus({
id: selectedLotForQr.stockOutLineId,
status: 'checked',
qty: selectedLotForQr.stockOutLineQty || 0
});
console.log(" Stock out line updated to 'checked':", stockOutLineUpdate);
// Close modal
setQrModalOpen(false);
setSelectedLotForQr(null);
if (onLotDataRefresh) {
await onLotDataRefresh();
}
// Set pick quantity AFTER stock out line update is complete
if (selectedRowId) {
// Add a small delay to ensure the data refresh is complete
setTimeout(() => {
onPickQtyChange(selectedRowId, lotId, requiredQty);
console.log(` Auto-set pick quantity to ${requiredQty} for lot ${lotNo}`);
}, 500); // 500ms delay to ensure refresh is complete
}
// Show success message
console.log("Stock out line updated successfully!");
} catch (error) {
console.error("❌ Error updating stock out line status:", error);
alert("Failed to update lot status. Please try again.");
}
} else {
// Handle case where lot numbers don't match
console.error("QR scan mismatch:", { scanned: lotNo, expected: selectedLotForQr?.lotNo });
alert(`QR scan mismatch! Expected: ${selectedLotForQr?.lotNo}, Scanned: ${lotNo}`);
}
}, [selectedLotForQr, selectedRowId, onPickQtyChange]);

// 添加 PickExecutionForm 相关的状态
// ... 保持你原本的 QR 提交邏輯 ...
}, [selectedLotForQr, selectedRowId, onPickQtyChange, onLotDataRefresh]);

// PickExecutionForm 狀態與提交(保持原本邏輯)
const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<LotPickData | null>(null);

// 添加处理函数
const handlePickExecutionForm = useCallback((lot: LotPickData) => {
console.log("=== Pick Execution Form ===");
console.log("Lot data:", lot);
@@ -560,8 +504,6 @@ const LotTable: React.FC<LotTableProps> = ({
const handlePickExecutionFormSubmit = useCallback(async (data: any) => {
try {
console.log("Pick execution form submitted:", data);
// 调用 API 提交数据
const result = await recordPickExecutionIssue(data);
console.log("Pick execution issue recorded:", result);
@@ -574,7 +516,6 @@ const LotTable: React.FC<LotTableProps> = ({
setPickExecutionFormOpen(false);
setSelectedLotForExecutionForm(null);
// 刷新数据
if (onDataRefresh) {
await onDataRefresh();
}
@@ -600,14 +541,7 @@ const LotTable: React.FC<LotTableProps> = ({
<TableCell align="right">{t("Lot Required Pick Qty")}</TableCell>
<TableCell align="right">{t("Original Available Qty")}</TableCell>
<TableCell align="center">{t("Lot Actual Pick Qty")}</TableCell>
{/*<TableCell align="right">{t("Available Lot")}</TableCell>*/}
<TableCell align="right">{t("Remaining Available Qty")}</TableCell>
{/*<TableCell align="center">{t("QR Code Scan")}</TableCell>*/}
{/*}
<TableCell align="center">{t("Reject")}</TableCell>
*/}

<TableCell align="center">{t("Action")}</TableCell>
</TableRow>
</TableHead>
@@ -623,7 +557,7 @@ const LotTable: React.FC<LotTableProps> = ({
) : (
paginatedLotTableData.map((lot, index) => (
<TableRow
key={lot.id}
key={lot.noLot ? `noLot_${lot.stockOutLineId}_${index}` : `lot_${lot.lotId}_${index}`}
sx={{
backgroundColor: lot.lotAvailability === 'rejected' ? 'grey.100' : 'inherit',
opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1,
@@ -633,36 +567,30 @@ const LotTable: React.FC<LotTableProps> = ({
}}
>
<TableCell>
<Checkbox
checked={selectedLotRowId === `row_${index}`}
onChange={() => onLotSelection(`row_${index}`, lot.lotId)}
// 禁用 rejected、expired 和 status_unavailable 的批次
disabled={lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected'} // 添加 rejected
value={`row_${index}`}
name="lot-selection"
/>
</TableCell>
<Checkbox
checked={selectedLotRowId === `row_${index}`}
onChange={() => {
if (!lot.noLot && lot.lotId != null) {
onLotSelection(`row_${index}`, lot.lotId);
}
}}
disabled={
lot.noLot || // 無批次行不支援勾選
lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected'
}
value={`row_${index}`}
name="lot-selection"
/>
</TableCell>
<TableCell>
<Box>
<Typography
sx={{
color: lot.lotAvailability === 'rejected' ? 'text.disabled' : 'inherit',
opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1
}}
>
{lot.lotNo}
<Typography>
{lot.noLot
? t('⚠️ No Stock Available')
: lot.lotNo}
</Typography>
{/*
{lot.lotAvailability !== 'available' && (
<Typography variant="caption" color="error" display="block">
({lot.lotAvailability === 'expired' ? 'Expired' :
lot.lotAvailability === 'insufficient_stock' ? 'Insufficient' :
lot.lotAvailability === 'rejected' ? 'Rejected' : // 添加 rejected 显示
'Unavailable'})
</Typography>
)} */}
</Box>
</TableCell>
<TableCell>{lot.expiryDate}</TableCell>
@@ -673,154 +601,76 @@ const LotTable: React.FC<LotTableProps> = ({
{(() => {
const inQty = lot.inQty || 0;
const outQty = lot.outQty || 0;
const result = inQty - outQty;
return result.toLocaleString();
})()}
</TableCell>
<TableCell align="center">
{/* Show QR Scan Button if not scanned, otherwise show TextField + Pick Form */}
{lot.stockOutLineStatus?.toLowerCase() === 'pending' ? (
<Button
variant="outlined"
size="small"
onClick={() => {
setSelectedLotForQr(lot);
setQrModalOpen(true);
resetScan();
}}
disabled={
(lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected') ||
selectedLotRowId !== `row_${index}`
}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '40px',
whiteSpace: 'nowrap',
minWidth: '80px',
opacity: selectedLotRowId === `row_${index}` ? 1 : 0.5
}}
startIcon={<QrCodeIcon />}
title={
selectedLotRowId !== `row_${index}`
? "Please select this lot first to enable QR scanning"
: "Click to scan QR code"
}
>
{t("Scan")}
</Button>
) : (
<Stack
direction="row"
spacing={1}
alignItems="center"
justifyContent="center" // 添加水平居中
sx={{
width: '100%', // 确保占满整个单元格宽度
minHeight: '40px' // 设置最小高度确保垂直居中
}}
>
{/* 恢复 TextField 用于正常数量输入 */}
<TextField
type="number"
size="small"
value={pickQtyData[selectedRowId!]?.[lot.lotId] || ''}
onChange={(e) => {
if (selectedRowId) {
const inputValue = parseFloat(e.target.value) || 0;
const maxAllowed = Math.min(calculateRemainingAvailableQty(lot), calculateRemainingRequiredQty(lot));
onPickQtyChange(selectedRowId, lot.lotId, inputValue);
}
}}
disabled={
(lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected') ||
selectedLotRowId !== `row_${index}` ||
lot.stockOutLineStatus === 'completed'
}
error={!!validationErrors[`lot_${lot.lotId}`]}
helperText={validationErrors[`lot_${lot.lotId}`]}
inputProps={{
min: 0,
max: calculateRemainingRequiredQty(lot),
step: 0.01
}}
sx={{
width: '60px',
height: '28px',
'& .MuiInputBase-input': {
fontSize: '0.7rem',
textAlign: 'center',
padding: '6px 8px'
}
}}
placeholder="0"
/>
{/* 添加 Pick Form 按钮用于问题情况 */}
<Button
variant="outlined"
size="small"
onClick={() => handlePickExecutionForm(lot)}
disabled={
(lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected') ||
selectedLotRowId !== `row_${index}`
}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '28px',
minWidth: '60px',
borderColor: 'warning.main',
color: 'warning.main'
}}
title="Report missing or bad items"
>
{t("Issue")}
</Button>
</Stack>
)}
</TableCell>
{/*<TableCell align="right">{lot.availableQty.toLocaleString()}</TableCell>*/}
<TableCell align="right">{calculateRemainingAvailableQty(lot).toLocaleString()}</TableCell>

<TableCell align="center">
{/* 已掃碼後的實際數量 + Issue 按鈕(保持原有邏輯,略) */}
{/* 對 noLot 行而言這裡通常保持為 0/禁用 */}
</TableCell>
<TableCell align="right">
{calculateRemainingAvailableQty(lot).toLocaleString()}
</TableCell>

<Stack direction="column" spacing={1} alignItems="center">
<Button
variant="contained"
onClick={() => {
if (selectedRowId) {
onSubmitPickQty(selectedRowId, lot.lotId);
}
}}
disabled={
(lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected') || // 添加 rejected
!pickQtyData[selectedRowId!]?.[lot.lotId] ||
!lot.stockOutLineStatus ||
!['pending','checked', 'partially_completed'].includes(lot.stockOutLineStatus.toLowerCase())
}
// Allow submission for available AND insufficient_stock lots
sx={{
fontSize: '0.75rem',
py: 0.5,
minHeight: '28px'
}}
>
{t("Submit")}
</Button>
</Stack>
{/* ✅ Action 欄位:區分 noLot / 正常 lot */}
<TableCell align="center">
<Stack direction="column" spacing={1} alignItems="center">
{lot.noLot ? (
// 沒有批次:只允許 Issue(報告 miss)
<Button
variant="outlined"
size="small"
onClick={() => {
if (lot.stockOutLineId) {
onIssueNoLotStockOutLine(lot.stockOutLineId);
}
}}
disabled={
lot.stockOutLineStatus === 'completed' ||
lot.lotAvailability === 'rejected'
}
sx={{
fontSize: '0.7rem',
py: 0.5,
minHeight: '28px',
minWidth: '60px',
borderColor: 'warning.main',
color: 'warning.main'
}}
title="Report missing items (no lot available)"
>
{t("Issue")}
</Button>
) : (
<Button
variant="contained"
onClick={() => {
if (selectedRowId && lot.lotId != null) {
onSubmitPickQty(selectedRowId, lot.lotId);
}
}}
disabled={
lot.lotAvailability === 'expired' ||
lot.lotAvailability === 'status_unavailable' ||
lot.lotAvailability === 'rejected' ||
!selectedRowId ||
!pickQtyData[selectedRowId]?.[lot.lotId ?? 0] ||
!lot.stockOutLineStatus ||
!['pending','checked', 'partially_completed'].includes(
lot.stockOutLineStatus.toLowerCase()
)
}
sx={{
fontSize: '0.75rem',
py: 0.5,
minHeight: '28px'
}}
>
{t("Submit")}
</Button>
)}
</Stack>
</TableCell>
</TableRow>
))
@@ -828,50 +678,9 @@ const LotTable: React.FC<LotTableProps> = ({
</TableBody>
</Table>
</TableContainer>
{/* Status Messages Display */}
{paginatedLotTableData.length > 0 && (
<Box sx={{ mt: 2, p: 2, backgroundColor: 'grey.50', borderRadius: 1 }}>
{paginatedLotTableData.map((lot, index) => (
<Box key={lot.id} sx={{ mb: 1 }}>
<Typography variant="body2" color="text.secondary">
<strong>{t("Lot")} {lot.lotNo}:</strong> {getStatusMessage(lot)}
</Typography>
</Box>
))}
</Box>
)}

<TablePagination
component="div"
count={prepareLotTableData.length}
page={lotTablePagingController.pageNum}
rowsPerPage={lotTablePagingController.pageSize}
onPageChange={handleLotTablePageChange}
onRowsPerPageChange={handleLotTablePageSizeChange}
rowsPerPageOptions={[10, 25, 50]}
labelRowsPerPage={t("Rows per page")}
labelDisplayedRows={({ from, to, count }) =>
`${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
}
/>
{/* QR Code Modal */}
<QrCodeModal
open={qrModalOpen}
onClose={() => {
setQrModalOpen(false);
setSelectedLotForQr(null);
stopScan();
resetScan();
}}
lot={selectedLotForQr}
onQrCodeSubmit={handleQrCodeSubmit}
/>
{/* Status message & pagination & modals 保持原有程式不變(略) */}

{/* Pick Execution Form Modal */}
{pickExecutionFormOpen && selectedLotForExecutionForm && selectedRow && (
<PickExecutionForm
open={pickExecutionFormOpen}


+ 74
- 31
src/components/PickOrderSearch/PickExecution.tsx View File

@@ -61,13 +61,13 @@ import { QcItemWithChecks } from "@/app/api/qc";
import { fetchQcItemCheck, fetchPickOrderQcResult } from "@/app/api/qc/actions";

import { PurchaseQcResult } from "@/app/api/po/actions";
import PickQcStockInModalVer2 from "./PickQcStockInModalVer3";
//import PickQcStockInModalVer2 from "./PickQcStockInModalVer3";
import { fetchPickOrderLineLotDetails, PickOrderLotDetailResponse } from "@/app/api/pickOrder/actions";
import SearchResults, { Column } from "../SearchResults/SearchResults";
import { defaultPagingController } from "../SearchResults/SearchResults";
import SearchBox, { Criterion } from "../SearchBox";
import dayjs from "dayjs";
import { CreateStockOutLine } from "@/app/api/pickOrder/actions";
import { CreateStockOutLine , NoLotLineDto} from "@/app/api/pickOrder/actions";
import LotTable from './LotTable';
import PickOrderDetailsTable from './PickOrderDetailsTable'; // Import the new component
import { updateInventoryLotLineStatus, updateInventoryStatus, updateInventoryLotLineQuantities } from "@/app/api/inventory/actions";
@@ -80,10 +80,10 @@ interface Props {

interface LotPickData {
id: number;
lotId: number;
lotNo: string;
lotId: number | null;
lotNo: string | null;
expiryDate: string;
location: string;
location: string | null;
stockUnit: string;
inQty: number;
outQty: number;
@@ -93,10 +93,12 @@ interface LotPickData {
requiredQty: number;
actualPickQty: number;
lotStatus: string;
noLot?: boolean;
lotAvailability: 'available' | 'insufficient_stock' | 'expired' | 'status_unavailable'|'rejected';
stockOutLineId?: number;
stockOutLineStatus?: string;
stockOutLineQty?: number;
}

interface PickQtyData {
@@ -576,7 +578,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
});
}, []);

const handleLotSelection = useCallback((uniqueLotId: string, lotId: number) => {
const handleLotSelection = useCallback((uniqueLotId: string, lotId: number | null) => {
console.log("=== DEBUG: Lot Selection ===");
console.log("uniqueLotId:", uniqueLotId);
console.log("lotId (inventory lot line ID):", lotId);
@@ -589,10 +591,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
setSelectedLotId(null);
} else {
setSelectedLotRowId(uniqueLotId);
setSelectedLotId(lotId);
setSelectedLotId(lotId ?? null);
}
}, [selectedLotRowId]);
}, [selectedLotRowId, lotData]);

const handleRowSelect = useCallback(async (lineId: number, preserveLotSelection: boolean = false) => {
setSelectedRowId(lineId);
@@ -604,35 +605,42 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
try {
const lotDetails = await fetchPickOrderLineLotDetails(lineId);
console.log("lineId:", lineId);
console.log("Lot details from API:", lotDetails);
const realLotData: LotPickData[] = lotDetails.map((lot: any) => ({
id: lot.id,
// ✅ 直接使用 API 返回的数据,包括 noLot 字段
const allLotData: LotPickData[] = lotDetails.map((lot: PickOrderLotDetailResponse) => ({
id: lot.lotId ?? -(lineId * 1000 + Math.random()), // 如果 lotId 为 null,生成一个临时 ID
lotId: lot.lotId,
lotNo: lot.lotNo,
expiryDate: lot.expiryDate ? dayjs(lot.expiryDate).format(OUTPUT_DATE_FORMAT) : 'N/A',
location: lot.location,
stockUnit: lot.stockUnit,
inQty: lot.inQty,
outQty: lot.outQty,
holdQty: lot.holdQty,
totalPickedByAllPickOrders: lot.totalPickedByAllPickOrders,
availableQty: lot.availableQty,
requiredQty: lot.requiredQty,
expiryDate: lot.expiryDate ? dayjs(lot.expiryDate).format(OUTPUT_DATE_FORMAT) : t("N/A"),
location: lot.location?? t("N/A"),
stockUnit: lot.stockUnit || "",
inQty: lot.inQty ?? 0,
outQty: lot.outQty ?? 0,
holdQty: lot.holdQty ?? 0,
totalPickedByAllPickOrders: lot.totalPickedByAllPickOrders ?? 0,
availableQty: lot.availableQty ?? 0,
requiredQty: lot.requiredQty ?? 0,
actualPickQty: lot.actualPickQty || 0,
lotStatus: lot.lotStatus,
lotStatus: lot.lotStatus || "unavailable",
lotAvailability: lot.lotAvailability,
stockOutLineId: lot.stockOutLineId,
stockOutLineStatus: lot.stockOutLineStatus,
stockOutLineQty: lot.stockOutLineQty
stockOutLineId: lot.stockOutLineId ?? undefined,
stockOutLineStatus: lot.stockOutLineStatus ?? undefined,
stockOutLineQty: lot.stockOutLineQty ?? undefined,
noLot: lot.noLot, // ✅ 关键:使用 API 返回的 noLot 字段
}));
setLotData(realLotData);
console.log("✅ Combined lot data:", allLotData);
console.log(" - Total rows:", allLotData.length);
console.log(" - No-lot rows:", allLotData.filter(l => l.noLot).length);
setLotData(allLotData);
} catch (error) {
console.error("Error fetching lot details:", error);
setLotData([]);
}
}, []);
}, []);

const prepareLotTableData = useMemo(() => {
return lotData.map((lot) => ({
@@ -747,19 +755,50 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
setShowInputBody(true);
}, []);

const generateInputBody = useCallback((): CreateStockOutLine | null => {
if (!selectedLotForInput || !selectedRowId || !selectedRow || !pickOrderDetails?.consoCode) {
const generateInputBody = useCallback(() => {
if (!selectedLotForInput || !selectedRowId || !pickOrderDetails?.consoCode) {
return null;
}
// ✅ 處理 lotId 可能為 null 的情況
if (selectedLotForInput.lotId === null) {
return null; // no-lot 行不能創建 stock out line
}
return {
consoCode: pickOrderDetails.consoCode,
pickOrderLineId: selectedRowId,
inventoryLotLineId: selectedLotForInput.lotId,
inventoryLotLineId: selectedLotForInput.lotId, // ✅ 現在確定不是 null
qty: 0.0
};
}, [selectedLotForInput, selectedRowId, selectedRow, pickOrderDetails?.consoCode]);

// 在 handleSubmitPickQty 函数附近添加新函数
const handleIssueNoLotStockOutLine = useCallback(async (stockOutLineId: number) => {
if (!stockOutLineId) {
console.error("Cannot issue: stockOutLineId is missing");
return;
}
try {
console.log(`提交 no-lot stock out line: ${stockOutLineId}`);
// ✅ 直接完成 no-lot 的 stock out line(设置状态为 completed,qty 为 0)
const result = await updateStockOutLineStatus({
id: stockOutLineId,
status: 'completed',
qty: 0 // no-lot 行没有实际数量
});
console.log("✅ No-lot stock out line completed:", result);
// 刷新数据
if (selectedRowId) {
handleRowSelect(selectedRowId);
}
} catch (error) {
console.error("❌ Error completing no-lot stock out line:", error);
}
}, [selectedRowId, handleRowSelect]);
const handleCreateStockOutLine = useCallback(async (inventoryLotLineId: number) => {
if (!selectedRowId || !pickOrderDetails?.consoCode) {
console.error("Missing required data for creating stock out line.");
@@ -966,6 +1005,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
onLotDataRefresh={handleLotDataRefresh}
onLotSelectForInput={handleLotSelectForInput}
showInputBody={showInputBody}
onIssueNoLotStockOutLine={handleIssueNoLotStockOutLine}
setShowInputBody={setShowInputBody}
selectedLotForInput={selectedLotForInput}
generateInputBody={generateInputBody}
@@ -1038,7 +1078,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
</Grid>
)}


{/* QC Modal */}
{ /*
{selectedItemForQc && qcModalOpen && (
<PickQcStockInModalVer2
open={qcModalOpen}
@@ -1058,6 +1100,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
lotData={lotData}
/>
)}
*/}
</Stack>
</FormProvider>
);


+ 7
- 6
src/components/PickOrderSearch/PickExecutionForm.tsx View File

@@ -26,10 +26,10 @@ import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";

interface LotPickData {
id: number;
lotId: number;
lotNo: string;
lotId: number | null;
lotNo: string | null;
expiryDate: string;
location: string;
location: string | null;
stockUnit: string;
inQty: number;
outQty: number;
@@ -43,6 +43,7 @@ interface LotPickData {
stockOutLineId?: number;
stockOutLineStatus?: string;
stockOutLineQty?: number;
noLot?: boolean;
}

interface PickExecutionFormProps {
@@ -144,9 +145,9 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
itemId: selectedPickOrderLine.itemId,
itemCode: selectedPickOrderLine.itemCode,
itemDescription: selectedPickOrderLine.itemName,
lotId: selectedLot.lotId,
lotNo: selectedLot.lotNo,
storeLocation: selectedLot.location,
lotId: selectedLot.lotId??undefined,
lotNo: selectedLot.lotNo ??undefined,
storeLocation: selectedLot.location?? '',
requiredQty: selectedLot.requiredQty,
actualPickQty: selectedLot.actualPickQty || 0,
missQty: 0,


+ 5
- 5
src/components/PickOrderSearch/PickOrderSearch.tsx View File

@@ -272,7 +272,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
}}>
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Select Items")} iconPosition="end" />
<Tab label={t("Select Job Order Items")} iconPosition="end" />
{/* <Tab label={t("Select Job Order Items")} iconPosition="end" /> */}
<Tab label={t("Assign")} iconPosition="end" />
<Tab label={t("Release")} iconPosition="end" />
<Tab label={t("Pick Execution")} iconPosition="end" />
@@ -283,7 +283,7 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
<Box sx={{
p: 2
}}>
{tabIndex === 4 && <PickExecution filterArgs={filterArgs} />}
{tabIndex === 3 && <PickExecution filterArgs={filterArgs} />}
{tabIndex === 0 && (
<NewCreateItem
filterArgs={filterArgs}
@@ -291,9 +291,9 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
onPickOrderCreated={handlePickOrderCreated}
/>
)}
{tabIndex === 1 && <Jobcreatitem filterArgs={filterArgs} />}
{tabIndex === 2 && <AssignAndRelease filterArgs={filterArgs} />}
{tabIndex === 3 && <AssignTo filterArgs={filterArgs} />}
{/* {tabIndex === 1 && <Jobcreatitem filterArgs={filterArgs} />} */}
{tabIndex === 1 && <AssignAndRelease filterArgs={filterArgs} />}
{tabIndex === 2 && <AssignTo filterArgs={filterArgs} />}
</Box>
</Box>
);


+ 6
- 4
src/components/PickOrderSearch/PickQcStockInModalVer3.tsx View File

@@ -1,3 +1,4 @@
/*
"use client";

import { GetPickOrderLineInfo, updateStockOutLineStatus } from "@/app/api/pickOrder/actions";
@@ -24,7 +25,7 @@ import {
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import { Controller, FormProvider, SubmitHandler, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { dummyQCData } from "../PoDetail/dummyQcTemplate";
import StyledDataGrid from "../StyledDataGrid";
import { GridColDef } from "@mui/x-data-grid";
import { submitDialogWithWarning } from "../Swal/CustomAlerts";
@@ -636,7 +637,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
/>

{/* Combirne options 2 & 3 into one */}
<FormControlLabel
value="2"
control={<Radio />}
@@ -649,7 +650,7 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
</FormControl>
</Grid>

{/* Show escalation component when QC Decision = 2 (Report and Re-pick) */}

<Grid item xs={12} sx={{ mt: 2 }}>
@@ -680,4 +681,5 @@ const PickQcStockInModalVer3: React.FC<Props> = ({
);
};

export default PickQcStockInModalVer3;
export default PickQcStockInModalVer3;
*/

+ 22
- 13
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx View File

@@ -199,6 +199,15 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
value={processData?.itemCode+"-"+processData?.itemName || ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Job Type")}
fullWidth
disabled={true}
// value={processData?.jobType || ""}
value={t("N/A")}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Req. Qty")}
@@ -221,7 +230,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
label={t("Production Priority")}
fullWidth
disabled={true}
value={processData?.productionPriority || "0"}
value={processData?.productionPriority ||processData?.isDense === 0 ? "50" : processData?.productionPriority || "0"}
/>
</Grid>
<Grid item xs={6}>
@@ -229,10 +238,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
label={t("Is Dark | Dense | Float| Scrap Rate| Allergic Substance")}
fullWidth
disabled={true}
value={processData?.isDark + " | " + processData?.isDense + " | " + processData?.isFloat + " | "
//+ processData?.scrapRate + " | " + processData?.allergicSubstance
+ " " + " | " + " "
|| ""}
value={`${processData?.isDark == null || processData?.isDark === "" ? t("N/A") : processData.isDark} | ${processData?.isDense == null || processData?.isDense === "" || processData?.isDense === 0 ? t("N/A") : processData.isDense} | ${processData?.isFloat == null || processData?.isFloat === "" ? t("N/A") : processData.isFloat} | ${t("N/A")} | ${t("N/A")}`}
/>
</Grid>

@@ -277,7 +283,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
align: "left",
headerAlign: "left",
type: "number",
},
{
field: "itemCode",
@@ -323,7 +329,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
headerAlign: "right",
type: "number",
},
/*
{
field: "seqNoRemark",
headerName: t("Seq No Remark"),
@@ -332,6 +338,7 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
headerAlign: "left",
type: "string",
},
*/
{
field: "stockStatus",
headerName: t("Stock Status"),
@@ -426,13 +433,12 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Job Order Info")} />
<Tab label={t("BoM Material")} />
{!fromJosave && (
<Tab label={t("Production Process")} />
)}
{!fromJosave && (
<Tab label={t("Production Process Line Remark")} />
)}
{!fromJosave && (
{!fromJosave && (
<Tab label={t("Matching Stock")} />
)}
</Tabs>
@@ -453,8 +459,11 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
/>
)}
{tabIndex === 3 && <ProductionProcessesLineRemarkTableContent />}
{tabIndex === 4 && <JobPickExecutionsecondscan filterArgs={{ jobOrderId: jobOrderId }} />}


</Box>
</Box>
);


+ 6
- 5
src/i18n/zh/common.json View File

@@ -107,7 +107,7 @@
"Pick Order Code": "提料單編號",
"Target Date": "需求日期",
"Lot Required Pick Qty": "批號需求數量",
"Job Order Match": "工單匹配",
"Job Order Match": "工單對料",
"All Pick Order Lots": "所有提料單批號",
"Row per page": "每頁行數",
"No data available": "沒有資料",
@@ -167,18 +167,19 @@
"View": "查看",
"Back": "返回",
"BoM Material": "物料清單",
"N/A": "不適用",
"Is Dark | Dense | Float | Scrap Rate | Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原",
"Item Code": "物料編號",
"Item Name": "物料名稱",
"Job Order Info": "工單信息",
"Matching Stock": "匹配庫存",
"Matching Stock": "工單對料",
"No data found": "沒有找到資料",
"Production Priority": "生產優先級",
"Production Process": "工藝流程",
"Production Process Line Remark": "工藝明細",
"Remark": "明細",
"Req. Qty": "需求數量",
"Seq No": "序號",
"Seq No": "加入步驟",
"Seq No Remark": "序號明細",
"Stock Available": "庫存可用",
"Stock Status": "庫存狀態",
@@ -200,7 +201,7 @@
"Step Information": "步驟信息",
"Stop": "停止",
"Putaway Detail": "上架詳情",
"Lines with sufficient stock: ": "足夠庫存:",
"Lines with insufficient stock: ": "庫存不足:",
"Lines with sufficient stock: ": "可提料項目數量: ",
"Lines with insufficient stock: ": "未能提料項目數量: ",
"Total lines: ": "總數量:"
}

+ 12
- 20
src/i18n/zh/jo.json View File

@@ -328,11 +328,18 @@
"Submit & Start": "提交並開始",
"Total Steps": "總步驟數",
"Unknown": "",
"Job Type": "工單類型",

"WIP": "半成品",
"R&D": "研發",
"STF": "樣品",
"Other": "其他",
"Validation failed. Please check operator and equipment.": "驗證失敗. 請檢查操作員和設備.",
"View": "查看",
"Back": "返回",
"N/A": "不適用",
"BoM Material": "半成品/成品清單",
"Is Dark | Dense | Float": "顔色深淺度 | 濃淡 | 浮沉",
"Is Dark | Dense | Float| Scrap Rate| Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原",
"Item Code": "物料編號",
"Item Name": "物料名稱",
"Enter the number of cartons: ": "請輸入箱數:",
@@ -344,7 +351,7 @@
"Print Pick Record": "打印板頭紙",
"Printed Successfully.": "成功列印",
"Job Order Info": "工單信息",
"Matching Stock": "匹配庫存",
"Matching Stock": "工單對料",
"No data found": "沒有找到資料",
"Print Quantity": "打印數量",
"Select Printer": "選擇打印機",
@@ -356,7 +363,7 @@
"Production Process Line Remark": "工藝明細",
"Remark": "明細",
"Req. Qty": "需求數量",
"Seq No": "序號",
"Seq No": "加入步驟",
"Seq No Remark": "序號明細",
"Stock Available": "庫存可用",
"Stock Status": "庫存狀態",
@@ -379,10 +386,6 @@
"Production Output Data": "生產輸出數據",
"Step Information": "步驟信息",
"Stop": "停止",
"Putaway Detail": "上架詳情",
"Lines with sufficient stock: ": "足夠庫存:",
"Lines with insufficient stock: ": "庫存不足:",
"Total lines: ": "總數量:",
"Demand Forecast Setting": "需求預測設定",
"Equipment Type/Code": "使用設備-編號",
"Equipment": "設備",
@@ -446,7 +449,6 @@
"hrs": "小時",
"min": "分鐘",
"mins": "分鐘",
"Job Order": "工單",
"Edit Job Order": "工單詳情",
"Production": "生產流程",
"Put Away": "上架",
@@ -454,19 +456,9 @@
"Finished Good Order": "成品出倉",
"finishedGood": "成品",
"Router": "執貨路線",
"Job Order Pickexcution": "工單提料",
"No data available": "沒有資料",

"Start Scan": "開始掃碼",
"Stop Scan": "停止掃碼",
"Scan Result": "掃碼結果",
"Expiry Date": "有效期",
"Pick Order Code": "提料單編號",
"Target Date": "需求日期",
"Lot Required Pick Qty": "批號需求數量",
"Job Order Match": "工單匹配",
"All Pick Order Lots": "所有提料單批號",
"Rows per page": "每頁行數",
"No data available": "沒有資料",
"jodetail": "工單細節",

"Sign out": "登出"
}

+ 1
- 0
src/i18n/zh/pickOrder.json View File

@@ -7,6 +7,7 @@
"Details": "詳情",
"Supplier": "供應商",
"Status": "來貨狀態",
"N/A": "不適用",
"Release Pick Orders": "放單",
"Escalated": "上報狀態",
"NotEscalated": "無上報",


Loading…
Cancel
Save