浏览代码

update putaway

master
cyril.tsui 9 小时前
父节点
当前提交
37ed4d31aa
共有 18 个文件被更改,包括 244 次插入69 次删除
  1. +2
    -0
      src/app/(main)/po/edit/page.tsx
  2. +28
    -6
      src/app/api/po/actions.ts
  3. +2
    -0
      src/app/api/po/index.ts
  4. +21
    -0
      src/app/api/settings/printer/index.ts
  5. +12
    -0
      src/app/api/warehouse/index.ts
  6. +12
    -11
      src/components/InputDataGrid/InputDataGrid.tsx
  7. +7
    -7
      src/components/PickOrderSearch/PutawayForm.tsx
  8. +2
    -2
      src/components/PickOrderSearch/dummyQcTemplate.tsx
  9. +1
    -1
      src/components/PickOrderSearch/pickorderModelVer2.tsx
  10. +4
    -1
      src/components/PoDetail/PoDetail.tsx
  11. +5
    -2
      src/components/PoDetail/PoDetailWrapper.tsx
  12. +6
    -2
      src/components/PoDetail/PoInputGrid.tsx
  13. +2
    -2
      src/components/PoDetail/PoQcStockInModal.tsx
  14. +50
    -11
      src/components/PoDetail/PutawayForm.tsx
  15. +82
    -18
      src/components/PoDetail/QcStockInModalVer2.tsx
  16. +2
    -2
      src/components/PoDetail/QrModal.tsx
  17. +3
    -3
      src/components/PoDetail/dummyQcTemplate.tsx
  18. +3
    -1
      src/i18n/zh/purchaseOrder.json

+ 2
- 0
src/app/(main)/po/edit/page.tsx 查看文件

@@ -1,3 +1,4 @@
import { fetchPrinterCombo } from "@/app/api/settings/printer";
import { fetchEscalationCombo } from "@/app/api/user"; import { fetchEscalationCombo } from "@/app/api/user";
import { SearchParams } from "@/app/utils/fetchUtil"; import { SearchParams } from "@/app/utils/fetchUtil";
import { TypeEnum } from "@/app/utils/typeEnum"; import { TypeEnum } from "@/app/utils/typeEnum";
@@ -25,6 +26,7 @@ const PoEdit: React.FC<Props> = async ({ searchParams }) => {
} }


fetchEscalationCombo() fetchEscalationCombo()
fetchPrinterCombo()


