Pārlūkot izejas kodu

update

MergeProblem1
CANCERYS\kw093 pirms 18 stundām
vecāks
revīzija
eabbc39c57
9 mainītis faili ar 91 papildinājumiem un 31 dzēšanām
  1. +2
    -2
      src/app/(main)/ps/page.tsx
  2. +5
    -2
      src/components/PoDetail/PoDetail.tsx
  3. +16
    -8
      src/components/PoDetail/PoInputGrid.tsx
  4. +2
    -2
      src/components/PoDetail/PutAwayForm.tsx
  5. +15
    -10
      src/components/PoSearch/PoSearch.tsx
  6. +30
    -4
      src/components/ProductionProcess/ProductionProcessList.tsx
  7. +14
    -1
      src/components/ProductionProcess/ProductionProcessPage.tsx
  8. +4
    -2
      src/components/SearchBox/SearchBox.tsx
  9. +3
    -0
      src/i18n/zh/common.json

+ 2
- 2
src/app/(main)/ps/page.tsx Parādīt failu

@@ -210,8 +210,8 @@ export default function ProductionSchedulePage() {
} }
}; };


const fromDateDefault = dayjs().subtract(29, "day").format("YYYY-MM-DD");
const toDateDefault = dayjs().format("YYYY-MM-DD");
const fromDateDefault = dayjs().subtract(6, "day").format("YYYY-MM-DD");
const toDateDefault = dayjs().add(1, "day").format("YYYY-MM-DD");


const fetchItemDailyOut = async (force: boolean = false) => { const fetchItemDailyOut = async (force: boolean = false) => {
// Avoid starting a new fetch while an import is in progress, // Avoid starting a new fetch while an import is in progress,


+ 5
- 2
src/components/PoDetail/PoDetail.tsx Parādīt failu

@@ -416,14 +416,17 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => {
}, []); }, []);


useEffect(() => { useEffect(() => {
if (processedQty === row.qty) {
// `processedQty` comes from putAwayLines (stock unit).
// After the fix, `row.qty` is qtyM18 (M18 unit), so compare using stockUom demand.
const targetStockQty = Number(row.stockUom?.stockQty ?? row.qty ?? 0);
if (targetStockQty > 0 && processedQty >= targetStockQty) {
setCurrStatus("completed".toUpperCase()); setCurrStatus("completed".toUpperCase());
} else if (processedQty > 0) { } else if (processedQty > 0) {
setCurrStatus("receiving".toUpperCase()); setCurrStatus("receiving".toUpperCase());
} else { } else {
setCurrStatus("pending".toUpperCase()); setCurrStatus("pending".toUpperCase());
} }
}, [processedQty, row.qty]);
}, [processedQty, row.qty, row.stockUom?.stockQty]);


