瀏覽代碼

update 1-9

master
CANCERYS\kw093 1 天之前
父節點
當前提交
2575dbc099
共有 14 個檔案被更改,包括 644 行新增513 行删除
  1. +1
    -0
      src/app/api/bom/index.ts
  2. +13
    -1
      src/app/api/jo/actions.ts
  3. +32
    -21
      src/components/JoSearch/JoCreateFormModal.tsx
  4. +2
    -1
      src/components/JoSearch/JoSearch.tsx
  5. +6
    -0
      src/components/JoSearch/JoSearchWrapper.tsx
  6. +484
    -468
      src/components/Jodetail/JobPickExecutionsecondscan.tsx
  7. +1
    -1
      src/components/PickOrderSearch/PickExecution.tsx
  8. +2
    -1
      src/components/ProductionProcess/ProcessSummaryHeader.tsx
  9. +16
    -1
      src/components/ProductionProcess/ProductionProcessDetail.tsx
  10. +11
    -10
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
  11. +51
    -2
      src/components/ProductionProcess/ProductionProcessList.tsx
  12. +19
    -3
      src/components/ProductionProcess/ProductionProcessPage.tsx
  13. +3
    -2
      src/i18n/zh/common.json
  14. +3
    -2
      src/i18n/zh/jo.json

+ 1
- 0
src/app/api/bom/index.ts 查看文件

@@ -7,6 +7,7 @@ export interface BomCombo {
value: number;
label: string;
outputQty: number;
outputQtyUom: string;
}