return ( return (
<> <>


+ 28
- 6
src/app/api/po/actions.ts 查看文件

@@ -6,10 +6,11 @@ import { revalidateTag } from "next/cache";
import { cache } from "react"; import { cache } from "react";
import { PoResult, StockInLine } from "."; import { PoResult, StockInLine } from ".";
//import { serverFetchJson } from "@/app/utils/fetchUtil"; //import { serverFetchJson } from "@/app/utils/fetchUtil";
import { serverFetchJson } from "../../utils/fetchUtil";
import { serverFetchJson, serverFetchWithNoContent } from "../../utils/fetchUtil";
import { QcItemResult } from "../settings/qcItem"; import { QcItemResult } from "../settings/qcItem";
import { RecordsRes } from "../utils"; import { RecordsRes } from "../utils";
import { Uom } from "../settings/uom"; import { Uom } from "../settings/uom";
import { convertObjToURLSearchParams } from "@/app/utils/commonUtil";
// import { BASE_API_URL } from "@/config/api"; // import { BASE_API_URL } from "@/config/api";


export interface PostStockInLineResponse<T> { export interface PostStockInLineResponse<T> {
@@ -81,26 +82,34 @@ export interface EscalationInput {
acceptedQty?: number; // this is the qty to be escalated acceptedQty?: number; // this is the qty to be escalated
// escalationQty: number // escalationQty: number
} }
export interface PutawayLine {
export interface PutAwayLine {
id?: number id?: number
qty: number qty: number
warehouseId: number; warehouseId: number;
warehouse: string; warehouse: string;
printQty: number
printQty: number;
_isNew?: boolean;
printQty?: number;
} }
export interface PutawayInput {
export interface PutAwayInput {
status: string; status: string;
acceptedQty: number; acceptedQty: number;
warehouseId: number; warehouseId: number;
putawayLine: PutawayLine[]
putAwayLines: PutAwayLine[]
} }


export type ModalFormInput = Partial< export type ModalFormInput = Partial<
PurchaseQCInput & StockInInput & PutawayInput
PurchaseQCInput & StockInInput & PutAwayInput
> & { > & {
escalationLog? : Partial<EscalationInput> escalationLog? : Partial<EscalationInput>
}; };


export interface PrintQrCodeForSilRequest {
stockInLineId: number;
printerId: number;
printQty?: number;
}

export const testFetch = cache(async (id: number) => { export const testFetch = cache(async (id: number) => {
return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, {
next: { tags: ["po"] }, next: { tags: ["po"] },
@@ -217,3 +226,16 @@ export const testing = cache(async (queryParams?: Record<string, any>) => {
); );
} }
}); });

export const printQrCodeForSil = cache(async(data: PrintQrCodeForSilRequest) => {
const params = convertObjToURLSearchParams(data)
return serverFetchWithNoContent(`${BASE_API_URL}/stockInLine/printQrCode?${params}`,
{
method: "GET",
headers: { "Content-Type": "application/json" },
next: {
tags: ["printQrCodeForSil"],
},
},
)
})

+ 2
- 0
src/app/api/po/index.ts 查看文件

@@ -6,6 +6,7 @@ import { serverFetchJson } from "../../utils/fetchUtil";
import { BASE_API_URL } from "../../../config/api"; import { BASE_API_URL } from "../../../config/api";
import { Uom } from "../settings/uom"; import { Uom } from "../settings/uom";
import { RecordsRes } from "../utils"; import { RecordsRes } from "../utils";
import { PutAwayLine } from "./actions";


export interface PoResult { export interface PoResult {
id: number; id: number;
@@ -82,6 +83,7 @@ export interface StockInLine {
dnDate?: number[]; dnDate?: number[];
stockQty?: number; stockQty?: number;
handlerId?: number; handlerId?: number;
putAwayLines?: PutAwayLine[];
} }


export const fetchPoList = cache(async (queryParams?: Record<string, any>) => { export const fetchPoList = cache(async (queryParams?: Record<string, any>) => {


+ 21
- 0
src/app/api/settings/printer/index.ts 查看文件

@@ -0,0 +1,21 @@
import { serverFetchJson } from '@/app/utils/fetchUtil';
import { BASE_API_URL } from '@/config/api';
import { cache } from "react";
import "server-only";

export interface PrinterCombo {
id: number;
value: number;
label?: string;
code?: string;
name?: string;
description?: string;
ip?: string;
port?: number;
}

export const fetchPrinterCombo = cache(async () => {
return serverFetchJson<PrinterCombo[]>(`${BASE_API_URL}/printers/combo`, {
next: { tags: ["qcItems"] },
})
})

+ 12
- 0
src/app/api/warehouse/index.ts 查看文件

@@ -10,8 +10,20 @@ export interface WarehouseResult {
description: string; description: string;
} }


export interface WarehouseCombo {
id: number;
value: number;
label: string;
}

export const fetchWarehouseList = cache(async () => { export const fetchWarehouseList = cache(async () => {
return serverFetchJson<WarehouseResult[]>(`${BASE_API_URL}/warehouse`, { return serverFetchJson<WarehouseResult[]>(`${BASE_API_URL}/warehouse`, {
next: { tags: ["warehouse"] }, next: { tags: ["warehouse"] },
}); });
}); });

export const fetchWarehouseCombo = cache(async () => {
return serverFetchJson<WarehouseCombo[]>(`${BASE_API_URL}/warehouse/combo`, {
next: { tags: ["warehouseCombo"] },
});
});

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

@@ -74,6 +74,7 @@ export interface InputDataGridProps<T, V, E> {
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E;
needAdd?: boolean; needAdd?: boolean;
showRemoveBtn?: boolean; showRemoveBtn?: boolean;
addRowDefaultValue?: Partial<V>;
} }


export interface SelectionInputDataGridProps<T, V, E> { export interface SelectionInputDataGridProps<T, V, E> {
@@ -85,6 +86,7 @@ export interface SelectionInputDataGridProps<T, V, E> {
validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E;
needAdd?: boolean; needAdd?: boolean;
showRemoveBtn?: boolean; showRemoveBtn?: boolean;
addRowDefaultValue?: Partial<V>;
} }


export type Props<T, V, E> = export type Props<T, V, E> =
@@ -112,6 +114,7 @@ function InputDataGrid<T, V, E>({
validateRow, validateRow,
needAdd, needAdd,
showRemoveBtn = true, showRemoveBtn = true,
addRowDefaultValue = {},
}: Props<T, V, E>) { }: Props<T, V, E>) {
const { const {
t, t,
@@ -126,13 +129,13 @@ function InputDataGrid<T, V, E>({
[], [],
); );
const list: TableRow<V, E>[] = getValues(formKey); const list: TableRow<V, E>[] = getValues(formKey);
console.log(list)
// console.log(list)
const [rows, setRows] = useState<TableRow<V, E>[]>(() => { const [rows, setRows] = useState<TableRow<V, E>[]>(() => {
const list: TableRow<V, E>[] = getValues(formKey); const list: TableRow<V, E>[] = getValues(formKey);
console.log(list)
// console.log(list)
return list && list.length > 0 ? list : []; return list && list.length > 0 ? list : [];
}); });
console.log(rows)
// console.log(rows)
// const originalRows = list && list.length > 0 ? list : []; // const originalRows = list && list.length > 0 ? list : [];
const originalRows = useMemo(() => ( const originalRows = useMemo(() => (
list && list.length > 0 ? list : [] list && list.length > 0 ? list : []
@@ -145,7 +148,7 @@ function InputDataGrid<T, V, E>({
const rowModel: GridRowSelectionModel = getValues( const rowModel: GridRowSelectionModel = getValues(
`${formKey}_active`, `${formKey}_active`,
) as GridRowSelectionModel; ) as GridRowSelectionModel;
console.log(rowModel);
// console.log(rowModel);
return rowModel; return rowModel;
}); });


@@ -162,7 +165,7 @@ function InputDataGrid<T, V, E>({
(updateError: ProcessRowUpdateError<T, E>) => { (updateError: ProcessRowUpdateError<T, E>) => {
const errors = updateError.errors; const errors = updateError.errors;
const row = updateError.row; const row = updateError.row;
console.log(errors);
// console.log(errors);
apiRef.current.updateRows([{ ...row, _error: errors }]); apiRef.current.updateRows([{ ...row, _error: errors }]);
}, },
[apiRef], [apiRef],
@@ -176,7 +179,7 @@ function InputDataGrid<T, V, E>({
///////////////// /////////////////
// validation here // validation here
const errors = validateRow(newRow); const errors = validateRow(newRow);
console.log(newRow);
// console.log(newRow);
if (errors) { if (errors) {
throw new ProcessRowUpdateError( throw new ProcessRowUpdateError(
originalRow, originalRow,
@@ -189,7 +192,6 @@ function InputDataGrid<T, V, E>({
const rowToSave = { const rowToSave = {
...updatedRow, ...updatedRow,
} as TableRow<V, E>; /// test } as TableRow<V, E>; /// test
console.log(rowToSave);
setRows((rw) => setRows((rw) =>
rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)), rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)),
); );
@@ -198,8 +200,8 @@ function InputDataGrid<T, V, E>({
[validateRow, getRowId], [validateRow, getRowId],
); );


const addRow = useCallback(() => {
const newEntry = { id: Date.now(), _isNew: true } as TableRow<V, E>;
const addRow = useCallback((addRowDefaultValue: Partial<V>) => {
const newEntry = { ...addRowDefaultValue, id: Date.now(), _isNew: true } as TableRow<V, E>;
setRows((prev) => [...prev, newEntry]); setRows((prev) => [...prev, newEntry]);
setRowModesModel((model) => ({ setRowModesModel((model) => ({
...model, ...model,
@@ -290,7 +292,6 @@ function InputDataGrid<T, V, E>({
// sync useForm // sync useForm
useEffect(() => { useEffect(() => {
// console.log(formKey) // console.log(formKey)
// console.log(rows)
setValue(formKey, rows); setValue(formKey, rows);
}, [formKey, rows, setValue]); }, [formKey, rows, setValue]);


@@ -300,7 +301,7 @@ function InputDataGrid<T, V, E>({
disableRipple disableRipple
variant="outlined" variant="outlined"
startIcon={<Add />} startIcon={<Add />}
onClick={addRow}
onClick={() => addRow(addRowDefaultValue)}
size="small" size="small"
> >
新增 新增


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

@@ -1,6 +1,6 @@
"use client"; "use client";


import { PurchaseQcResult, PutawayInput, PutawayLine } from "@/app/api/po/actions";
import { PurchaseQcResult, PutAwayInput, PutAwayLine } from "@/app/api/po/actions";
import { import {
Autocomplete, Autocomplete,
Box, Box,
@@ -61,11 +61,11 @@ interface Props {
} }
type EntryError = type EntryError =
| { | {
[field in keyof PutawayLine]?: string;
[field in keyof PutAwayLine]?: string;
} }
| undefined; | undefined;


type PutawayRow = TableRow<Partial<PutawayLine>, EntryError>;
type PutawayRow = TableRow<Partial<PutAwayLine>, EntryError>;


const style = { const style = {
position: "absolute", position: "absolute",
@@ -93,7 +93,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
resetField, resetField,
setError, setError,
clearErrors, clearErrors,
} = useFormContext<PutawayInput>();
} = useFormContext<PutAwayInput>();
console.log(itemDetail); console.log(itemDetail);
// const [recordQty, setRecordQty] = useState(0); // const [recordQty, setRecordQty] = useState(0);
const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId); const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId);
@@ -143,7 +143,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
}, },
[], [],
); );
console.log(watch("putawayLine"))
console.log(watch("putAwayLines"))
// const accQty = watch("acceptedQty"); // const accQty = watch("acceptedQty");
// const validateForm = useCallback(() => { // const validateForm = useCallback(() => {
// console.log(accQty); // console.log(accQty);
@@ -492,10 +492,10 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
style={{ display: "flex", justifyContent: "center" }} style={{ display: "flex", justifyContent: "center" }}
> >
{/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */} {/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */}
<InputDataGrid<PutawayInput, PutawayLine, EntryError>
<InputDataGrid<PutAwayInput, PutAwayLine, EntryError>
apiRef={apiRef} apiRef={apiRef}
checkboxSelection={false} checkboxSelection={false}
_formKey={"putawayLine"}
_formKey={"putAwayLines"}
columns={columns} columns={columns}
validateRow={validation} validateRow={validation}
needAdd={true} needAdd={true}


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

@@ -1,4 +1,4 @@
import { PutawayLine } from "@/app/api/po/actions"
import { PutAwayLine } from "@/app/api/po/actions"


export interface QcData { export interface QcData {
id: number, id: number,
@@ -67,7 +67,7 @@ export const dummyEscalationHistory: EscalationData[] = [
}, },
] ]


export const dummyPutawayLine: PutawayLine[] = [
export const dummyPutawayLine: PutAwayLine[] = [
{ {
id: 1, id: 1,
qty: 100, qty: 100,


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

@@ -80,7 +80,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
const formProps = useForm<any>({ const formProps = useForm<any>({
defaultValues: { defaultValues: {
...itemDetail, ...itemDetail,
putawayLine: dummyPutawayLine,
putAwayLine: dummyPutawayLine,
// receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), // receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT),
// warehouseId: itemDetail.defaultWarehouseId || 0 // warehouseId: itemDetail.defaultWarehouseId || 0
}, },


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

@@ -79,6 +79,7 @@ import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers";
import { debounce } from "lodash"; import { debounce } from "lodash";
import LoadingComponent from "../General/LoadingComponent"; import LoadingComponent from "../General/LoadingComponent";
import { getMailTemplateForStockInLine } from "@/app/api/mailTemplate/actions"; import { getMailTemplateForStockInLine } from "@/app/api/mailTemplate/actions";
import { PrinterCombo } from "@/app/api/settings/printer";
//import { useRouter } from "next/navigation"; //import { useRouter } from "next/navigation";




@@ -86,6 +87,7 @@ type Props = {
po: PoResult; po: PoResult;
qc: QcItemWithChecks[]; qc: QcItemWithChecks[];
warehouse: WarehouseResult[]; warehouse: WarehouseResult[];
printerCombo: PrinterCombo[];
}; };


type EntryError = type EntryError =
@@ -188,7 +190,7 @@ interface PolInputResult {
dnQty: number, dnQty: number,
} }


const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => {
const cameras = useContext(CameraContext); const cameras = useContext(CameraContext);
// console.log(cameras); // console.log(cameras);
const { t } = useTranslation("purchaseOrder"); const { t } = useTranslation("purchaseOrder");
@@ -858,6 +860,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
warehouse={warehouse} warehouse={warehouse}
fetchPoDetail={fetchPoDetail} fetchPoDetail={fetchPoDetail}
handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} handleMailTemplateForStockInLine={handleMailTemplateForStockInLine}
printerCombo={printerCombo}
/> />
</Box> </Box>
</TableCell> </TableCell>


+ 5
- 2
src/components/PoDetail/PoDetailWrapper.tsx 查看文件

@@ -11,6 +11,7 @@ import { QcItemWithChecks } from "@/app/api/qc";
import { fetchWarehouseList } from "@/app/api/warehouse"; import { fetchWarehouseList } from "@/app/api/warehouse";
import { fetchQcItemCheck } from "@/app/api/qc/actions"; import { fetchQcItemCheck } from "@/app/api/qc/actions";
import { fetchEscalationCombo } from "@/app/api/user"; import { fetchEscalationCombo } from "@/app/api/user";
import { fetchPrinterCombo } from "@/app/api/settings/printer";


interface SubComponents { interface SubComponents {
Loading: typeof PoDetailLoading; Loading: typeof PoDetailLoading;
@@ -25,16 +26,18 @@ const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => {
poWithStockInLine, poWithStockInLine,
warehouse, warehouse,
qc, qc,
escalationCombo
escalationCombo,
printerCombo,
] = await Promise.all([ ] = await Promise.all([
fetchPoWithStockInLines(id), fetchPoWithStockInLines(id),
fetchWarehouseList(), fetchWarehouseList(),
fetchQcItemCheck(), fetchQcItemCheck(),
fetchEscalationCombo(), fetchEscalationCombo(),
fetchPrinterCombo()
]); ]);
// const poWithStockInLine = await fetchPoWithStockInLines(id) // const poWithStockInLine = await fetchPoWithStockInLines(id)


return <PoDetail po={poWithStockInLine} qc={qc} warehouse={warehouse} />;
return <PoDetail po={poWithStockInLine} qc={qc} warehouse={warehouse} printerCombo={printerCombo} />;
}; };


PoDetailWrapper.Loading = PoDetailLoading; PoDetailWrapper.Loading = PoDetailLoading;


+ 6
- 2
src/components/PoDetail/PoInputGrid.tsx 查看文件

@@ -62,6 +62,7 @@ import { useSession } from "next-auth/react";
// import { SessionWithTokens } from "src/config/authConfig"; // import { SessionWithTokens } from "src/config/authConfig";
import PoQcStockInModalVer2 from "./QcStockInModalVer2"; import PoQcStockInModalVer2 from "./QcStockInModalVer2";
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
import { PrinterCombo } from "@/app/api/settings/printer";
import { EscalationResult } from "@/app/api/escalation"; import { EscalationResult } from "@/app/api/escalation";
import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions"; import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions";
import { SessionWithTokens } from "@/config/authConfig"; import { SessionWithTokens } from "@/config/authConfig";
@@ -80,6 +81,7 @@ interface Props {
warehouse: WarehouseResult[]; warehouse: WarehouseResult[];
fetchPoDetail: (poId: string) => void; fetchPoDetail: (poId: string) => void;
handleMailTemplateForStockInLine: (stockInLineId: number) => void; handleMailTemplateForStockInLine: (stockInLineId: number) => void;
printerCombo: PrinterCombo[];
} }


export type StockInLineEntryError = { export type StockInLineEntryError = {
@@ -119,7 +121,8 @@ function PoInputGrid({
stockInLine, stockInLine,
warehouse, warehouse,
fetchPoDetail, fetchPoDetail,
handleMailTemplateForStockInLine
handleMailTemplateForStockInLine,
printerCombo
}: Props) { }: Props) {
console.log(itemDetail); console.log(itemDetail);
const { t } = useTranslation("purchaseOrder"); const { t } = useTranslation("purchaseOrder");
@@ -301,7 +304,7 @@ const closeNewModal = useCallback(() => {
const handleNewQC = useCallback( const handleNewQC = useCallback(
(id: GridRowId, params: any) => async() => { (id: GridRowId, params: any) => async() => {
// console.log(id) // console.log(id)
// console.log(params)
console.log("params", params.row)
// setBtnIsLoading(true); // setBtnIsLoading(true);
setRowModesModel((prev) => ({ setRowModesModel((prev) => ({
...prev, ...prev,
@@ -939,6 +942,7 @@ const closeNewModal = useCallback(() => {
onClose={closeNewModal} onClose={closeNewModal}
itemDetail={modalInfo} itemDetail={modalInfo}
handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} handleMailTemplateForStockInLine={handleMailTemplateForStockInLine}
printerCombo={printerCombo}
/> />
</> </>
) )


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

@@ -28,7 +28,7 @@ import { useSearchParams } from "next/navigation";
import { StockInLineRow } from "./PoInputGrid"; import { StockInLineRow } from "./PoInputGrid";
import EscalationForm from "./EscalationForm"; import EscalationForm from "./EscalationForm";
import StockInForm from "./StockInForm"; import StockInForm from "./StockInForm";
import PutawayForm from "./PutawayForm";
import PutAwayForm from "./PutAwayForm";
import { import {
INPUT_DATE_FORMAT, INPUT_DATE_FORMAT,
stockInLineStatusMap, stockInLineStatusMap,
@@ -437,7 +437,7 @@ const PoQcStockInModal: React.FC<Props> = ({
/> />
)} )}
{itemDetail !== undefined && type === "putaway" && ( {itemDetail !== undefined && type === "putaway" && (
<PutawayForm
<PutAwayForm
itemDetail={itemDetail} itemDetail={itemDetail}
warehouse={warehouse!} warehouse={warehouse!}
disabled={!renderSubmitButton} disabled={!renderSubmitButton}


+ 50
- 11
src/components/PoDetail/PutawayForm.tsx 查看文件

@@ -1,6 +1,6 @@
"use client"; "use client";


import { PurchaseQcResult, PutawayInput, PutawayLine } from "@/app/api/po/actions";
import { PurchaseQcResult, PutAwayInput, PutAwayLine } from "@/app/api/po/actions";
import { import {
Autocomplete, Autocomplete,
Box, Box,
@@ -50,7 +50,8 @@ import { QrCodeInfo } from "@/app/api/qrcode";
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import dayjs from "dayjs"; import dayjs from "dayjs";
import arraySupport from "dayjs/plugin/arraySupport"; import arraySupport from "dayjs/plugin/arraySupport";
import { dummyPutawayLine } from "./dummyQcTemplate";
import { dummyPutAwayLine } from "./dummyQcTemplate";
import { PrinterCombo } from "@/app/api/settings/printer";
dayjs.extend(arraySupport); dayjs.extend(arraySupport);


interface Props { interface Props {
@@ -58,14 +59,15 @@ interface Props {
warehouse: WarehouseResult[]; warehouse: WarehouseResult[];
disabled: boolean; disabled: boolean;
// qc: QcItemWithChecks[]; // qc: QcItemWithChecks[];
printerCombo: PrinterCombo[];
} }
type EntryError = type EntryError =
| { | {
[field in keyof PutawayLine]?: string;
[field in keyof PutAwayLine]?: string;
} }
| undefined; | undefined;


type PutawayRow = TableRow<Partial<PutawayLine>, EntryError>;
type PutAwayRow = TableRow<Partial<PutAwayLine>, EntryError>;


const style = { const style = {
position: "absolute", position: "absolute",
@@ -79,7 +81,7 @@ const style = {
width: "auto", width: "auto",
}; };


const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, printerCombo }) => {
const { t } = useTranslation("purchaseOrder"); const { t } = useTranslation("purchaseOrder");
const apiRef = useGridApiRef(); const apiRef = useGridApiRef();
const { const {
@@ -93,7 +95,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
resetField, resetField,
setError, setError,
clearErrors, clearErrors,
} = useFormContext<PutawayInput>();
} = useFormContext<PutAwayInput>();
console.log(itemDetail); console.log(itemDetail);
// const [recordQty, setRecordQty] = useState(0); // const [recordQty, setRecordQty] = useState(0);
const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId); const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId);
@@ -143,7 +145,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
}, },
[], [],
); );
console.log(watch("putawayLine"))
console.log(watch("putAwayLines"))
// const accQty = watch("acceptedQty"); // const accQty = watch("acceptedQty");
// const validateForm = useCallback(() => { // const validateForm = useCallback(() => {
// console.log(accQty); // console.log(accQty);
@@ -274,6 +276,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
field: "qty", field: "qty",
headerName: t("qty"), headerName: t("qty"),
flex: 1, flex: 1,
editable: true,
// renderCell(params) { // renderCell(params) {
// return <>100</> // return <>100</>
// }, // },
@@ -282,6 +285,33 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
field: "warehouse", field: "warehouse",
headerName: t("warehouse"), headerName: t("warehouse"),
flex: 1, flex: 1,
editable: true,
renderEditCell: (params) => {
const index = params.api.getRowIndexRelativeToVisibleRows(params.row.id)
// console.log(index)
// console.log(watch(`putAwayLines.${index}.warehouse`))
return <Autocomplete
fullWidth
disableClearable
options={options}
// defaultValue={options.find((o) => o.value === itemDetail.defaultWarehouseId)}
// value={options.find((o) => o.value === watch(`putAwayLines.${index}.warehouseId`))}
defaultValue={options.find((o) => o.value === watch(`putAwayLines.${index}.warehouseId`))}
onChange={(event, value) => {
params.api.setEditCellValue({ id: params.id, field: params.field, value: options.find((o) => o.value === value.value)?.label ?? ""})
params.api.setEditCellValue({ id: params.id, field: "warehouseId", value: value.value})
// setValue(`putAwayLines.${index}.warehouseId`, value.value)
// setValue(`putAwayLines.${index}.warehouse`, options.find((o) => o.value === value.value)?.label ?? "")
}}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
// label={t("Warehouse")}
/>
)}
/>
}
// renderCell(params) { // renderCell(params) {
// return <>{filteredWarehouse[0].name}</> // return <>{filteredWarehouse[0].name}</>
// }, // },
@@ -290,6 +320,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
field: "printQty", field: "printQty",
headerName: t("printQty"), headerName: t("printQty"),
flex: 1, flex: 1,
editable: true,
// renderCell(params) { // renderCell(params) {
// return <>100</> // return <>100</>
// }, // },
@@ -297,7 +328,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
], []) ], [])


const validation = useCallback( const validation = useCallback(
(newRow: GridRowModel<PutawayRow>): EntryError => {
(newRow: GridRowModel<PutAwayRow>): EntryError => {
const error: EntryError = {}; const error: EntryError = {};
const { qty, warehouseId, printQty } = newRow; const { qty, warehouseId, printQty } = newRow;


@@ -306,6 +337,13 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
[], [],
); );


const addRowDefaultValue = useMemo(() => {
const defaultMaxQty = watch("acceptedQty") - watch("putAwayLines").reduce((acc, cur) => acc + cur.qty, 0)
const defaultWarehouseId = itemDetail.defaultWarehouseId ?? 1
const defaultWarehouse = options.find((o) => o.value === defaultWarehouseId)?.label
return {qty: defaultMaxQty, warehouseId: defaultWarehouseId, warehouse: defaultWarehouse, printQty: 1 } as Partial<PutAwayLine>
}, [])

return ( return (
<Grid container justifyContent="flex-start" alignItems="flex-start"> <Grid container justifyContent="flex-start" alignItems="flex-start">
<Grid item xs={12}> <Grid item xs={12}>
@@ -493,14 +531,15 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
style={{ display: "flex", justifyContent: "center" }} style={{ display: "flex", justifyContent: "center" }}
> >
{/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */} {/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */}
<InputDataGrid<PutawayInput, PutawayLine, EntryError>
<InputDataGrid<PutAwayInput, PutAwayLine, EntryError>
apiRef={apiRef} apiRef={apiRef}
checkboxSelection={false} checkboxSelection={false}
_formKey={"putawayLine"}
_formKey={"putAwayLines"}
columns={columns} columns={columns}
validateRow={validation} validateRow={validation}
needAdd={true} needAdd={true}
showRemoveBtn={false} showRemoveBtn={false}
addRowDefaultValue={addRowDefaultValue}
/> />
</Grid> </Grid>
</Grid> </Grid>
@@ -525,4 +564,4 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => {
</Grid> </Grid>
); );
}; };
export default PutawayForm;
export default PutAwayForm;

+ 82
- 18
src/components/PoDetail/QcStockInModalVer2.tsx 查看文件

@@ -1,14 +1,16 @@
"use client"; "use client";
import { StockInLine } from "@/app/api/po"; import { StockInLine } from "@/app/api/po";
import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine, PurchaseQCInput } from "@/app/api/po/actions";
import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine, PurchaseQCInput, printQrCodeForSil, PrintQrCodeForSilRequest } from "@/app/api/po/actions";
import { QcItemWithChecks, QcData } from "@/app/api/qc"; import { QcItemWithChecks, QcData } from "@/app/api/qc";
import { import {
Autocomplete,
Box, Box,
Button, Button,
Grid, Grid,
Modal, Modal,
ModalProps, ModalProps,
Stack, Stack,
TextField,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
@@ -20,10 +22,16 @@ import StockInFormVer2 from "./StockInFormVer2";
import PutawayForm from "./PutawayForm"; import PutawayForm from "./PutawayForm";
import QcComponent from "./QcComponent"; import QcComponent from "./QcComponent";
import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate"; import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate";
import QcFormVer2 from "./QcFormVer2";
import PutAwayForm from "./PutAwayForm";
import { dummyPutAwayLine, dummyQCData } from "./dummyQcTemplate";
import { useGridApiRef } from "@mui/x-data-grid"; import { useGridApiRef } from "@mui/x-data-grid";
import {submitDialogWithWarning} from "../Swal/CustomAlerts"; import {submitDialogWithWarning} from "../Swal/CustomAlerts";
import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString, INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import { INPUT_DATE_FORMAT, arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { fetchPoQrcode } from "@/app/api/pdf/actions";
import { downloadFile } from "@/app/utils/commonUtil";
import { PrinterCombo } from "@/app/api/settings/printer";
import { watch } from "fs"; import { watch } from "fs";
import { EscalationResult } from "@/app/api/escalation"; import { EscalationResult } from "@/app/api/escalation";
import { SessionWithTokens } from "@/config/authConfig"; import { SessionWithTokens } from "@/config/authConfig";
@@ -60,6 +68,7 @@ interface CommonProps extends Omit<ModalProps, "children"> {
warehouse?: any[]; warehouse?: any[];
// type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject";
handleMailTemplateForStockInLine: (stockInLineId: number) => void; handleMailTemplateForStockInLine: (stockInLineId: number) => void;
printerCombo: PrinterCombo[];
onClose: () => void; onClose: () => void;
} }
interface Props extends CommonProps { interface Props extends CommonProps {
@@ -78,19 +87,25 @@ const PoQcStockInModalVer2: React.FC<Props> = ({
qc, qc,
warehouse, warehouse,
handleMailTemplateForStockInLine, handleMailTemplateForStockInLine,
printerCombo
}) => { }) => {
const { const {
t, t,
i18n: { language }, i18n: { language },
} = useTranslation("purchaseOrder"); } = useTranslation("purchaseOrder");


// Select Printer
const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0])

const defaultNewValue = useMemo(() => { const defaultNewValue = useMemo(() => {
return ( return (
{ {
...itemDetail, ...itemDetail,
status: itemDetail.status ?? "pending", status: itemDetail.status ?? "pending",
dnDate: arrayToInputDateString(itemDetail.dnDate)?? dayjsToInputDateString(dayjs()), dnDate: arrayToInputDateString(itemDetail.dnDate)?? dayjsToInputDateString(dayjs()),
putawayLine: dummyPutawayLine,
// putAwayLines: dummyPutAwayLine,
// putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [],
putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1})) ?? [],
qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData], qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData],
escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [], escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [],
receiptDate: itemDetail.receiptDate ?? dayjs().add(0, "month").format(INPUT_DATE_FORMAT), receiptDate: itemDetail.receiptDate ?? dayjs().add(0, "month").format(INPUT_DATE_FORMAT),
@@ -123,6 +138,17 @@ const [qcItems, setQcItems] = useState(dummyQCData)
} else return false; } else return false;
}; };


useEffect(() => {
formProps.reset({
...itemDetail,
dnDate: dayjsToInputDateString(dayjs()),
// putAwayLines: dummyPutAwayLine,
putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1})) ?? [],
})
setOpenPutaway(isPutaway);
}, [open, itemDetail])
const [viewOnly, setViewOnly] = useState(false); const [viewOnly, setViewOnly] = useState(false);
useEffect(() => { useEffect(() => {
if (itemDetail && itemDetail.status) { if (itemDetail && itemDetail.status) {
@@ -248,7 +274,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)


if (validationErrors.length > 0) { if (validationErrors.length > 0) {
console.error("QC Validation failed:", validationErrors); console.error("QC Validation failed:", validationErrors);
alert(`未完成品檢: ${validationErrors}`);
alert(`未完成品檢: ${validationErrors}`);
return; return;
} }


@@ -275,7 +301,6 @@ const [qcItems, setQcItems] = useState(dummyQCData)
// const qcData = data; // const qcData = data;


console.log("QC Data for submission:", qcData); console.log("QC Data for submission:", qcData);

if (data.qcDecision == 3) { // Escalate if (data.qcDecision == 3) { // Escalate
const escalationLog = { const escalationLog = {
type : "qc", type : "qc",
@@ -286,12 +311,10 @@ const [qcItems, setQcItems] = useState(dummyQCData)
} }
console.log("ESCALATION RESULT", escalationLog); console.log("ESCALATION RESULT", escalationLog);
await postStockInLine({...qcData, escalationLog}); await postStockInLine({...qcData, escalationLog});

} else { } else {
await postStockInLine(qcData); await postStockInLine(qcData);
} }


if (qcData.qcAccept) { if (qcData.qcAccept) {
// submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?",
// confirmButtonText: t("confirm putaway"), html: ""}); // confirmButtonText: t("confirm putaway"), html: ""});
@@ -312,7 +335,6 @@ const [qcItems, setQcItems] = useState(dummyQCData)
console.log("Submitting", submitData); console.log("Submitting", submitData);


const res = await updateStockInLine(submitData); const res = await updateStockInLine(submitData);
console.log("Result ", res);
return res; return res;
},[itemDetail]) },[itemDetail])


@@ -357,13 +379,16 @@ const [qcItems, setQcItems] = useState(dummyQCData)
expiryDate : arrayToInputDateString(data.expiryDate), expiryDate : arrayToInputDateString(data.expiryDate),
receiptDate : arrayToInputDateString(data.receiptDate), receiptDate : arrayToInputDateString(data.receiptDate),


// for putaway data
inventoryLotLines: data.putAwayLines?.filter((line) => Boolean(line._isNew))

// Add other putaway specific fields // Add other putaway specific fields
} as ModalFormInput; } as ModalFormInput;
console.log("Putaway Data:", putawayData); console.log("Putaway Data:", putawayData);


// Handle putaway submission logic here // Handle putaway submission logic here
const res = await postStockInLine(putawayData); const res = await postStockInLine(putawayData);
console.log("Result ", res);
console.log("result ", res);
// Close modal after successful putaway // Close modal after successful putaway
closeHandler({}, "backdropClick"); closeHandler({}, "backdropClick");
@@ -371,11 +396,32 @@ const [qcItems, setQcItems] = useState(dummyQCData)
[closeHandler], [closeHandler],
); );
// Print handler // Print handler
const onPrint = useCallback(() => {
console.log("Print putaway documents");
const [isPrinting, setIsPrinting] = useState(false)
const handlePrint = useCallback(async () => {
// console.log("Print putaway documents");
console.log("%c data", "background: white; color: red", formProps.watch("putAwayLines"));
// Handle print logic here // Handle print logic here
window.print();
}, []);
// window.print();
// const postData = { stockInLineIds: [itemDetail.id]};
// const response = await fetchPoQrcode(postData);
// if (response) {
// downloadFile(new Uint8Array(response.blobValue), response.filename)
// }
try {
setIsPrinting(() => true)
const data: PrintQrCodeForSilRequest = {
stockInLineId: itemDetail.id,
printerId: selectedPrinter.id,
printQty: formProps.watch("putAwayLines")?.reduce((acc, cur) => acc + cur.printQty, 0)
}
const response = await printQrCodeForSil(data);
if (response) {
console.log(response)
}
} finally {
setIsPrinting(() => false)
}
}, [itemDetail.id]);
const acceptQty = formProps.watch("acceptedQty") const acceptQty = formProps.watch("acceptedQty")


@@ -395,7 +441,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)
console.log(qcItems) console.log(qcItems)
// checkQcIsPassed(qcItems) // checkQcIsPassed(qcItems)
}, [qcItems, checkQcIsPassed]) }, [qcItems, checkQcIsPassed])
return ( return (
<> <>
<FormProvider {...formProps}> <FormProvider {...formProps}>
@@ -410,26 +456,44 @@ const [qcItems, setQcItems] = useState(dummyQCData)
marginRight: 3, marginRight: 3,
}} }}
> >
{openPutaway ? (
{openPutaway ? (
<Box <Box
component="form" component="form"
onSubmit={formProps.handleSubmit(onSubmitPutaway)} onSubmit={formProps.handleSubmit(onSubmitPutaway)}
> >
<PutawayForm
<PutAwayForm
printerCombo={printerCombo}
itemDetail={itemDetail} itemDetail={itemDetail}
warehouse={warehouse!} warehouse={warehouse!}
disabled={viewOnly} disabled={viewOnly}
/> />
<Stack direction="row" justifyContent="flex-end" gap={1}> <Stack direction="row" justifyContent="flex-end" gap={1}>
<Autocomplete
disableClearable
options={printerCombo}
defaultValue={selectedPrinter}
onChange={(event, value) => {
setSelectedPrinter(value)
}}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label={t("Printer")}
sx={{ width: 300}}
/>
)}
/>
<Button <Button
id="printButton" id="printButton"
type="button" type="button"
variant="contained" variant="contained"
color="primary" color="primary"
sx={{ mt: 1 }} sx={{ mt: 1 }}
onClick={onPrint}
onClick={handlePrint}
disabled={isPrinting}
> >
{t("print")}
{isPrinting ? t("Printing") : t("print")}
</Button> </Button>
<Button <Button
id="putawaySubmit" id="putawaySubmit"


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

@@ -20,7 +20,7 @@ import {
StockInLineEntry, StockInLineEntry,
updateStockInLine, updateStockInLine,
} from "@/app/api/po/actions"; } from "@/app/api/po/actions";
import PutawayForm from "./PutawayForm";
import PutAwayForm from "./PutAwayForm";
import { StockInLine } from "@/app/api/po"; import { StockInLine } from "@/app/api/po";
import { WarehouseResult } from "@/app/api/warehouse"; import { WarehouseResult } from "@/app/api/warehouse";
import { QrCodeInfo } from "@/app/api/qrcode"; import { QrCodeInfo } from "@/app/api/qrcode";
@@ -204,7 +204,7 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => {
</Typography> </Typography>
) : ( ) : (
<> <>
<PutawayForm
<PutAwayForm
itemDetail={itemDetail} itemDetail={itemDetail}
warehouse={warehouse} warehouse={warehouse}
disabled={false} disabled={false}


+ 3
- 3
src/components/PoDetail/dummyQcTemplate.tsx 查看文件

@@ -1,4 +1,4 @@
import { PutawayLine } from "@/app/api/po/actions"
import { PutAwayLine } from "@/app/api/po/actions"
import { QcData } from "@/app/api/qc" import { QcData } from "@/app/api/qc"


// export interface QcData { // export interface QcData {
@@ -73,12 +73,12 @@ export const dummyEscalationHistory: EscalationData[] = [
}, },
] ]


export const dummyPutawayLine: PutawayLine[] = [
export const dummyPutAwayLine: PutAwayLine[] = [
{ {
id: 1, id: 1,
qty: 100, qty: 100,
warehouseId: 1, warehouseId: 1,
warehouse: "W001 - 憶兆 3樓A倉", warehouse: "W001 - 憶兆 3樓A倉",
printQty: 100
printQty: 1
} }
] ]

+ 3
- 1
src/i18n/zh/purchaseOrder.json 查看文件

@@ -133,5 +133,7 @@
"bind": "綁定", "bind": "綁定",
"Search": "搜尋", "Search": "搜尋",
"Found": "已找到", "Found": "已找到",
"escalation processing": "處理上報記錄"
"escalation processing": "處理上報記錄",
"Printer": "列印機",
"Printing": "列印中"
} }

正在加载...
取消
保存