From 0a2ddaf4ae43e3d0ee4de8ec4ca71a672a45b38d Mon Sep 17 00:00:00 2001 From: "kelvin.yau" Date: Wed, 8 Oct 2025 13:55:11 +0800 Subject: [PATCH] jo bom status update --- src/app/api/jo/detail/route.ts | 19 +++++ src/components/JoSearch/JoSearch.tsx | 117 +++++++++++++++++++++++++-- src/i18n/zh/jo.json | 4 +- 3 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 src/app/api/jo/detail/route.ts diff --git a/src/app/api/jo/detail/route.ts b/src/app/api/jo/detail/route.ts new file mode 100644 index 0000000..d9468f8 --- /dev/null +++ b/src/app/api/jo/detail/route.ts @@ -0,0 +1,19 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { fetchJoDetail } from '@/app/api/jo'; + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url); + const id = searchParams.get('id'); + + if (!id) { + return NextResponse.json({ error: 'ID is required' }, { status: 400 }); + } + + try { + const joDetail = await fetchJoDetail(parseInt(id)); + return NextResponse.json(joDetail); + } catch (error) { + console.error('Error fetching JO detail:', error); + return NextResponse.json({ error: 'Failed to fetch JO detail' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/components/JoSearch/JoSearch.tsx b/src/components/JoSearch/JoSearch.tsx index 6815a0a..44bb8ee 100644 --- a/src/components/JoSearch/JoSearch.tsx +++ b/src/components/JoSearch/JoSearch.tsx @@ -11,7 +11,7 @@ import SearchBox from "../SearchBox/SearchBox"; import { useRouter } from "next/navigation"; import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form"; import { StockInLineInput } from "@/app/api/stockIn"; -import { JobOrder, JoStatus } from "@/app/api/jo"; +import { JobOrder, JoDetailPickLine, JoStatus } from "@/app/api/jo"; import { Button, Stack } from "@mui/material"; import { BomCombo } from "@/app/api/bom"; import JoCreateFormModal from "./JoCreateFormModal"; @@ -23,6 +23,9 @@ import { createStockInLine } from "@/app/api/stockIn/actions"; import { msg } from "../Swal/CustomAlerts"; import dayjs from "dayjs"; +import { fetchInventories } from "@/app/api/inventory/actions"; +import { InventoryResult } from "@/app/api/inventory"; + interface Props { defaultInputs: SearchJoResultRequest, bomCombo: BomCombo[] @@ -46,18 +49,104 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo }) => { const [totalCount, setTotalCount] = useState(0) const [isCreateJoModalOpen, setIsCreateJoModalOpen] = useState(false) + const [inventoryData, setInventoryData] = useState([]); + + const [detailedJos, setDetailedJos] = useState>(new Map()); + + const fetchJoDetailClient = async (id: number): Promise => { + const response = await fetch(`/api/jo/detail?id=${id}`); + if (!response.ok) { + throw new Error('Failed to fetch JO detail'); + } + return response.json(); + }; + + useEffect(() => { + const fetchDetailedJos = async () => { + const detailedMap = new Map(); + + for (const jo of filteredJos) { + try { + const detailedJo = await fetchJoDetailClient(jo.id); // Use client function + detailedMap.set(jo.id, detailedJo); + } catch (error) { + console.error(`Error fetching detail for JO ${jo.id}:`, error); + } + } + + setDetailedJos(detailedMap); + }; + + if (filteredJos.length > 0) { + fetchDetailedJos(); + } + }, [filteredJos]); + + useEffect(() => { + const fetchInventoryData = async () => { + try { + const inventoryResponse = await fetchInventories({ + code: "", + name: "", + type: "", + pageNum: 0, + pageSize: 1000 + }); + setInventoryData(inventoryResponse.records); + } catch (error) { + console.error("Error fetching inventory data:", error); + } + }; + + fetchInventoryData(); + }, []); + + const getStockAvailable = (pickLine: JoDetailPickLine) => { + const inventory = inventoryData.find(inventory => + inventory.itemCode === pickLine.code || inventory.itemName === pickLine.name + ); + + if (inventory) { + return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty); + } + + return 0; + }; + + const isStockSufficient = (pickLine: JoDetailPickLine) => { + const stockAvailable = getStockAvailable(pickLine); + return stockAvailable >= pickLine.reqQty; + }; + + const getStockCounts = (jo: JobOrder) => { + const detailedJo = detailedJos.get(jo.id); + + if (!detailedJo?.pickLines || detailedJo.pickLines.length === 0) { + return { total: 0, sufficient: 0, insufficient: 0 }; + } + + const totalLines = detailedJo.pickLines.length; + const sufficientLines = detailedJo.pickLines.filter(pickLine => isStockSufficient(pickLine)).length; + const insufficientLines = totalLines - sufficientLines; + + return { + total: totalLines, + sufficient: sufficientLines, + insufficient: insufficientLines + }; + }; + const searchCriteria: Criterion[] = useMemo(() => [ { label: t("Code"), paramName: "code", type: "text" }, - { label: t("Item Name"), paramName: "itemName", type: "text" }, - { label: t("Estimated Production Date From"), paramName: "planStartFrom", type: "date" }, - { label: t("Estimated Production Date To"), paramName: "planStartTo", type: "date" }, + { label: t("Item Name"), paramName: "itemName", type: "text" }, ], [t]) const columns = useMemo[]>( () => [ { name: "code", - label: t("Code") + label: t("Code"), + flex: 2 }, { name: "item", @@ -102,10 +191,26 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo }) => { },{ name: "planStart", label: t("Estimated Production Date"), + align: "left", + headerAlign: "left", renderCell: (row) => { return row.planStart ? arrayToDateString(row.planStart, "output") : '-' } }, + { + name: "stockStatus" as keyof JobOrder, + label: t("BOM Status"), + align: "left", + headerAlign: "left", + renderCell: (row) => { + const stockCounts = getStockCounts(row); + return ( + 0 ? 'red' : 'green' }}> + {stockCounts.sufficient}/{stockCounts.total} + + ); + } + }, { // TODO put it inside Action Buttons name: "id", @@ -127,7 +232,7 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo }) => { ) } }, - ], [] + ], [inventoryData] ) const handleUpdate = useCallback(async (jo: JobOrder) => { diff --git a/src/i18n/zh/jo.json b/src/i18n/zh/jo.json index 1b783b2..d54960e 100644 --- a/src/i18n/zh/jo.json +++ b/src/i18n/zh/jo.json @@ -271,5 +271,7 @@ "Submitting...": "提交中...", "COMPLETED": "已完成", "success": "成功", - "Total (Verified + Bad + Missing) must equal Required quantity": "驗證數量 + 不良數量 + 缺失數量必須等於需求數量" + "Total (Verified + Bad + Missing) must equal Required quantity": "驗證數量 + 不良數量 + 缺失數量必須等於需求數量", + "BOM Status": "材料預備狀況", + "Estimated Production Date": "預計生產日期" }