Просмотр исходного кода

adding the sync of BOM testing

production
[email protected] 1 месяц назад
Родитель
Сommit
faa2bc687e
1 измененных файлов: 217 добавлений и 1 удалений
  1. +217
    -1
      src/app/(main)/testing/page.tsx

+ 217
- 1
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<string | null>(null);
const [laserLastReceive, setLaserLastReceive] =
useState<LaserLastReceiveSuccess | null>(null);
const bomShopSyncInFlightRef = useRef(false);
const [bomShopSyncBomId, setBomShopSyncBomId] = useState("78");
const [bomShopM18HeaderId, setBomShopM18HeaderId] = useState("");
const [bomShopSyncLoading, setBomShopSyncLoading] = useState(false);
const [bomShopSyncResult, setBomShopSyncResult] = useState<string | null>(
null,
);
const bomByItemCodeInFlightRef = useRef(false);
const [bomByItemCodeInput, setBomByItemCodeInput] = useState("");
const [bomByItemCodeLoading, setBomByItemCodeLoading] = useState(false);
const [bomByItemCodeResult, setBomByItemCodeResult] = useState<string | null>(
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() {
<Tab label="2. OnPack NGPCL" />
<Tab label="3. Laser Bag2 自動送" />
<Tab label="4. 批號標籤列印" />
<Tab label="5. M18 BOM shop" />
</Tabs>

<TabPanel value={tabValue} index={0}>
@@ -601,6 +696,127 @@ export default function TestingPage() {
/>
</Section>
</TabPanel>

<TabPanel value={tabValue} index={4}>
<Section title="5. M18 BOM shop sync (udfBomForShop)">
<Alert severity="info" sx={{ mb: 2 }}>
Requires setting <code>M18.bom.shop.sync.enabled=true</code>. Use{" "}
<code>m18HeaderId</code> query param (or the field below) so M18{" "}
<strong>updates</strong> the existing udfBomForShop header instead of creating a duplicate.
</Alert>
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 600 }}>
Lookup BOM id by item code (like PO-by-code)
</Typography>
<Typography variant="body2" color="textSecondary" sx={{ mb: 1 }}>
Uses <code>GET /bom/by-item-code?code=…</code> — item is the BOM
header product (<code>bom.item</code>), same as FPSMS finished-good
<code> items.code</code>.
</Typography>
<Stack
direction={{ xs: "column", sm: "row" }}
spacing={2}
sx={{ mb: 2, alignItems: "center", flexWrap: "wrap" }}
>
<TextField
size="small"
label="Item code"
value={bomByItemCodeInput}
onChange={(e) => setBomByItemCodeInput(e.target.value)}
sx={{ width: 220 }}
/>
<Button
variant="outlined"
onClick={() => void handleBomLookupByItemCode()}
disabled={bomByItemCodeLoading}
>
{bomByItemCodeLoading ? "Looking up…" : "Lookup BOM id"}
</Button>
</Stack>
{bomByItemCodeResult ? (
<Stack spacing={1} sx={{ mb: 3 }}>
<TextField
fullWidth
multiline
minRows={4}
label="Lookup response (BomIdByItemCodeResponse)"
value={bomByItemCodeResult}
InputProps={{ readOnly: true }}
sx={{ fontFamily: "monospace" }}
/>
<Button
size="small"
variant="text"
onClick={() => {
try {
const o = JSON.parse(bomByItemCodeResult) as {
bomId?: number;
bomM18Id?: number;
};
if (o?.bomId != null)
setBomShopSyncBomId(String(o.bomId));
if (o?.bomM18Id != null)
setBomShopM18HeaderId(String(o.bomM18Id));
} catch {
/* ignore */
}
}}
>
Copy bomId + bomM18Id to sync fields below
</Button>
</Stack>
) : null}
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 600 }}>
M18 udfBomForShop sync
</Typography>
<Typography variant="body2" color="textSecondary" sx={{ mb: 1 }}>
POST <code>/m18/test/bom-shop-sync/:bomId</code> with optional{" "}
<code>?m18HeaderId=</code> (M18 udfBomForShop header id for{" "}
<strong>update</strong>; from lookup <code>bomM18Id</code> or{" "}
<code>Bom.m18Id</code> in DB). If omitted, backend uses{" "}
<code>bom.m18Id</code> when set; otherwise creates a new M18 row.
</Typography>
<Stack
direction={{ xs: "column", sm: "row" }}
spacing={2}
sx={{ mb: 2, alignItems: "center", flexWrap: "wrap" }}
>
<TextField
size="small"
label="BOM id"
value={bomShopSyncBomId}
onChange={(e) => setBomShopSyncBomId(e.target.value)}
sx={{ width: 160 }}
/>
<TextField
size="small"
label="M18 header id (optional, update)"
value={bomShopM18HeaderId}
onChange={(e) => setBomShopM18HeaderId(e.target.value)}
sx={{ width: 220 }}
helperText="e.g. 255 from bomM18Id — forces main.id in payload"
/>
<Button
variant="contained"
color="primary"
onClick={() => void handleBomShopSyncM18()}
disabled={bomShopSyncLoading}
>
{bomShopSyncLoading ? "Syncing…" : "Sync BOM to M18"}
</Button>
</Stack>
{bomShopSyncResult ? (
<TextField
fullWidth
multiline
minRows={10}
label="Response (M18BomShopSyncTriggerResult)"
value={bomShopSyncResult}
InputProps={{ readOnly: true }}
sx={{ fontFamily: "monospace" }}
/>
) : null}
</Section>
</TabPanel>
</Box>
);
}

Загрузка…
Отмена
Сохранить