| @@ -25,6 +25,7 @@ export interface StockIssueResult { | |||||
| handleStatus: string; | handleStatus: string; | ||||
| handleDate: string | null; | handleDate: string | null; | ||||
| handledBy: number | null; | handledBy: number | null; | ||||
| uomDesc: string | null; | |||||
| } | } | ||||
| export interface ExpiryItemResult { | export interface ExpiryItemResult { | ||||
| id: number; | id: number; | ||||
| @@ -178,6 +179,8 @@ export async function submitMissItem(issueId: number, handler: number) { | |||||
| itemDescription: string | null; | itemDescription: string | null; | ||||
| storeLocation: string | null; | storeLocation: string | null; | ||||
| issues: IssueDetailItem[]; | issues: IssueDetailItem[]; | ||||
| bookQty: number; | |||||
| uomDesc: string | null; | |||||
| } | } | ||||
| export interface IssueDetailItem { | export interface IssueDetailItem { | ||||
| @@ -8,7 +8,7 @@ export const LoadingComponent: React.FC = () => { | |||||
| display="flex" | display="flex" | ||||
| justifyContent="center" | justifyContent="center" | ||||
| alignItems="center" | alignItems="center" | ||||
| // autoheight="true" | |||||
| > | > | ||||
| <CircularProgress /> | <CircularProgress /> | ||||
| </Box> | </Box> | ||||
| @@ -370,7 +370,7 @@ function InputDataGrid<T, V, E>({ | |||||
| // columns={!checkboxSelection ? _columns : columns} | // columns={!checkboxSelection ? _columns : columns} | ||||
| columns={needActions ? _columns : columns} | columns={needActions ? _columns : columns} | ||||
| editMode="row" | editMode="row" | ||||
| // autoHeight | |||||
| sx={{ | sx={{ | ||||
| height: "30vh", | height: "30vh", | ||||
| "--DataGrid-overlayHeight": "100px", | "--DataGrid-overlayHeight": "100px", | ||||
| @@ -509,9 +509,11 @@ const FInishedJobOrderRecord: React.FC<Props> = ({ filterArgs }) => { | |||||
| size="small" | size="small" | ||||
| sx={{ mb: 1 }} | sx={{ mb: 1 }} | ||||
| /> | /> | ||||
| {/* | |||||
| <Typography variant="body2" color="text.secondary"> | <Typography variant="body2" color="text.secondary"> | ||||
| {jobOrderPickOrder.completedItems}/{jobOrderPickOrder.totalItems} {t("items completed")} | {jobOrderPickOrder.completedItems}/{jobOrderPickOrder.totalItems} {t("items completed")} | ||||
| </Typography> | </Typography> | ||||
| */} | |||||
| <Chip | <Chip | ||||
| label={jobOrderPickOrder.secondScanCompleted ? t("Second Scan Completed") : t("Second Scan Pending")} | label={jobOrderPickOrder.secondScanCompleted ? t("Second Scan Completed") : t("Second Scan Pending")} | ||||
| color={jobOrderPickOrder.secondScanCompleted ? 'success' : 'warning'} | color={jobOrderPickOrder.secondScanCompleted ? 'success' : 'warning'} | ||||
| @@ -247,10 +247,9 @@ useEffect(() => { | |||||
| // 增加 badPackageQty 判断,确保有坏包装会走 issue 流程 | // 增加 badPackageQty 判断,确保有坏包装会走 issue 流程 | ||||
| const badPackageQty = Number((formData as any).badPackageQty) || 0; | const badPackageQty = Number((formData as any).badPackageQty) || 0; | ||||
| const isNormalPick = verifiedQty > 0 | |||||
| && formData.missQty == 0 | |||||
| && formData.badItemQty == 0 | |||||
| && badPackageQty == 0; | |||||
| const isNormalPick = (formData.missQty == null || formData.missQty === 0) | |||||
| && (formData.badItemQty == null || formData.badItemQty === 0) | |||||
| && (badPackageQty === 0); | |||||
| if (isNormalPick) { | if (isNormalPick) { | ||||
| if (onNormalPickSubmit) { | if (onNormalPickSubmit) { | ||||
| @@ -1822,7 +1822,7 @@ const JobPickExecution: React.FC<Props> = ({ filterArgs, onBackToList }) => { | |||||
| }, [handleSubmitPickQtyWithQty]); | }, [handleSubmitPickQtyWithQty]); | ||||
| const handleSubmitAllScanned = useCallback(async () => { | const handleSubmitAllScanned = useCallback(async () => { | ||||
| const scannedLots = combinedLotData.filter(lot => | const scannedLots = combinedLotData.filter(lot => | ||||
| lot.stockOutLineStatus === 'checked' | |||||
| lot.stockOutLineStatus === 'checked' || lot.stockOutLineStatus === 'partially_completed' | |||||
| ); | ); | ||||
| if (scannedLots.length === 0) { | if (scannedLots.length === 0) { | ||||
| @@ -306,7 +306,7 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| {statusLower === "completed" && ( | {statusLower === "completed" && ( | ||||
| <Button onClick={() => handleViewStockIn(process)}> | |||||
| <Button variant="contained" size="small" onClick={() => handleViewStockIn(process)}> | |||||
| {t("view stockin")} | {t("view stockin")} | ||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| @@ -232,7 +232,7 @@ const QcForm: React.FC<Props> = ({ rows, disabled = false }) => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| // autoHeight | |||||
| <StyledDataGrid | <StyledDataGrid | ||||
| columns={qcColumns} | columns={qcColumns} | ||||
| rows={rows} | rows={rows} | ||||
| @@ -169,7 +169,17 @@ const SearchPage: React.FC<Props> = ({ dataList }) => { | |||||
| { name: "itemDescription", label: t("Item") }, | { name: "itemDescription", label: t("Item") }, | ||||
| { name: "lotNo", label: t("Lot No.") }, | { name: "lotNo", label: t("Lot No.") }, | ||||
| { name: "storeLocation", label: t("Location") }, | { name: "storeLocation", label: t("Location") }, | ||||
| { | |||||
| name: "bookQty", | |||||
| label: t("Book Qty"), | |||||
| renderCell: (item) => ( | |||||
| <>{item.bookQty?.toFixed(2) ?? "0"} {item.uomDesc ?? ""}</> | |||||
| ), | |||||
| }, | |||||
| { name: "issueQty", label: t("Miss Qty") }, | { name: "issueQty", label: t("Miss Qty") }, | ||||
| { name: "uomDesc", label: t("UoM"), renderCell: (item) => ( | |||||
| <>{item.uomDesc ?? ""}</> | |||||
| ) }, | |||||
| { | { | ||||
| name: "id", | name: "id", | ||||
| label: t("Action"), | label: t("Action"), | ||||
| @@ -196,6 +206,9 @@ const SearchPage: React.FC<Props> = ({ dataList }) => { | |||||
| { name: "lotNo", label: t("Lot No.") }, | { name: "lotNo", label: t("Lot No.") }, | ||||
| { name: "storeLocation", label: t("Location") }, | { name: "storeLocation", label: t("Location") }, | ||||
| { name: "issueQty", label: t("Defective Qty") }, | { name: "issueQty", label: t("Defective Qty") }, | ||||
| { name: "uomDesc", label: t("UoM"), renderCell: (item) => ( | |||||
| <>{item.uomDesc ?? ""}</> | |||||
| ) }, | |||||
| { | { | ||||
| name: "id", | name: "id", | ||||
| label: t("Action"), | label: t("Action"), | ||||
| @@ -49,7 +49,10 @@ const SubmitIssueForm: React.FC<Props> = ({ | |||||
| const [submitting, setSubmitting] = useState(false); | const [submitting, setSubmitting] = useState(false); | ||||
| const [details, setDetails] = useState<LotIssueDetailResponse | null>(null); | const [details, setDetails] = useState<LotIssueDetailResponse | null>(null); | ||||
| const [submitQty, setSubmitQty] = useState<string>(""); | const [submitQty, setSubmitQty] = useState<string>(""); | ||||
| const bookQty = details?.bookQty ?? 0; | |||||
| const submitQtyNum = parseFloat(submitQty); | |||||
| const submitQtyValid = !Number.isNaN(submitQtyNum) && submitQtyNum >= 0; | |||||
| const remainAvailable = submitQtyValid ? Math.max(0, bookQty - submitQtyNum) : bookQty; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (open && lotId) { | if (open && lotId) { | ||||
| loadDetails(); | loadDetails(); | ||||
| @@ -121,9 +124,17 @@ const SubmitIssueForm: React.FC<Props> = ({ | |||||
| <Typography variant="body2" sx={{ mb: 1 }}> | <Typography variant="body2" sx={{ mb: 1 }}> | ||||
| <strong>{t("Lot No.")}:</strong> {details.lotNo} | <strong>{t("Lot No.")}:</strong> {details.lotNo} | ||||
| </Typography> | </Typography> | ||||
| <Typography variant="body2" sx={{ mb: 2 }}> | |||||
| <Typography variant="body2" sx={{ mb: 1 }}> | |||||
| <strong>{t("Location")}:</strong> {details.storeLocation} | <strong>{t("Location")}:</strong> {details.storeLocation} | ||||
| </Typography> | </Typography> | ||||
| <Typography variant="body2" sx={{ mb: 1 }}> | |||||
| <strong>{t("Book Qty")}:</strong>{" "} | |||||
| {details.bookQty} | |||||
| </Typography> | |||||
| <Typography variant="body2" sx={{ mb: 1 }}> | |||||
| <strong>{t("UoM")}:</strong>{" "} | |||||
| {details.uomDesc ?? ""} | |||||
| </Typography> | |||||
| </Box> | </Box> | ||||
| <TableContainer component={Paper} sx={{ mb: 2 }}> | <TableContainer component={Paper} sx={{ mb: 2 }}> | ||||
| @@ -146,8 +157,8 @@ const SubmitIssueForm: React.FC<Props> = ({ | |||||
| <TableCell>{issue.pickerName || "-"}</TableCell> | <TableCell>{issue.pickerName || "-"}</TableCell> | ||||
| <TableCell align="right"> | <TableCell align="right"> | ||||
| {issueType === "miss" | {issueType === "miss" | ||||
| ? issue.missQty?.toFixed(2) || "0" | |||||
| : issue.issueQty?.toFixed(2) || "0"} | |||||
| ? issue.missQty?.toFixed(0) || "0" | |||||
| : issue.issueQty?.toFixed(0) || "0"} | |||||
| </TableCell> | </TableCell> | ||||
| <TableCell>{issue.pickOrderCode}</TableCell> | <TableCell>{issue.pickOrderCode}</TableCell> | ||||
| <TableCell>{issue.doOrderCode || "-"}</TableCell> | <TableCell>{issue.doOrderCode || "-"}</TableCell> | ||||
| @@ -168,6 +179,26 @@ const SubmitIssueForm: React.FC<Props> = ({ | |||||
| inputProps={{ min: 0, step: 0.01 }} | inputProps={{ min: 0, step: 0.01 }} | ||||
| sx={{ mt: 2 }} | sx={{ mt: 2 }} | ||||
| /> | /> | ||||
| <TextField | |||||
| fullWidth | |||||
| label={t("Remain available Quantity")} | |||||
| type="number" | |||||
| value={remainAvailable} | |||||
| onChange={(e) => { | |||||
| const raw = e.target.value; | |||||
| if (raw === "") { | |||||
| setSubmitQty(""); | |||||
| return; | |||||
| } | |||||
| const remain = parseFloat(raw); | |||||
| if (!Number.isNaN(remain) && remain >= 0) { | |||||
| const newSubmit = Math.max(0, bookQty - remain); | |||||
| setSubmitQty(newSubmit.toFixed(0)); | |||||
| } | |||||
| }} | |||||
| inputProps={{ min: 0, step: 0.01, readOnly: false }} | |||||
| sx={{ mt: 2 }} | |||||
| /> | |||||
| </DialogContent> | </DialogContent> | ||||
| <DialogActions> | <DialogActions> | ||||
| <Button onClick={onClose} disabled={submitting}> | <Button onClick={onClose} disabled={submitting}> | ||||
| @@ -13,7 +13,7 @@ | |||||
| "Overall Time Remaining": "總剩餘時間", | "Overall Time Remaining": "總剩餘時間", | ||||
| "Reset": "重置", | "Reset": "重置", | ||||
| "Search": "搜索", | "Search": "搜索", | ||||
| "This lot is rejected, please scan another lot.": "此批次已封存,請掃描另一個批號。", | |||||
| "This lot is rejected, please scan another lot.": "此批次發現問題,請掃描另一個批號。", | |||||
| "Process Start Time": "工序開始時間", | "Process Start Time": "工序開始時間", | ||||
| "Stock Req. Qty": "需求數", | "Stock Req. Qty": "需求數", | ||||
| "Staff No Required": "員工編號必填", | "Staff No Required": "員工編號必填", | ||||
| @@ -12,9 +12,16 @@ | |||||
| "Record Status": "記錄狀態", | "Record Status": "記錄狀態", | ||||
| "Stock take record status updated to not match": "盤點記錄狀態更新為數值不符", | "Stock take record status updated to not match": "盤點記錄狀態更新為數值不符", | ||||
| "available": "可用", | "available": "可用", | ||||
| "Issue Qty": "問題數量", | |||||
| "Submit Bad Item": "提交不良品", | |||||
| "Remain available Quantity": "剩餘可用數量", | |||||
| "Submitting...": "提交中...", | |||||
| "Item-lotNo-ExpiryDate": "貨品-批號-到期日", | "Item-lotNo-ExpiryDate": "貨品-批號-到期日", | ||||
| "Submit Miss Item": "提交缺貨", | |||||
| "Item-lotNo-ExpiryDate": "貨品-批號-到期日", | "Item-lotNo-ExpiryDate": "貨品-批號-到期日", | ||||
| "not available": "不可用", | "not available": "不可用", | ||||
| "Book Qty": "帳面庫存", | |||||
| "Submit Quantity": "提交數量", | |||||
| "Batch Submit All": "批量提交所有", | "Batch Submit All": "批量提交所有", | ||||
| "Batch Save All": "批量保存所有", | "Batch Save All": "批量保存所有", | ||||
| "Batch Submit All": "批量提交所有", | "Batch Submit All": "批量提交所有", | ||||
| @@ -120,7 +120,7 @@ | |||||
| "Pick Order Detail": "提料單細節", | "Pick Order Detail": "提料單細節", | ||||
| "Finished Job Order Record": "已完成工單記錄", | "Finished Job Order Record": "已完成工單記錄", | ||||
| "No. of Items to be Picked": "需提料數量", | "No. of Items to be Picked": "需提料數量", | ||||
| "No. of Items with Issue During Pick": "提料過程中出現問題的數量", | |||||
| "No. of Items with Issue During Pick": "問題數量", | |||||
| "Pick Start Time": "提料開始時間", | "Pick Start Time": "提料開始時間", | ||||
| "Pick End Time": "提料結束時間", | "Pick End Time": "提料結束時間", | ||||
| "FG / WIP Item": "成品/半成品", | "FG / WIP Item": "成品/半成品", | ||||