ソースを参照

jo bom status update

master
kelvin.yau 2ヶ月前
コミット
0a2ddaf4ae
3個のファイルの変更133行の追加7行の削除
  1. +19
    -0
      src/app/api/jo/detail/route.ts
  2. +111
    -6
      src/components/JoSearch/JoSearch.tsx
  3. +3
    -1
      src/i18n/zh/jo.json

+ 19
- 0
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 });
}
}

+ 111
- 6
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<Props> = ({ defaultInputs, bomCombo }) => {
const [totalCount, setTotalCount] = useState(0)
const [isCreateJoModalOpen, setIsCreateJoModalOpen] = useState(false)

const [inventoryData, setInventoryData] = useState<InventoryResult[]>([]);

const [detailedJos, setDetailedJos] = useState<Map<number, JobOrder>>(new Map());

const fetchJoDetailClient = async (id: number): Promise<JobOrder> => {
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<number, JobOrder>();
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<SearchParamNames>[] = 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<Column<JobOrder>[]>(
() => [
{
name: "code",
label: t("Code")
label: t("Code"),
flex: 2
},
{
name: "item",
@@ -102,10 +191,26 @@ const JoSearch: React.FC<Props> = ({ 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 (
<span style={{ color: stockCounts.insufficient > 0 ? 'red' : 'green' }}>
{stockCounts.sufficient}/{stockCounts.total}
</span>
);
}
},
{
// TODO put it inside Action Buttons
name: "id",
@@ -127,7 +232,7 @@ const JoSearch: React.FC<Props> = ({ defaultInputs, bomCombo }) => {
)
}
},
], []
], [inventoryData]
)

const handleUpdate = useCallback(async (jo: JobOrder) => {


+ 3
- 1
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": "預計生產日期"
}

読み込み中…
キャンセル
保存