diff --git a/src/app/(main)/testing/page.tsx b/src/app/(main)/testing/page.tsx index 9ef0e81..2af8f13 100644 --- a/src/app/(main)/testing/page.tsx +++ b/src/app/(main)/testing/page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { Alert, Box, @@ -94,6 +94,19 @@ export default function TestingPage() { const [laserAutoError, setLaserAutoError] = useState(null); const [laserLastReceive, setLaserLastReceive] = useState(null); + const bomShopSyncInFlightRef = useRef(false); + const [bomShopSyncBomId, setBomShopSyncBomId] = useState("78"); + const [bomShopM18HeaderId, setBomShopM18HeaderId] = useState(""); + const [bomShopSyncLoading, setBomShopSyncLoading] = useState(false); + const [bomShopSyncResult, setBomShopSyncResult] = useState( + null, + ); + const bomByItemCodeInFlightRef = useRef(false); + const [bomByItemCodeInput, setBomByItemCodeInput] = useState(""); + const [bomByItemCodeLoading, setBomByItemCodeLoading] = useState(false); + const [bomByItemCodeResult, setBomByItemCodeResult] = useState( + null, + ); const onpackPayload = useMemo( () => buildOnPackJobOrdersPayload(onpackJobOrders), @@ -259,6 +272,87 @@ export default function TestingPage() { } }; + const handleBomShopSyncM18 = async () => { + if (bomShopSyncInFlightRef.current) return; + const id = parseInt(bomShopSyncBomId.trim(), 10); + if (!Number.isFinite(id) || id <= 0) { + alert("Enter a valid BOM id (positive integer)."); + return; + } + bomShopSyncInFlightRef.current = true; + setBomShopSyncLoading(true); + setBomShopSyncResult(null); + try { + const m18H = bomShopM18HeaderId.trim(); + const qs = + m18H && /^\d+$/.test(m18H) + ? `?m18HeaderId=${encodeURIComponent(m18H)}` + : ""; + const response = await clientAuthFetch( + `${NEXT_PUBLIC_API_URL}/m18/test/bom-shop-sync/${id}${qs}`, + { method: "POST" }, + ); + if (response.status === 401 || response.status === 403) return; + const text = await response.text(); + let display = text; + try { + const parsed: unknown = JSON.parse(text); + display = JSON.stringify(parsed, null, 2); + } catch { + /* keep raw */ + } + if (!response.ok) { + setBomShopSyncResult(`HTTP ${response.status}\n\n${display}`); + return; + } + setBomShopSyncResult(display); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + setBomShopSyncResult(`Error: ${msg}`); + } finally { + setBomShopSyncLoading(false); + bomShopSyncInFlightRef.current = false; + } + }; + + const handleBomLookupByItemCode = async () => { + if (bomByItemCodeInFlightRef.current) return; + const code = bomByItemCodeInput.trim(); + if (!code) { + alert("Enter an item code."); + return; + } + bomByItemCodeInFlightRef.current = true; + setBomByItemCodeLoading(true); + setBomByItemCodeResult(null); + try { + const response = await clientAuthFetch( + `${NEXT_PUBLIC_API_URL}/bom/by-item-code?code=${encodeURIComponent(code)}`, + { method: "GET" }, + ); + if (response.status === 401 || response.status === 403) return; + const text = await response.text(); + let display = text; + try { + const parsed: unknown = JSON.parse(text); + display = JSON.stringify(parsed, null, 2); + } catch { + /* keep raw */ + } + setBomByItemCodeResult(display); + if (!response.ok) { + alert(`Lookup failed: HTTP ${response.status}`); + } + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + setBomByItemCodeResult(`Error: ${msg}`); + alert(msg); + } finally { + setBomByItemCodeLoading(false); + bomByItemCodeInFlightRef.current = false; + } + }; + const Section = ({ title, children, @@ -307,6 +401,7 @@ export default function TestingPage() { + @@ -601,6 +696,127 @@ export default function TestingPage() { /> + + +
+ + Requires setting M18.bom.shop.sync.enabled=true. Use{" "} + m18HeaderId query param (or the field below) so M18{" "} + updates the existing udfBomForShop header instead of creating a duplicate. + + + Lookup BOM id by item code (like PO-by-code) + + + Uses GET /bom/by-item-code?code=… — item is the BOM + header product (bom.item), same as FPSMS finished-good + items.code. + + + setBomByItemCodeInput(e.target.value)} + sx={{ width: 220 }} + /> + + + {bomByItemCodeResult ? ( + + + + + ) : null} + + M18 udfBomForShop sync + + + POST /m18/test/bom-shop-sync/:bomId with optional{" "} + ?m18HeaderId= (M18 udfBomForShop header id for{" "} + update; from lookup bomM18Id or{" "} + Bom.m18Id in DB). If omitted, backend uses{" "} + bom.m18Id when set; otherwise creates a new M18 row. + + + setBomShopSyncBomId(e.target.value)} + sx={{ width: 160 }} + /> + setBomShopM18HeaderId(e.target.value)} + sx={{ width: 220 }} + helperText="e.g. 255 from bomM18Id — forces main.id in payload" + /> + + + {bomShopSyncResult ? ( + + ) : null} +
+
); }