| @@ -1,3 +1,4 @@ | |||
| import { fetchPrinterCombo } from "@/app/api/settings/printer"; | |||
| import { fetchEscalationCombo } from "@/app/api/user"; | |||
| import { SearchParams } from "@/app/utils/fetchUtil"; | |||
| import { TypeEnum } from "@/app/utils/typeEnum"; | |||
| @@ -25,6 +26,7 @@ const PoEdit: React.FC<Props> = async ({ searchParams }) => { | |||
| } | |||
| fetchEscalationCombo() | |||
| fetchPrinterCombo() | |||
| return ( | |||
| <> | |||
| @@ -6,10 +6,11 @@ import { revalidateTag } from "next/cache"; | |||
| import { cache } from "react"; | |||
| import { PoResult, StockInLine } from "."; | |||
| //import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
| import { serverFetchJson } from "../../utils/fetchUtil"; | |||
| import { serverFetchJson, serverFetchWithNoContent } from "../../utils/fetchUtil"; | |||
| import { QcItemResult } from "../settings/qcItem"; | |||
| import { RecordsRes } from "../utils"; | |||
| import { Uom } from "../settings/uom"; | |||
| import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; | |||
| // import { BASE_API_URL } from "@/config/api"; | |||
| export interface PostStockInLineResponse<T> { | |||
| @@ -81,26 +82,34 @@ export interface EscalationInput { | |||
| acceptedQty?: number; // this is the qty to be escalated | |||
| // escalationQty: number | |||
| } | |||
| export interface PutawayLine { | |||
| export interface PutAwayLine { | |||
| id?: number | |||
| qty: number | |||
| warehouseId: number; | |||
| warehouse: string; | |||
| printQty: number | |||
| printQty: number; | |||
| _isNew?: boolean; | |||
| printQty?: number; | |||
| } | |||
| export interface PutawayInput { | |||
| export interface PutAwayInput { | |||
| status: string; | |||
| acceptedQty: number; | |||
| warehouseId: number; | |||
| putawayLine: PutawayLine[] | |||
| putAwayLines: PutAwayLine[] | |||
| } | |||
| export type ModalFormInput = Partial< | |||
| PurchaseQCInput & StockInInput & PutawayInput | |||
| PurchaseQCInput & StockInInput & PutAwayInput | |||
| > & { | |||
| escalationLog? : Partial<EscalationInput> | |||
| }; | |||
| export interface PrintQrCodeForSilRequest { | |||
| stockInLineId: number; | |||
| printerId: number; | |||
| printQty?: number; | |||
| } | |||
| export const testFetch = cache(async (id: number) => { | |||
| return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | |||
| 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"], | |||
| }, | |||
| }, | |||
| ) | |||
| }) | |||
| @@ -6,6 +6,7 @@ import { serverFetchJson } from "../../utils/fetchUtil"; | |||
| import { BASE_API_URL } from "../../../config/api"; | |||
| import { Uom } from "../settings/uom"; | |||
| import { RecordsRes } from "../utils"; | |||
| import { PutAwayLine } from "./actions"; | |||
| export interface PoResult { | |||
| id: number; | |||
| @@ -82,6 +83,7 @@ export interface StockInLine { | |||
| dnDate?: number[]; | |||
| stockQty?: number; | |||
| handlerId?: number; | |||
| putAwayLines?: PutAwayLine[]; | |||
| } | |||
| export const fetchPoList = cache(async (queryParams?: Record<string, any>) => { | |||
| @@ -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"] }, | |||
| }) | |||
| }) | |||
| @@ -10,8 +10,20 @@ export interface WarehouseResult { | |||
| description: string; | |||
| } | |||
| export interface WarehouseCombo { | |||
| id: number; | |||
| value: number; | |||
| label: string; | |||
| } | |||
| export const fetchWarehouseList = cache(async () => { | |||
| return serverFetchJson<WarehouseResult[]>(`${BASE_API_URL}/warehouse`, { | |||
| next: { tags: ["warehouse"] }, | |||
| }); | |||
| }); | |||
| export const fetchWarehouseCombo = cache(async () => { | |||
| return serverFetchJson<WarehouseCombo[]>(`${BASE_API_URL}/warehouse/combo`, { | |||
| next: { tags: ["warehouseCombo"] }, | |||
| }); | |||
| }); | |||
| @@ -74,6 +74,7 @@ export interface InputDataGridProps<T, V, E> { | |||
| validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||
| needAdd?: boolean; | |||
| showRemoveBtn?: boolean; | |||
| addRowDefaultValue?: Partial<V>; | |||
| } | |||
| export interface SelectionInputDataGridProps<T, V, E> { | |||
| @@ -85,6 +86,7 @@ export interface SelectionInputDataGridProps<T, V, E> { | |||
| validateRow: (newRow: GridRowModel<TableRow<V, E>>) => E; | |||
| needAdd?: boolean; | |||
| showRemoveBtn?: boolean; | |||
| addRowDefaultValue?: Partial<V>; | |||
| } | |||
| export type Props<T, V, E> = | |||
| @@ -112,6 +114,7 @@ function InputDataGrid<T, V, E>({ | |||
| validateRow, | |||
| needAdd, | |||
| showRemoveBtn = true, | |||
| addRowDefaultValue = {}, | |||
| }: Props<T, V, E>) { | |||
| const { | |||
| t, | |||
| @@ -126,13 +129,13 @@ function InputDataGrid<T, V, E>({ | |||
| [], | |||
| ); | |||
| const list: TableRow<V, E>[] = getValues(formKey); | |||
| console.log(list) | |||
| // console.log(list) | |||
| const [rows, setRows] = useState<TableRow<V, E>[]>(() => { | |||
| const list: TableRow<V, E>[] = getValues(formKey); | |||
| console.log(list) | |||
| // console.log(list) | |||
| return list && list.length > 0 ? list : []; | |||
| }); | |||
| console.log(rows) | |||
| // console.log(rows) | |||
| // const originalRows = list && list.length > 0 ? list : []; | |||
| const originalRows = useMemo(() => ( | |||
| list && list.length > 0 ? list : [] | |||
| @@ -145,7 +148,7 @@ function InputDataGrid<T, V, E>({ | |||
| const rowModel: GridRowSelectionModel = getValues( | |||
| `${formKey}_active`, | |||
| ) as GridRowSelectionModel; | |||
| console.log(rowModel); | |||
| // console.log(rowModel); | |||
| return rowModel; | |||
| }); | |||
| @@ -162,7 +165,7 @@ function InputDataGrid<T, V, E>({ | |||
| (updateError: ProcessRowUpdateError<T, E>) => { | |||
| const errors = updateError.errors; | |||
| const row = updateError.row; | |||
| console.log(errors); | |||
| // console.log(errors); | |||
| apiRef.current.updateRows([{ ...row, _error: errors }]); | |||
| }, | |||
| [apiRef], | |||
| @@ -176,7 +179,7 @@ function InputDataGrid<T, V, E>({ | |||
| ///////////////// | |||
| // validation here | |||
| const errors = validateRow(newRow); | |||
| console.log(newRow); | |||
| // console.log(newRow); | |||
| if (errors) { | |||
| throw new ProcessRowUpdateError( | |||
| originalRow, | |||
| @@ -189,7 +192,6 @@ function InputDataGrid<T, V, E>({ | |||
| const rowToSave = { | |||
| ...updatedRow, | |||
| } as TableRow<V, E>; /// test | |||
| console.log(rowToSave); | |||
| setRows((rw) => | |||
| rw.map((r) => (getRowId(r) === getRowId(originalRow) ? rowToSave : r)), | |||
| ); | |||
| @@ -198,8 +200,8 @@ function InputDataGrid<T, V, E>({ | |||
| [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]); | |||
| setRowModesModel((model) => ({ | |||
| ...model, | |||
| @@ -290,7 +292,6 @@ function InputDataGrid<T, V, E>({ | |||
| // sync useForm | |||
| useEffect(() => { | |||
| // console.log(formKey) | |||
| // console.log(rows) | |||
| setValue(formKey, rows); | |||
| }, [formKey, rows, setValue]); | |||
| @@ -300,7 +301,7 @@ function InputDataGrid<T, V, E>({ | |||
| disableRipple | |||
| variant="outlined" | |||
| startIcon={<Add />} | |||
| onClick={addRow} | |||
| onClick={() => addRow(addRowDefaultValue)} | |||
| size="small" | |||
| > | |||
| 新增 | |||
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
| import { PurchaseQcResult, PutawayInput, PutawayLine } from "@/app/api/po/actions"; | |||
| import { PurchaseQcResult, PutAwayInput, PutAwayLine } from "@/app/api/po/actions"; | |||
| import { | |||
| Autocomplete, | |||
| Box, | |||
| @@ -61,11 +61,11 @@ interface Props { | |||
| } | |||
| type EntryError = | |||
| | { | |||
| [field in keyof PutawayLine]?: string; | |||
| [field in keyof PutAwayLine]?: string; | |||
| } | |||
| | undefined; | |||
| type PutawayRow = TableRow<Partial<PutawayLine>, EntryError>; | |||
| type PutawayRow = TableRow<Partial<PutAwayLine>, EntryError>; | |||
| const style = { | |||
| position: "absolute", | |||
| @@ -93,7 +93,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| resetField, | |||
| setError, | |||
| clearErrors, | |||
| } = useFormContext<PutawayInput>(); | |||
| } = useFormContext<PutAwayInput>(); | |||
| console.log(itemDetail); | |||
| // const [recordQty, setRecordQty] = useState(0); | |||
| 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 validateForm = useCallback(() => { | |||
| // console.log(accQty); | |||
| @@ -492,10 +492,10 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| style={{ display: "flex", justifyContent: "center" }} | |||
| > | |||
| {/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */} | |||
| <InputDataGrid<PutawayInput, PutawayLine, EntryError> | |||
| <InputDataGrid<PutAwayInput, PutAwayLine, EntryError> | |||
| apiRef={apiRef} | |||
| checkboxSelection={false} | |||
| _formKey={"putawayLine"} | |||
| _formKey={"putAwayLines"} | |||
| columns={columns} | |||
| validateRow={validation} | |||
| needAdd={true} | |||
| @@ -1,4 +1,4 @@ | |||
| import { PutawayLine } from "@/app/api/po/actions" | |||
| import { PutAwayLine } from "@/app/api/po/actions" | |||
| export interface QcData { | |||
| id: number, | |||
| @@ -67,7 +67,7 @@ export const dummyEscalationHistory: EscalationData[] = [ | |||
| }, | |||
| ] | |||
| export const dummyPutawayLine: PutawayLine[] = [ | |||
| export const dummyPutawayLine: PutAwayLine[] = [ | |||
| { | |||
| id: 1, | |||
| qty: 100, | |||
| @@ -80,7 +80,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| const formProps = useForm<any>({ | |||
| defaultValues: { | |||
| ...itemDetail, | |||
| putawayLine: dummyPutawayLine, | |||
| putAwayLine: dummyPutawayLine, | |||
| // receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), | |||
| // warehouseId: itemDetail.defaultWarehouseId || 0 | |||
| }, | |||
| @@ -79,6 +79,7 @@ import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers"; | |||
| import { debounce } from "lodash"; | |||
| import LoadingComponent from "../General/LoadingComponent"; | |||
| import { getMailTemplateForStockInLine } from "@/app/api/mailTemplate/actions"; | |||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||
| //import { useRouter } from "next/navigation"; | |||
| @@ -86,6 +87,7 @@ type Props = { | |||
| po: PoResult; | |||
| qc: QcItemWithChecks[]; | |||
| warehouse: WarehouseResult[]; | |||
| printerCombo: PrinterCombo[]; | |||
| }; | |||
| type EntryError = | |||
| @@ -188,7 +190,7 @@ interface PolInputResult { | |||
| dnQty: number, | |||
| } | |||
| const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| const PoDetail: React.FC<Props> = ({ po, qc, warehouse, printerCombo }) => { | |||
| const cameras = useContext(CameraContext); | |||
| // console.log(cameras); | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| @@ -858,6 +860,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| warehouse={warehouse} | |||
| fetchPoDetail={fetchPoDetail} | |||
| handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||
| printerCombo={printerCombo} | |||
| /> | |||
| </Box> | |||
| </TableCell> | |||
| @@ -11,6 +11,7 @@ import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { fetchWarehouseList } from "@/app/api/warehouse"; | |||
| import { fetchQcItemCheck } from "@/app/api/qc/actions"; | |||
| import { fetchEscalationCombo } from "@/app/api/user"; | |||
| import { fetchPrinterCombo } from "@/app/api/settings/printer"; | |||
| interface SubComponents { | |||
| Loading: typeof PoDetailLoading; | |||
| @@ -25,16 +26,18 @@ const PoDetailWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | |||
| poWithStockInLine, | |||
| warehouse, | |||
| qc, | |||
| escalationCombo | |||
| escalationCombo, | |||
| printerCombo, | |||
| ] = await Promise.all([ | |||
| fetchPoWithStockInLines(id), | |||
| fetchWarehouseList(), | |||
| fetchQcItemCheck(), | |||
| fetchEscalationCombo(), | |||
| fetchPrinterCombo() | |||
| ]); | |||
| // 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; | |||
| @@ -62,6 +62,7 @@ import { useSession } from "next-auth/react"; | |||
| // import { SessionWithTokens } from "src/config/authConfig"; | |||
| import PoQcStockInModalVer2 from "./QcStockInModalVer2"; | |||
| import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; | |||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||
| import { EscalationResult } from "@/app/api/escalation"; | |||
| import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions"; | |||
| import { SessionWithTokens } from "@/config/authConfig"; | |||
| @@ -80,6 +81,7 @@ interface Props { | |||
| warehouse: WarehouseResult[]; | |||
| fetchPoDetail: (poId: string) => void; | |||
| handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||
| printerCombo: PrinterCombo[]; | |||
| } | |||
| export type StockInLineEntryError = { | |||
| @@ -119,7 +121,8 @@ function PoInputGrid({ | |||
| stockInLine, | |||
| warehouse, | |||
| fetchPoDetail, | |||
| handleMailTemplateForStockInLine | |||
| handleMailTemplateForStockInLine, | |||
| printerCombo | |||
| }: Props) { | |||
| console.log(itemDetail); | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| @@ -301,7 +304,7 @@ const closeNewModal = useCallback(() => { | |||
| const handleNewQC = useCallback( | |||
| (id: GridRowId, params: any) => async() => { | |||
| // console.log(id) | |||
| // console.log(params) | |||
| console.log("params", params.row) | |||
| // setBtnIsLoading(true); | |||
| setRowModesModel((prev) => ({ | |||
| ...prev, | |||
| @@ -939,6 +942,7 @@ const closeNewModal = useCallback(() => { | |||
| onClose={closeNewModal} | |||
| itemDetail={modalInfo} | |||
| handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} | |||
| printerCombo={printerCombo} | |||
| /> | |||
| </> | |||
| ) | |||
| @@ -28,7 +28,7 @@ import { useSearchParams } from "next/navigation"; | |||
| import { StockInLineRow } from "./PoInputGrid"; | |||
| import EscalationForm from "./EscalationForm"; | |||
| import StockInForm from "./StockInForm"; | |||
| import PutawayForm from "./PutawayForm"; | |||
| import PutAwayForm from "./PutAwayForm"; | |||
| import { | |||
| INPUT_DATE_FORMAT, | |||
| stockInLineStatusMap, | |||
| @@ -437,7 +437,7 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||
| /> | |||
| )} | |||
| {itemDetail !== undefined && type === "putaway" && ( | |||
| <PutawayForm | |||
| <PutAwayForm | |||
| itemDetail={itemDetail} | |||
| warehouse={warehouse!} | |||
| disabled={!renderSubmitButton} | |||
| @@ -1,6 +1,6 @@ | |||
| "use client"; | |||
| import { PurchaseQcResult, PutawayInput, PutawayLine } from "@/app/api/po/actions"; | |||
| import { PurchaseQcResult, PutAwayInput, PutAwayLine } from "@/app/api/po/actions"; | |||
| import { | |||
| Autocomplete, | |||
| Box, | |||
| @@ -50,7 +50,8 @@ import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | |||
| import dayjs from "dayjs"; | |||
| import arraySupport from "dayjs/plugin/arraySupport"; | |||
| import { dummyPutawayLine } from "./dummyQcTemplate"; | |||
| import { dummyPutAwayLine } from "./dummyQcTemplate"; | |||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||
| dayjs.extend(arraySupport); | |||
| interface Props { | |||
| @@ -58,14 +59,15 @@ interface Props { | |||
| warehouse: WarehouseResult[]; | |||
| disabled: boolean; | |||
| // qc: QcItemWithChecks[]; | |||
| printerCombo: PrinterCombo[]; | |||
| } | |||
| type EntryError = | |||
| | { | |||
| [field in keyof PutawayLine]?: string; | |||
| [field in keyof PutAwayLine]?: string; | |||
| } | |||
| | undefined; | |||
| type PutawayRow = TableRow<Partial<PutawayLine>, EntryError>; | |||
| type PutAwayRow = TableRow<Partial<PutAwayLine>, EntryError>; | |||
| const style = { | |||
| position: "absolute", | |||
| @@ -79,7 +81,7 @@ const style = { | |||
| width: "auto", | |||
| }; | |||
| const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| const PutAwayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled, printerCombo }) => { | |||
| const { t } = useTranslation("purchaseOrder"); | |||
| const apiRef = useGridApiRef(); | |||
| const { | |||
| @@ -93,7 +95,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| resetField, | |||
| setError, | |||
| clearErrors, | |||
| } = useFormContext<PutawayInput>(); | |||
| } = useFormContext<PutAwayInput>(); | |||
| console.log(itemDetail); | |||
| // const [recordQty, setRecordQty] = useState(0); | |||
| 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 validateForm = useCallback(() => { | |||
| // console.log(accQty); | |||
| @@ -274,6 +276,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| field: "qty", | |||
| headerName: t("qty"), | |||
| flex: 1, | |||
| editable: true, | |||
| // renderCell(params) { | |||
| // return <>100</> | |||
| // }, | |||
| @@ -282,6 +285,33 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| field: "warehouse", | |||
| headerName: t("warehouse"), | |||
| 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) { | |||
| // return <>{filteredWarehouse[0].name}</> | |||
| // }, | |||
| @@ -290,6 +320,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| field: "printQty", | |||
| headerName: t("printQty"), | |||
| flex: 1, | |||
| editable: true, | |||
| // renderCell(params) { | |||
| // return <>100</> | |||
| // }, | |||
| @@ -297,7 +328,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| ], []) | |||
| const validation = useCallback( | |||
| (newRow: GridRowModel<PutawayRow>): EntryError => { | |||
| (newRow: GridRowModel<PutAwayRow>): EntryError => { | |||
| const error: EntryError = {}; | |||
| 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 ( | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| <Grid item xs={12}> | |||
| @@ -493,14 +531,15 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| style={{ display: "flex", justifyContent: "center" }} | |||
| > | |||
| {/* <QrCode content={qrContent} sx={{ width: 200, height: 200 }} /> */} | |||
| <InputDataGrid<PutawayInput, PutawayLine, EntryError> | |||
| <InputDataGrid<PutAwayInput, PutAwayLine, EntryError> | |||
| apiRef={apiRef} | |||
| checkboxSelection={false} | |||
| _formKey={"putawayLine"} | |||
| _formKey={"putAwayLines"} | |||
| columns={columns} | |||
| validateRow={validation} | |||
| needAdd={true} | |||
| showRemoveBtn={false} | |||
| addRowDefaultValue={addRowDefaultValue} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -525,4 +564,4 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse, disabled }) => { | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default PutawayForm; | |||
| export default PutAwayForm; | |||
| @@ -1,14 +1,16 @@ | |||
| "use client"; | |||
| 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 { | |||
| Autocomplete, | |||
| Box, | |||
| Button, | |||
| Grid, | |||
| Modal, | |||
| ModalProps, | |||
| Stack, | |||
| TextField, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | |||
| @@ -20,10 +22,16 @@ import StockInFormVer2 from "./StockInFormVer2"; | |||
| import PutawayForm from "./PutawayForm"; | |||
| import QcComponent from "./QcComponent"; | |||
| 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 {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 { fetchPoQrcode } from "@/app/api/pdf/actions"; | |||
| import { downloadFile } from "@/app/utils/commonUtil"; | |||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||
| import { watch } from "fs"; | |||
| import { EscalationResult } from "@/app/api/escalation"; | |||
| import { SessionWithTokens } from "@/config/authConfig"; | |||
| @@ -60,6 +68,7 @@ interface CommonProps extends Omit<ModalProps, "children"> { | |||
| warehouse?: any[]; | |||
| // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; | |||
| handleMailTemplateForStockInLine: (stockInLineId: number) => void; | |||
| printerCombo: PrinterCombo[]; | |||
| onClose: () => void; | |||
| } | |||
| interface Props extends CommonProps { | |||
| @@ -78,19 +87,25 @@ const PoQcStockInModalVer2: React.FC<Props> = ({ | |||
| qc, | |||
| warehouse, | |||
| handleMailTemplateForStockInLine, | |||
| printerCombo | |||
| }) => { | |||
| const { | |||
| t, | |||
| i18n: { language }, | |||
| } = useTranslation("purchaseOrder"); | |||
| // Select Printer | |||
| const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]) | |||
| const defaultNewValue = useMemo(() => { | |||
| return ( | |||
| { | |||
| ...itemDetail, | |||
| status: itemDetail.status ?? "pending", | |||
| 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], | |||
| escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [], | |||
| receiptDate: itemDetail.receiptDate ?? dayjs().add(0, "month").format(INPUT_DATE_FORMAT), | |||
| @@ -123,6 +138,17 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| } 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); | |||
| useEffect(() => { | |||
| if (itemDetail && itemDetail.status) { | |||
| @@ -248,7 +274,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| if (validationErrors.length > 0) { | |||
| console.error("QC Validation failed:", validationErrors); | |||
| alert(`未完成品檢: ${validationErrors}`); | |||
| alert(`未完成品檢: ${validationErrors}`); | |||
| return; | |||
| } | |||
| @@ -275,7 +301,6 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| // const qcData = data; | |||
| console.log("QC Data for submission:", qcData); | |||
| if (data.qcDecision == 3) { // Escalate | |||
| const escalationLog = { | |||
| type : "qc", | |||
| @@ -286,12 +311,10 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| } | |||
| console.log("ESCALATION RESULT", escalationLog); | |||
| await postStockInLine({...qcData, escalationLog}); | |||
| } else { | |||
| await postStockInLine(qcData); | |||
| } | |||
| if (qcData.qcAccept) { | |||
| // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", | |||
| // confirmButtonText: t("confirm putaway"), html: ""}); | |||
| @@ -312,7 +335,6 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| console.log("Submitting", submitData); | |||
| const res = await updateStockInLine(submitData); | |||
| console.log("Result ", res); | |||
| return res; | |||
| },[itemDetail]) | |||
| @@ -357,13 +379,16 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| expiryDate : arrayToInputDateString(data.expiryDate), | |||
| receiptDate : arrayToInputDateString(data.receiptDate), | |||
| // for putaway data | |||
| inventoryLotLines: data.putAwayLines?.filter((line) => Boolean(line._isNew)) | |||
| // Add other putaway specific fields | |||
| } as ModalFormInput; | |||
| console.log("Putaway Data:", putawayData); | |||
| // Handle putaway submission logic here | |||
| const res = await postStockInLine(putawayData); | |||
| console.log("Result ", res); | |||
| console.log("result ", res); | |||
| // Close modal after successful putaway | |||
| closeHandler({}, "backdropClick"); | |||
| @@ -371,11 +396,32 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| [closeHandler], | |||
| ); | |||
| // 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 | |||
| 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") | |||
| @@ -395,7 +441,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| console.log(qcItems) | |||
| // checkQcIsPassed(qcItems) | |||
| }, [qcItems, checkQcIsPassed]) | |||
| return ( | |||
| <> | |||
| <FormProvider {...formProps}> | |||
| @@ -410,26 +456,44 @@ const [qcItems, setQcItems] = useState(dummyQCData) | |||
| marginRight: 3, | |||
| }} | |||
| > | |||
| {openPutaway ? ( | |||
| {openPutaway ? ( | |||
| <Box | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmitPutaway)} | |||
| > | |||
| <PutawayForm | |||
| <PutAwayForm | |||
| printerCombo={printerCombo} | |||
| itemDetail={itemDetail} | |||
| warehouse={warehouse!} | |||
| disabled={viewOnly} | |||
| /> | |||
| <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 | |||
| id="printButton" | |||
| type="button" | |||
| variant="contained" | |||
| color="primary" | |||
| sx={{ mt: 1 }} | |||
| onClick={onPrint} | |||
| onClick={handlePrint} | |||
| disabled={isPrinting} | |||
| > | |||
| {t("print")} | |||
| {isPrinting ? t("Printing") : t("print")} | |||
| </Button> | |||
| <Button | |||
| id="putawaySubmit" | |||
| @@ -20,7 +20,7 @@ import { | |||
| StockInLineEntry, | |||
| updateStockInLine, | |||
| } from "@/app/api/po/actions"; | |||
| import PutawayForm from "./PutawayForm"; | |||
| import PutAwayForm from "./PutAwayForm"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| @@ -204,7 +204,7 @@ const QrModal: React.FC<Props> = ({ open, onClose, warehouse }) => { | |||
| </Typography> | |||
| ) : ( | |||
| <> | |||
| <PutawayForm | |||
| <PutAwayForm | |||
| itemDetail={itemDetail} | |||
| warehouse={warehouse} | |||
| disabled={false} | |||
| @@ -1,4 +1,4 @@ | |||
| import { PutawayLine } from "@/app/api/po/actions" | |||
| import { PutAwayLine } from "@/app/api/po/actions" | |||
| import { QcData } from "@/app/api/qc" | |||
| // export interface QcData { | |||
| @@ -73,12 +73,12 @@ export const dummyEscalationHistory: EscalationData[] = [ | |||
| }, | |||
| ] | |||
| export const dummyPutawayLine: PutawayLine[] = [ | |||
| export const dummyPutAwayLine: PutAwayLine[] = [ | |||
| { | |||
| id: 1, | |||
| qty: 100, | |||
| warehouseId: 1, | |||
| warehouse: "W001 - 憶兆 3樓A倉", | |||
| printQty: 100 | |||
| printQty: 1 | |||
| } | |||
| ] | |||
| @@ -133,5 +133,7 @@ | |||
| "bind": "綁定", | |||
| "Search": "搜尋", | |||
| "Found": "已找到", | |||
| "escalation processing": "處理上報記錄" | |||
| "escalation processing": "處理上報記錄", | |||
| "Printer": "列印機", | |||
| "Printing": "列印中" | |||
| } | |||