export const preloadBomCombo = (() => {


+ 13
- 1
src/app/api/jo/actions.ts 查看文件

@@ -248,6 +248,8 @@ export interface ProductProcessWithLinesResponse {
isDark: string;
isDense: number;
isFloat: string;
timeSequence: number;
complexity: number;
scrapRate: number;
allergicSubstance: string;
itemId: number;
@@ -315,6 +317,8 @@ export interface AllJoborderProductProcessInfoResponse {
endTime?: string;
date: string;
bomId?: number;
assignedTo: number;
pickOrderId: number;
itemName: string;
requiredQty: number;
jobOrderId: number;
@@ -834,7 +838,15 @@ export const assignJobOrderPickOrder = async (pickOrderId: number, userId: numbe
}
);
};

export const unAssignJobOrderPickOrder = async (pickOrderId: number) => {
return serverFetchJson<AssignJobOrderResponse>(
`${BASE_API_URL}/jo/unassign-job-order-pick-order/${pickOrderId}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
}
);
};
// 获取 Job Order 分层数据
export const fetchJobOrderLotsHierarchical = cache(async (userId: number) => {
return serverFetchJson<JobOrderLotsHierarchicalResponse>(


+ 32
- 21
src/components/JoSearch/JoCreateFormModal.tsx 查看文件

@@ -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 ,FormControl, InputLabel, Select, MenuItem} from "@mui/material";
import { Autocomplete, Box, Button, Card, Grid, Modal, Stack, TextField, Typography ,FormControl, InputLabel, Select, MenuItem,InputAdornment} 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";
@@ -66,7 +66,7 @@ const JoCreateFormModal: React.FC<Props> = ({
msg(t("update success"));
onModalClose();
}
}, [])
}, [onSearch, onModalClose, t])

const onSubmitError = useCallback<SubmitErrorHandler<SaveJo>>((error) => {
console.log(error)
@@ -166,25 +166,36 @@ const JoCreateFormModal: React.FC<Props> = ({
required: "Req. Qty. required!",
validate: (value) => value > 0
}}
render={({ field, fieldState: { error } }) => (
<TextField
{...field}
label={t("Req. Qty")}
fullWidth
error={Boolean(error)}
variant="outlined"
type="number"
disabled={true}
// sx={{
// backgroundColor: "background.paper",
// }}
value={field.value ?? ""}
onChange={(e) => {
const val = e.target.value === "" ? undefined : Number(e.target.value);
field.onChange(val);
}}
/>
)}
render={({ field, fieldState: { error } }) => {
const selectedBom = bomCombo.find(bom => bom.id === formProps.watch("bomId"));
const uom = selectedBom?.outputQtyUom || "";
return (
<TextField
{...field}
label={t("Req. Qty")}
fullWidth
error={Boolean(error)}
variant="outlined"
type="number"
disabled={true}
value={field.value ?? ""}
onChange={(e) => {
const val = e.target.value === "" ? undefined : Number(e.target.value);
field.onChange(val);
}}
InputProps={{
endAdornment: uom ? (
<InputAdornment position="end">
<Typography variant="body2" sx={{ color: "text.secondary" }}>
{uom}
</Typography>
</InputAdornment>
) : null
}}
/>
);
}}
/>
</Grid>
<Grid item xs={12} sm={12} md={6}>


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

@@ -400,7 +400,8 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo, printerCombo, jobT
bomCombo={bomCombo}
onClose={onCloseCreateJoModal}
onSearch={() => {
setInputs(defaultInputs);
setPagingController(defaultPagingController);
}}
/>



+ 6
- 0
src/components/JoSearch/JoSearchWrapper.tsx 查看文件

@@ -10,9 +10,15 @@ interface SubComponents {
}

const JoSearchWrapper: React.FC & SubComponents = async () => {
const today = new Date();
const todayStr = today.toISOString().split('T')[0];

const defaultInputs: SearchJoResultRequest = {
code: "",
itemName: "",
planStart: `${todayStr}T00:00`,
planStartTo: `${todayStr}T23:59:59`,

}

const [


+ 484
- 468
src/components/Jodetail/JobPickExecutionsecondscan.tsx
文件差異過大導致無法顯示
查看文件


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

@@ -1007,7 +1007,7 @@ const handleIssueNoLotStockOutLine = useCallback(async (stockOutLineId: number)
showInputBody={showInputBody}
onIssueNoLotStockOutLine={handleIssueNoLotStockOutLine}
setShowInputBody={setShowInputBody}
selectedLotForInput={selectedLotForInput}
//selectedLotForInput={selectedLotForInput}
generateInputBody={generateInputBody}
// Add missing props
totalPickedByAllPickOrders={0} // You can calculate this from lotData if needed


+ 2
- 1
src/components/ProductionProcess/ProcessSummaryHeader.tsx 查看文件

@@ -10,6 +10,7 @@ interface Props {
itemName?: string;
jobType?: string;
outputQty?: number | string;
outputQtyUom?: string;
date?: string;
};
}
@@ -32,7 +33,7 @@ const ProcessSummaryHeader: React.FC<Props> = ({ processData }) => {
</Typography>

<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
{t("Qty")}: <strong style={{ color: "green" }}>{processData?.outputQty}</strong>
{t("Qty")}: <strong style={{ color: "green" }}>{processData?.outputQty}</strong> ({processData?.outputQtyUom ?? ""})
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
{t("Production Date")}: <strong style={{ color: "green" }}>{processData?.date ? dayjs(processData.date).format(OUTPUT_DATE_FORMAT) : ""}</strong>


+ 16
- 1
src/components/ProductionProcess/ProductionProcessDetail.tsx 查看文件

@@ -599,12 +599,14 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => {
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
{t("Time Information(mins)")}
</Typography>
{/*
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{t("Processing Time")}-
</Typography>
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{t("Setup Time")} - {t("Changeover Time")}
</Typography>
*/}
</Box>
</TableCell>
<TableCell align="center">{t("Status")}</TableCell>
@@ -637,7 +639,20 @@ const processQrCode = useCallback((qrValue: string, lineId: number) => {
<TableCell><Typography fontWeight={500}>{line.prepTimeInMinutes} </Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.postProdTimeInMinutes} </Typography></TableCell>
*/}
<TableCell><Typography fontWeight={500}>{line.durationInMinutes} - {line.prepTimeInMinutes} - {line.postProdTimeInMinutes} </Typography></TableCell>
<TableCell>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
<Typography variant="body2" >
{t("Processing Time")}: {line.durationInMinutes}{t("mins")}
</Typography>
<Typography variant="body2" >
{t("Setup Time")}: {line.prepTimeInMinutes} {t("mins")}
</Typography>
<Typography variant="body2" >

{t("Changeover Time")}: {line.postProdTimeInMinutes} {t("mins")}
</Typography>
</Box>
</TableCell>
<TableCell align="center">
{isCompleted ? (
<Chip label={t("Completed")} color="success" size="small"


+ 11
- 10
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx 查看文件

@@ -38,6 +38,7 @@ import { releaseJo, startJo } from "@/app/api/jo/actions";
import JobPickExecutionsecondscan from "../Jodetail/JobPickExecutionsecondscan";
import ProcessSummaryHeader from "./ProcessSummaryHeader";
import EditIcon from "@mui/icons-material/Edit";

interface JobOrderLine {
id: number;
jobOrderId: number;
@@ -292,10 +293,10 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
</Grid>
<Grid item xs={6}>
<TextField
label={t("Is Dark | Dense | Float| Scrap Rate| Allergic Substance")}
label={t("Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity")}
fullWidth
disabled={true}
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} | ${processData?.scrapRate == -1 || processData?.scrapRate === "" ? t("N/A") : processData.scrapRate} | ${processData?.allergicSubstance == null || processData?.allergicSubstance === "" ? t("N/A") :t (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} | ${processData?.scrapRate == -1 || processData?.scrapRate === "" ? t("N/A") : processData.scrapRate} | ${processData?.allergicSubstance == null || processData?.allergicSubstance === "" ? t("N/A") :t (processData.allergicSubstance)} | ${processData?.timeSequence == null || processData?.timeSequence === "" ? t("N/A") : processData.timeSequence} | ${processData?.complexity == null || processData?.complexity === "" ? t("N/A") : processData.complexity}`}
/>
</Grid>

@@ -311,10 +312,10 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
headerName: t("Seq"),
flex: 0.2,
align: "left",
headerAlign: "center",
headerAlign: "left",
type: "number",
renderCell: (params) => {
return <Typography sx={{ fontSize: "14px" }}>{params.value}</Typography>;
return <Typography sx={{ fontSize: "18px" }}>{params.value}</Typography>;
},
},
{
@@ -322,9 +323,9 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
headerName: t("Remark"),
flex: 1,
align: "left",
headerAlign: "center",
headerAlign: "left",
renderCell: (params) => {
return <Typography sx={{ fontSize: "14px" }}>{params.value || ""}</Typography>;
return <Typography sx={{ fontSize: "18px" }}>{params.value || ""}</Typography>;
},
},
];
@@ -521,9 +522,9 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
<Tab label={t("Production Process Line Remark")} />
{!fromJosave && (
{/* {!fromJosave && (
<Tab label={t("Matching Stock")} />
)}
)} */}
</Tabs>
</Box>

@@ -545,8 +546,8 @@ const handleRelease = useCallback(async ( jobOrderId: number) => {
)}
{tabIndex === 3 && <ProductionProcessesLineRemarkTableContent />}
{tabIndex === 4 && <JobPickExecutionsecondscan filterArgs={{ jobOrderId: jobOrderId }} />}
{/* {tabIndex === 4 && <JobPickExecutionsecondscan filterArgs={{ jobOrderId: jobOrderId }} />} */}
<Dialog
open={openOperationPriorityDialog}
onClose={handleClosePriorityDialog}


+ 51
- 2
src/components/ProductionProcess/ProductionProcessList.tsx 查看文件

@@ -24,19 +24,22 @@ import {
AllJoborderProductProcessInfoResponse,
updateJo,
fetchProductProcessesByJobOrderId,
completeProductProcessLine
completeProductProcessLine,
assignJobOrderPickOrder
} from "@/app/api/jo/actions";
import { StockInLineInput } from "@/app/api/stockIn";
import { PrinterCombo } from "@/app/api/settings/printer";
import JobPickExecutionsecondscan from "../Jodetail/JobPickExecutionsecondscan";
interface ProductProcessListProps {
onSelectProcess: (jobOrderId: number|undefined, productProcessId: number|undefined) => void;
onSelectMatchingStock: (jobOrderId: number|undefined, productProcessId: number|undefined) => void;
printerCombo: PrinterCombo[];
}


const PER_PAGE = 6;

const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess, printerCombo }) => {
const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess, printerCombo ,onSelectMatchingStock}) => {
const { t } = useTranslation( ["common", "production","purchaseOrder"]);
const { data: session } = useSession() as { data: SessionWithTokens | null };
const sessionToken = session as SessionWithTokens | null;
@@ -45,6 +48,44 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
const [page, setPage] = useState(0);
const [openModal, setOpenModal] = useState<boolean>(false);
const [modalInfo, setModalInfo] = useState<StockInLineInput>();
const currentUserId = session?.id ? parseInt(session.id) : undefined;
const handleAssignPickOrder = useCallback(async (pickOrderId: number, jobOrderId?: number, productProcessId?: number) => {
if (!currentUserId) {
alert(t("无法获取用户ID"));
return;
}

try {
console.log("🔄 Assigning pick order:", pickOrderId, "to user:", currentUserId);
// 调用分配 API 并读取响应
const assignResult = await assignJobOrderPickOrder(pickOrderId, currentUserId);
console.log("📦 Assign result:", assignResult);
// 检查分配是否成功
if (assignResult.message === "Successfully assigned") {
console.log("✅ Successfully assigned pick order");
console.log("✅ Pick order ID:", assignResult.id);
console.log("✅ Pick order code:", assignResult.code);
// 分配成功后,导航到 second scan 页面
if (onSelectMatchingStock && jobOrderId) {
onSelectMatchingStock(jobOrderId, productProcessId);
} else {
alert(t("分配成功"));
}
} else {
// 分配失败
console.error("❌ Assignment failed:", assignResult.message);
alert(t(`分配失败: ${assignResult.message || "未知错误"}`));
}
} catch (error: any) {
console.error("❌ Error assigning pick order:", error);
alert(t(`分配时出错: ${error?.message || "未知错误"}。请稍后重试。`));
}
}, [currentUserId, t, onSelectMatchingStock]);
const handleViewStockIn = useCallback((process: AllJoborderProductProcessInfoResponse) => {
if (!process.stockInLineId) {
alert(t("Invalid Stock In Line Id"));
@@ -224,6 +265,14 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
<Button variant="contained" size="small" onClick={() => onSelectProcess(process.jobOrderId, process.id)}>
{t("View Details")}
</Button>
<Button
variant="contained"
size="small"
disabled={process.assignedTo != null}
onClick={() => handleAssignPickOrder(process.pickOrderId, process.jobOrderId, process.id)}
>
{t("Matching Stock")}
</Button>
{statusLower !== "completed" && (
<Button variant="contained" size="small" onClick={() => handleUpdateJo(process)}>
{t("Update Job Order")}


+ 19
- 3
src/components/ProductionProcess/ProductionProcessPage.tsx 查看文件

@@ -5,7 +5,7 @@ import { SessionWithTokens } from "@/config/authConfig";
import ProductionProcessList from "@/components/ProductionProcess/ProductionProcessList";
import ProductionProcessDetail from "@/components/ProductionProcess/ProductionProcessDetail";
import ProductionProcessJobOrderDetail from "@/components/ProductionProcess/ProductionProcessJobOrderDetail";
import JobPickExecutionsecondscan from "@/components/Jodetail/JobPickExecutionsecondscan";
import {
fetchProductProcesses,
fetchProductProcessesByJobOrderId,
@@ -28,11 +28,21 @@ interface ProductionProcessPageProps {

const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCombo }) => {
const [selectedProcessId, setSelectedProcessId] = useState<number | null>(null);
const [selectedMatchingStock, setSelectedMatchingStock] = useState<{
jobOrderId: number;
productProcessId: number;
} | null>(null);
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;

// …原有逻辑省略…

if (selectedMatchingStock) {
return (
<JobPickExecutionsecondscan
filterArgs={{ jobOrderId: selectedMatchingStock.jobOrderId }}
onBack={() => setSelectedMatchingStock(null)}
/>
);
}
if (selectedProcessId !== null) {
return (
<ProductionProcessJobOrderDetail
@@ -51,6 +61,12 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo
setSelectedProcessId(id);
}
}}
onSelectMatchingStock={(jobOrderId, productProcessId) => {
setSelectedMatchingStock({
jobOrderId: jobOrderId || 0,
productProcessId: productProcessId || 0
});
}}
/>
);
};


+ 3
- 2
src/i18n/zh/common.json 查看文件

@@ -118,7 +118,7 @@
"Stop Scan": "停止掃碼",
"Scan Result": "掃碼結果",
"Expiry Date": "有效期",
"Is Dark | Dense | Float| Scrap Rate| Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原",
"Pick Order Code": "提料單編號",
"Target Date": "需求日期",
"Lot Required Pick Qty": "批號需求數量",
@@ -183,7 +183,7 @@
"Back": "返回",
"BoM Material": "物料清單",
"N/A": "不適用",
"Is Dark | Dense | Float | Scrap Rate | Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原",
"Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原 | 時間順序 | 複雜度",
"Item Code": "物料編號",
"Item Name": "物料名稱",
"Job Order Info": "工單信息",
@@ -210,6 +210,7 @@
"Reason": "原因",
"Invalid Stock In Line Id": "無效庫存行ID",
"Production date": "生產日期",
"update production priority": "更新生產優先級",
"Required Qty": "需求數量",
"Total processes": "總流程數",
"View Details": "查看詳情",


+ 3
- 2
src/i18n/zh/jo.json 查看文件

@@ -47,6 +47,7 @@
"Pause Reason": "暫停原因",
"Reason": "原因",
"Stock Available": "倉庫可用數",
"update production priority": "更新生產優先級",
"Staff No": "員工編號",
"Please scan staff no": "請掃描員工編號",
"Stock Status": "可提料",
@@ -349,8 +350,8 @@
"View": "查看",
"Back": "返回",
"N/A": "不適用",
"BoM Material": "半成品/成品清單",
"Is Dark | Dense | Float| Scrap Rate| Allergic Substance": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原",
"BoM Material": "物料清單",
"Is Dark | Dense | Float| Scrap Rate| Allergic Substance | Time Sequence | Complexity": "顔色深淺度 | 濃淡 | 浮沉 | 損耗率 | 過敏原 | 時間順序 | 複雜度",
"Item Code": "物料編號",
"Item Name": "物料名稱",
"Enter the number of cartons: ": "請輸入箱數:",


Loading…
取消
儲存