瀏覽代碼

update

master
CANCERYS\kw093 2 週之前
父節點
當前提交
cdc7770db0
共有 15 個檔案被更改,包括 310 行新增424 行删除
  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 查看文件

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


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


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


+ 26
- 11
src/app/api/pickOrder/actions.ts 查看文件

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


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

export interface CurrentInventoryItemInfo { export interface CurrentInventoryItemInfo {
id: number; id: number;
code: string; code: string;
@@ -743,18 +750,26 @@ export const fetchPickOrderDetails = cache(async (ids: string) => {
); );
}); });
export interface PickOrderLotDetailResponse { 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; requiredQty: number;
actualPickQty: 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 { interface ALLPickOrderLotDetailResponse {
// Pick Order Information // Pick Order Information


+ 1
- 1
src/components/InputDataGrid/InputDataGrid.tsx 查看文件

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


+ 25
- 15
src/components/JoSearch/JoCreateFormModal.tsx 查看文件

@@ -3,7 +3,7 @@ import { JoDetail } from "@/app/api/jo";
import { SaveJo, manualCreateJo } from "@/app/api/jo/actions"; import { SaveJo, manualCreateJo } from "@/app/api/jo/actions";
import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dateStringToDayjs, dayjsToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil"; import { OUTPUT_DATE_FORMAT, OUTPUT_TIME_FORMAT, dateStringToDayjs, dayjsToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil";
import { Check } from "@mui/icons-material"; 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 { DatePicker, DateTimePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs, { Dayjs } from "dayjs"; import dayjs, { Dayjs } from "dayjs";
@@ -52,6 +52,10 @@ const JoCreateFormModal: React.FC<Props> = ({


const onSubmit = useCallback<SubmitHandler<SaveJo>>(async (data) => { const onSubmit = useCallback<SubmitHandler<SaveJo>>(async (data) => {
data.type = "manual" data.type = "manual"
if (data.planStart) {
const dateDayjs = dateStringToDayjs(data.planStart)
data.planStart = dayjsToDateTimeString(dateDayjs.startOf('day'))
}
const response = await manualCreateJo(data) const response = await manualCreateJo(data)
if (response) { if (response) {
onSearch(); onSearch();
@@ -164,17 +168,20 @@ const JoCreateFormModal: React.FC<Props> = ({
/> />
</Grid> </Grid>
<Grid item xs={12} sm={12} md={6}> <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>
<Grid item xs={12} sm={12} md={6}> <Grid item xs={12} sm={12} md={6}>
<Controller <Controller
@@ -192,10 +199,13 @@ const JoCreateFormModal: React.FC<Props> = ({
} }
}} }}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<DateTimePicker
// <DateTimePicker
<DatePicker
label={t("Plan Start")} 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) => { onChange={(newValue: Dayjs | null) => {
handleDateTimePickerChange(newValue, field.onChange) handleDateTimePickerChange(newValue, field.onChange)
}} }}


+ 2
- 2
src/components/JoSearch/JoSearch.tsx 查看文件

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


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


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


+ 2
- 2
src/components/Jodetail/JodetailSearch.tsx 查看文件

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


+ 118
- 309
src/components/PickOrderSearch/LotTable.tsx 查看文件

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


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


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

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

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


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

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


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


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


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

const calculateRemainingAvailableQty = useCallback((lot: LotPickData) => { 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); return Math.max(0, remainingQty);
}, [selectedRowId, pickQtyData]); }, [selectedRowId, pickQtyData]);

const validatePickQty = useCallback((lot: LotPickData, inputValue: number) => { 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) => { 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 [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false);
const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<LotPickData | null>(null); const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<LotPickData | null>(null);


// 添加处理函数
const handlePickExecutionForm = useCallback((lot: LotPickData) => { const handlePickExecutionForm = useCallback((lot: LotPickData) => {
console.log("=== Pick Execution Form ==="); console.log("=== Pick Execution Form ===");
console.log("Lot data:", lot); console.log("Lot data:", lot);
@@ -560,8 +504,6 @@ const LotTable: React.FC<LotTableProps> = ({
const handlePickExecutionFormSubmit = useCallback(async (data: any) => { const handlePickExecutionFormSubmit = useCallback(async (data: any) => {
try { try {
console.log("Pick execution form submitted:", data); console.log("Pick execution form submitted:", data);
// 调用 API 提交数据
const result = await recordPickExecutionIssue(data); const result = await recordPickExecutionIssue(data);
console.log("Pick execution issue recorded:", result); console.log("Pick execution issue recorded:", result);
@@ -574,7 +516,6 @@ const LotTable: React.FC<LotTableProps> = ({
setPickExecutionFormOpen(false); setPickExecutionFormOpen(false);
setSelectedLotForExecutionForm(null); setSelectedLotForExecutionForm(null);
// 刷新数据
if (onDataRefresh) { if (onDataRefresh) {
await 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("Lot Required Pick Qty")}</TableCell>
<TableCell align="right">{t("Original Available Qty")}</TableCell> <TableCell align="right">{t("Original Available Qty")}</TableCell>
<TableCell align="center">{t("Lot Actual Pick 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="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> <TableCell align="center">{t("Action")}</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
@@ -623,7 +557,7 @@ const LotTable: React.FC<LotTableProps> = ({
) : ( ) : (
paginatedLotTableData.map((lot, index) => ( paginatedLotTableData.map((lot, index) => (
<TableRow <TableRow
key={lot.id}
key={lot.noLot ? `noLot_${lot.stockOutLineId}_${index}` : `lot_${lot.lotId}_${index}`}
sx={{ sx={{
backgroundColor: lot.lotAvailability === 'rejected' ? 'grey.100' : 'inherit', backgroundColor: lot.lotAvailability === 'rejected' ? 'grey.100' : 'inherit',
opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1, opacity: lot.lotAvailability === 'rejected' ? 0.6 : 1,
@@ -633,36 +567,30 @@ const LotTable: React.FC<LotTableProps> = ({
}} }}
> >
<TableCell> <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> <TableCell>
<Box> <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> </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> </Box>
</TableCell> </TableCell>
<TableCell>{lot.expiryDate}</TableCell> <TableCell>{lot.expiryDate}</TableCell>
@@ -673,154 +601,76 @@ const LotTable: React.FC<LotTableProps> = ({
{(() => { {(() => {
const inQty = lot.inQty || 0; const inQty = lot.inQty || 0;
const outQty = lot.outQty || 0; const outQty = lot.outQty || 0;
const result = inQty - outQty; const result = inQty - outQty;
return result.toLocaleString(); return result.toLocaleString();
})()} })()}
</TableCell> </TableCell>
<TableCell align="center"> <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> </TableCell>
</TableRow> </TableRow>
)) ))
@@ -828,50 +678,9 @@ const LotTable: React.FC<LotTableProps> = ({
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </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 && ( {pickExecutionFormOpen && selectedLotForExecutionForm && selectedRow && (
<PickExecutionForm <PickExecutionForm
open={pickExecutionFormOpen} open={pickExecutionFormOpen}


+ 74
- 31
src/components/PickOrderSearch/PickExecution.tsx 查看文件

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


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


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


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


const handleRowSelect = useCallback(async (lineId: number, preserveLotSelection: boolean = false) => { const handleRowSelect = useCallback(async (lineId: number, preserveLotSelection: boolean = false) => {
setSelectedRowId(lineId); setSelectedRowId(lineId);
@@ -604,35 +605,42 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
try { try {
const lotDetails = await fetchPickOrderLineLotDetails(lineId); const lotDetails = await fetchPickOrderLineLotDetails(lineId);
console.log("lineId:", lineId);
console.log("Lot details from API:", lotDetails); 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, lotId: lot.lotId,
lotNo: lot.lotNo, 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, actualPickQty: lot.actualPickQty || 0,
lotStatus: lot.lotStatus,
lotStatus: lot.lotStatus || "unavailable",
lotAvailability: lot.lotAvailability, 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) { } catch (error) {
console.error("Error fetching lot details:", error); console.error("Error fetching lot details:", error);
setLotData([]); setLotData([]);
} }
}, []);
}, []);


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


const generateInputBody = useCallback((): CreateStockOutLine | null => {
if (!selectedLotForInput || !selectedRowId || !selectedRow || !pickOrderDetails?.consoCode) {
const generateInputBody = useCallback(() => {
if (!selectedLotForInput || !selectedRowId || !pickOrderDetails?.consoCode) {
return null; return null;
} }
// ✅ 處理 lotId 可能為 null 的情況
if (selectedLotForInput.lotId === null) {
return null; // no-lot 行不能創建 stock out line
}
return { return {
consoCode: pickOrderDetails.consoCode, consoCode: pickOrderDetails.consoCode,
pickOrderLineId: selectedRowId, pickOrderLineId: selectedRowId,
inventoryLotLineId: selectedLotForInput.lotId,
inventoryLotLineId: selectedLotForInput.lotId, // ✅ 現在確定不是 null
qty: 0.0 qty: 0.0
}; };
}, [selectedLotForInput, selectedRowId, selectedRow, pickOrderDetails?.consoCode]); }, [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) => { const handleCreateStockOutLine = useCallback(async (inventoryLotLineId: number) => {
if (!selectedRowId || !pickOrderDetails?.consoCode) { if (!selectedRowId || !pickOrderDetails?.consoCode) {
console.error("Missing required data for creating stock out line."); console.error("Missing required data for creating stock out line.");
@@ -966,6 +1005,7 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
onLotDataRefresh={handleLotDataRefresh} onLotDataRefresh={handleLotDataRefresh}
onLotSelectForInput={handleLotSelectForInput} onLotSelectForInput={handleLotSelectForInput}
showInputBody={showInputBody} showInputBody={showInputBody}
onIssueNoLotStockOutLine={handleIssueNoLotStockOutLine}
setShowInputBody={setShowInputBody} setShowInputBody={setShowInputBody}
selectedLotForInput={selectedLotForInput} selectedLotForInput={selectedLotForInput}
generateInputBody={generateInputBody} generateInputBody={generateInputBody}
@@ -1038,7 +1078,9 @@ const PickExecution: React.FC<Props> = ({ filterArgs }) => {
</Grid> </Grid>
)} )}



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


+ 7
- 6
src/components/PickOrderSearch/PickExecutionForm.tsx 查看文件

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


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


interface PickExecutionFormProps { interface PickExecutionFormProps {
@@ -144,9 +145,9 @@ const calculateRequiredQty = useCallback((lot: LotPickData) => {
itemId: selectedPickOrderLine.itemId, itemId: selectedPickOrderLine.itemId,
itemCode: selectedPickOrderLine.itemCode, itemCode: selectedPickOrderLine.itemCode,
itemDescription: selectedPickOrderLine.itemName, 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, requiredQty: selectedLot.requiredQty,
actualPickQty: selectedLot.actualPickQty || 0, actualPickQty: selectedLot.actualPickQty || 0,
missQty: 0, missQty: 0,


+ 5
- 5
src/components/PickOrderSearch/PickOrderSearch.tsx 查看文件

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


+ 6
- 4
src/components/PickOrderSearch/PickQcStockInModalVer3.tsx 查看文件

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


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


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


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


<Grid item xs={12} sx={{ mt: 2 }}> <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 查看文件

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


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


</Box> </Box>
</Box> </Box>
); );


+ 6
- 5
src/i18n/zh/common.json 查看文件

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

+ 12
- 20
src/i18n/zh/jo.json 查看文件

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

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

"Start Scan": "開始掃碼", "Start Scan": "開始掃碼",
"Stop 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": "登出" "Sign out": "登出"
} }

+ 1
- 0
src/i18n/zh/pickOrder.json 查看文件

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


Loading…
取消
儲存