const handleRowSelect = () => { const handleRowSelect = () => {
// setSelectedRowId(row.id); // setSelectedRowId(row.id);


+ 16
- 8
src/components/PoDetail/PoInputGrid.tsx Parādīt failu

@@ -153,7 +153,8 @@ function PoInputGrid({
const [btnIsLoading, setBtnIsLoading] = useState(false); const [btnIsLoading, setBtnIsLoading] = useState(false);
const [currQty, setCurrQty] = useState(() => { const [currQty, setCurrQty] = useState(() => {
const total = entries.reduce( const total = entries.reduce(
(acc, curr) => acc + (curr.acceptedQty || 0),
// remaining qty (M18 unit)
(acc, curr) => acc + (curr.purchaseAcceptedQty || 0),
0, 0,
); );
return total; return total;
@@ -231,7 +232,8 @@ function PoInputGrid({
itemName: params.row.itemName, itemName: params.row.itemName,
// purchaseOrderId: params.row.purchaseOrderId, // purchaseOrderId: params.row.purchaseOrderId,
purchaseOrderLineId: params.row.purchaseOrderLineId, purchaseOrderLineId: params.row.purchaseOrderLineId,
acceptedQty: params.row.acceptedQty,
// For PO-origin, backend expects M18 qty and converts it to stock qty.
acceptedQty: params.row.purchaseAcceptedQty ?? params.row.acceptedQty,
}; };
const res = await createStockInLine(postData); const res = await createStockInLine(postData);
console.log(res); console.log(res);
@@ -516,7 +518,7 @@ function PoInputGrid({
// // flex: 0.6, // // flex: 0.6,
// }, // },
{ {
field: "acceptedQty",
field: "purchaseAcceptedQty",
headerName: t("acceptedQty"), headerName: t("acceptedQty"),
// flex: 0.5, // flex: 0.5,
width: 125, width: 125,
@@ -524,7 +526,7 @@ function PoInputGrid({
// editable: true, // editable: true,
// replace with tooltip + content // replace with tooltip + content
renderCell: (params) => { renderCell: (params) => {
const qty = params.row.purchaseAcceptedQty ?? params.row.acceptedQty ?? 0;
const qty = params.row.purchaseAcceptedQty ?? 0;
return integerFormatter.format(qty); return integerFormatter.format(qty);
} }
}, },
@@ -818,7 +820,8 @@ function PoInputGrid({
purchaseOrderLineId: itemDetail.id, purchaseOrderLineId: itemDetail.id,
itemNo: itemDetail.itemNo, itemNo: itemDetail.itemNo,
itemName: itemDetail.itemName, itemName: itemDetail.itemName,
acceptedQty: itemDetail.qty - currQty, // this bug
// User inputs qty in M18 unit; backend will convert to stock unit on create.
purchaseAcceptedQty: itemDetail.qty - currQty,
uom: itemDetail.uom, uom: itemDetail.uom,
status: "draft", status: "draft",
}; };
@@ -840,8 +843,13 @@ function PoInputGrid({
const error: StockInLineEntryError = {}; const error: StockInLineEntryError = {};
console.log(newRow); console.log(newRow);
console.log(currQty); console.log(currQty);
if (newRow.acceptedQty && newRow.acceptedQty > itemDetail.qty) {
error["acceptedQty"] = t("qty cannot be greater than remaining qty");
if (
newRow.purchaseAcceptedQty &&
newRow.purchaseAcceptedQty > itemDetail.qty
) {
error["purchaseAcceptedQty"] = t(
"qty cannot be greater than remaining qty",
);
} }
return Object.keys(error).length > 0 ? error : undefined; return Object.keys(error).length > 0 ? error : undefined;
}, },
@@ -872,7 +880,7 @@ function PoInputGrid({
setEntries(newEntries); setEntries(newEntries);
//update remaining qty //update remaining qty
const total = newEntries.reduce( const total = newEntries.reduce(
(acc, curr) => acc + (curr.acceptedQty || 0),
(acc, curr) => acc + (curr.purchaseAcceptedQty || 0),
0, 0,
); );
setCurrQty(total); setCurrQty(total);


+ 2
- 2
src/components/PoDetail/PutAwayForm.tsx Parādīt failu

@@ -395,7 +395,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse=[], disabled, sugg
<TextField <TextField
label={t("acceptedPutawayQty")} // TODO: fix it back to acceptedQty after db is fixed label={t("acceptedPutawayQty")} // TODO: fix it back to acceptedQty after db is fixed
fullWidth fullWidth
value={itemDetail.acceptedQty ?? itemDetail.demandQty}
value={itemDetail.qty ?? itemDetail.purchaseAcceptedQty ?? itemDetail.acceptedQty ?? itemDetail.demandQty}
disabled disabled
/> />
</Grid> </Grid>
@@ -403,7 +403,7 @@ const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse=[], disabled, sugg
<TextField <TextField
label={t("uom")} label={t("uom")}
fullWidth fullWidth
value={itemDetail.uom?.udfudesc}
value={itemDetail.purchaseUomDesc ?? itemDetail.uom?.udfudesc}
disabled disabled
/> />
</Grid> </Grid>


+ 15
- 10
src/components/PoSearch/PoSearch.tsx Parādīt failu

@@ -13,7 +13,7 @@ import { WarehouseResult } from "@/app/api/warehouse";
import NotificationIcon from "@mui/icons-material/NotificationImportant"; import NotificationIcon from "@mui/icons-material/NotificationImportant";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { defaultPagingController } from "../SearchResults/SearchResults"; import { defaultPagingController } from "../SearchResults/SearchResults";
import { fetchPoListClient, testing } from "@/app/api/po/actions";
import { testing } from "@/app/api/po/actions";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { arrayToDateString, dayjsToDateString } from "@/app/utils/formatUtil"; import { arrayToDateString, dayjsToDateString } from "@/app/utils/formatUtil";
import arraySupport from "dayjs/plugin/arraySupport"; import arraySupport from "dayjs/plugin/arraySupport";
@@ -289,7 +289,20 @@ const PoSearch: React.FC<Props> = ({
}; };
setAutoSyncStatus(null); setAutoSyncStatus(null);


const res = await fetchPoListClient(params);
const cleanedQuery: Record<string, string> = {};
Object.entries(params).forEach(([k, v]) => {
if (v === undefined || v === null) return;
if (typeof v === "string" && (v as string).trim() === "") return;
cleanedQuery[k] = String(v);
});
const baseListResp = await clientAuthFetch(
`${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams(cleanedQuery).toString()}`,
{ method: "GET" },
);
if (!baseListResp.ok) {
throw new Error(`PO list fetch failed: ${baseListResp.status}`);
}
const res = await baseListResp.json();
if (!res) return; if (!res) return;


if (res.records && res.records.length > 0) { if (res.records && res.records.length > 0) {
@@ -340,14 +353,6 @@ const PoSearch: React.FC<Props> = ({
if (syncOk) { if (syncOk) {
setAutoSyncStatus("成功找到PO"); setAutoSyncStatus("成功找到PO");


// Re-fetch /po/list directly from client to avoid cached server action results.
const cleanedQuery: Record<string, string> = {};
Object.entries(params).forEach(([k, v]) => {
if (v === undefined || v === null) return;
if (typeof v === "string" && v.trim() === "") return;
cleanedQuery[k] = String(v);
});

const listResp = await clientAuthFetch( const listResp = await clientAuthFetch(
`${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams( `${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams(
cleanedQuery, cleanedQuery,


+ 30
- 4
src/components/ProductionProcess/ProductionProcessList.tsx Parādīt failu

@@ -32,7 +32,14 @@ import { SessionWithTokens } from "@/config/authConfig";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox"; import SearchBox, { Criterion } from "@/components/SearchBox/SearchBox";

export type ProductionProcessListPersistedState = {
date: string;
itemCode: string | null;
jobOrderCode: string | null;
filter: "all" | "drink" | "other";
page: number;
selectedItemCodes: string[];
};


import { import {
AllJoborderProductProcessInfoResponse, AllJoborderProductProcessInfoResponse,
@@ -50,8 +57,21 @@ interface ProductProcessListProps {
onSelectMatchingStock: (jobOrderId: number|undefined, productProcessId: number|undefined,pickOrderId: number|undefined) => void; onSelectMatchingStock: (jobOrderId: number|undefined, productProcessId: number|undefined,pickOrderId: number|undefined) => void;
printerCombo: PrinterCombo[]; printerCombo: PrinterCombo[];
qcReady: boolean; qcReady: boolean;
listPersistedState: ProductionProcessListPersistedState;
onListPersistedStateChange: React.Dispatch<
React.SetStateAction<ProductionProcessListPersistedState>
>;
}
export function createDefaultProductionProcessListPersistedState(): ProductionProcessListPersistedState {
return {
date: dayjs().format("YYYY-MM-DD"),
itemCode: null,
jobOrderCode: null,
filter: "all",
page: 0,
selectedItemCodes: [],
};
} }

type SearchParam = "date" | "itemCode" | "jobOrderCode" | "processType"; type SearchParam = "date" | "itemCode" | "jobOrderCode" | "processType";


const PAGE_SIZE = 50; const PAGE_SIZE = 50;
@@ -377,8 +397,14 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
} }
/> />
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{t("Total job orders")}: {totalJobOrders} {selectedItemCodes.length > 0 ? `| ${t("Filtered")}: ${paged.length}` : ""}
</Typography>
{t("Search date") /* 或在 zh/common.json 加鍵,例如「搜尋日期」 */}:{" "}
{appliedSearch.date && dayjs(appliedSearch.date).isValid()
? dayjs(appliedSearch.date).format(OUTPUT_DATE_FORMAT)
: "-"}
{" | "}
{t("Total job orders")}: {totalJobOrders}
{selectedItemCodes.length > 0 ? ` | ${t("Filtered")}: ${paged.length}` : ""}
</Typography>


<Grid container spacing={2}> <Grid container spacing={2}>
{paged.map((process) => { {paged.map((process) => {


+ 14
- 1
src/components/ProductionProcess/ProductionProcessPage.tsx Parādīt failu

@@ -3,7 +3,9 @@ import React, { useState, useEffect, useCallback } from "react";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig"; import { SessionWithTokens } from "@/config/authConfig";
import { Box, Tabs, Tab, Stack, Typography, Autocomplete, TextField } from "@mui/material"; import { Box, Tabs, Tab, Stack, Typography, Autocomplete, TextField } from "@mui/material";
import ProductionProcessList from "@/components/ProductionProcess/ProductionProcessList";
import ProductionProcessList, {
createDefaultProductionProcessListPersistedState,
} from "@/components/ProductionProcess/ProductionProcessList";
import ProductionProcessDetail from "@/components/ProductionProcess/ProductionProcessDetail"; import ProductionProcessDetail from "@/components/ProductionProcess/ProductionProcessDetail";
import ProductionProcessJobOrderDetail from "@/components/ProductionProcess/ProductionProcessJobOrderDetail"; import ProductionProcessJobOrderDetail from "@/components/ProductionProcess/ProductionProcessJobOrderDetail";
import JobPickExecutionsecondscan from "@/components/Jodetail/JobPickExecutionsecondscan"; import JobPickExecutionsecondscan from "@/components/Jodetail/JobPickExecutionsecondscan";
@@ -43,6 +45,13 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo
pickOrderId: number; pickOrderId: number;
} | null>(null); } | null>(null);
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
/** 列表搜尋/分頁:保留在切換工單詳情時,返回後仍為同一條件 */
const [productionListState, setProductionListState] = useState(
createDefaultProductionProcessListPersistedState,
);
const [finishedQcListState, setFinishedQcListState] = useState(
createDefaultProductionProcessListPersistedState,
);
const { data: session } = useSession() as { data: SessionWithTokens | null }; const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined; const currentUserId = session?.id ? parseInt(session.id) : undefined;


@@ -180,6 +189,8 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo
<ProductionProcessList <ProductionProcessList
printerCombo={printerCombo} printerCombo={printerCombo}
qcReady={false} qcReady={false}
listPersistedState={productionListState}
onListPersistedStateChange={setProductionListState}
onSelectProcess={(jobOrderId) => { onSelectProcess={(jobOrderId) => {
const id = jobOrderId ?? null; const id = jobOrderId ?? null;
if (id !== null) { if (id !== null) {
@@ -200,6 +211,8 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo
<ProductionProcessList <ProductionProcessList
printerCombo={printerCombo} printerCombo={printerCombo}
qcReady={true} qcReady={true}
listPersistedState={finishedQcListState}
onListPersistedStateChange={setFinishedQcListState}
onSelectProcess={(jobOrderId) => { onSelectProcess={(jobOrderId) => {
const id = jobOrderId ?? null; const id = jobOrderId ?? null;
if (id !== null) { if (id !== null) {


+ 4
- 2
src/components/SearchBox/SearchBox.tsx Parādīt failu

@@ -40,6 +40,8 @@ interface BaseCriterion<T extends string> {
paramName2?: T; paramName2?: T;
// options?: T[] | string[]; // options?: T[] | string[];
defaultValue?: string; defaultValue?: string;
/** 與 `defaultValue` 配對,用於 dateRange / datetimeRange 重置時的結束值 */
defaultValueTo?: string;
preFilledValue?: string | { from?: string; to?: string }; preFilledValue?: string | { from?: string; to?: string };
filterObj?: T; filterObj?: T;
handleSelectionChange?: (selectedOptions: T[]) => void; handleSelectionChange?: (selectedOptions: T[]) => void;
@@ -159,7 +161,7 @@ function SearchBox<T extends string>({
tempCriteria = { tempCriteria = {
...tempCriteria, ...tempCriteria,
[c.paramName]: c.defaultValue ?? "", [c.paramName]: c.defaultValue ?? "",
[`${c.paramName}To`]: "",
[`${c.paramName}To`]: c.defaultValueTo ?? "",
}; };
} }
return tempCriteria; return tempCriteria;
@@ -188,7 +190,7 @@ function SearchBox<T extends string>({
{} as Record<T | `${T}To`, string>, {} as Record<T | `${T}To`, string>,
); );
return {...defaultInputs, ...preFilledCriteria} return {...defaultInputs, ...preFilledCriteria}
}, [defaultInputs])
}, [defaultInputs, criteria])


const [inputs, setInputs] = useState(preFilledInputs); const [inputs, setInputs] = useState(preFilledInputs);
const [isReset, setIsReset] = useState(false); const [isReset, setIsReset] = useState(false);


+ 3
- 0
src/i18n/zh/common.json Parādīt failu

@@ -17,7 +17,10 @@
"Process & Equipment": "製程與設備", "Process & Equipment": "製程與設備",
"Sequence": "順序", "Sequence": "順序",
"Process Name": "製程名稱", "Process Name": "製程名稱",
"Plan start (from)": "開始日期(從)",
"Plan start (to)": "開始日期(至)",
"Process Description": "說明", "Process Description": "說明",
"Search date": "搜索日期",
"Confirm to Pass this Process?": "確認要通過此工序嗎?", "Confirm to Pass this Process?": "確認要通過此工序嗎?",
"Equipment Name": "設備", "Equipment Name": "設備",
"Confirm to update this Job Order?": "確認要完成此工單嗎?", "Confirm to update this Job Order?": "確認要完成此工單嗎?",


Notiek ielāde…
Atcelt
Saglabāt