| @@ -95,12 +95,17 @@ export default function TestingPage() { | |||
| const [laserLastReceive, setLaserLastReceive] = | |||
| useState<LaserLastReceiveSuccess | null>(null); | |||
| const bomShopSyncInFlightRef = useRef(false); | |||
| const bomShopSyncAllInFlightRef = useRef(false); | |||
| const [bomShopSyncBomId, setBomShopSyncBomId] = useState("78"); | |||
| const [bomShopM18HeaderId, setBomShopM18HeaderId] = useState(""); | |||
| const [bomShopSyncLoading, setBomShopSyncLoading] = useState(false); | |||
| const [bomShopSyncResult, setBomShopSyncResult] = useState<string | null>( | |||
| null, | |||
| ); | |||
| const [bomShopSyncAllLoading, setBomShopSyncAllLoading] = useState(false); | |||
| const [bomShopSyncAllResult, setBomShopSyncAllResult] = useState< | |||
| string | null | |||
| >(null); | |||
| const bomByItemCodeInFlightRef = useRef(false); | |||
| const [bomByItemCodeInput, setBomByItemCodeInput] = useState(""); | |||
| const [bomByItemCodeLoading, setBomByItemCodeLoading] = useState(false); | |||
| @@ -315,6 +320,39 @@ export default function TestingPage() { | |||
| } | |||
| }; | |||
| const handleBomShopSyncAllM18 = async () => { | |||
| if (bomShopSyncAllInFlightRef.current) return; | |||
| bomShopSyncAllInFlightRef.current = true; | |||
| setBomShopSyncAllLoading(true); | |||
| setBomShopSyncAllResult(null); | |||
| try { | |||
| const response = await clientAuthFetch( | |||
| `${NEXT_PUBLIC_API_URL}/scheduler/trigger/bom-shop-sync-all`, | |||
| { 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 { | |||
| /* plain string from backend is fine */ | |||
| } | |||
| if (!response.ok) { | |||
| setBomShopSyncAllResult(`HTTP ${response.status}\n\n${display}`); | |||
| return; | |||
| } | |||
| setBomShopSyncAllResult(display); | |||
| } catch (e) { | |||
| const msg = e instanceof Error ? e.message : String(e); | |||
| setBomShopSyncAllResult(`Error: ${msg}`); | |||
| } finally { | |||
| setBomShopSyncAllLoading(false); | |||
| bomShopSyncAllInFlightRef.current = false; | |||
| } | |||
| }; | |||
| const handleBomLookupByItemCode = async () => { | |||
| if (bomByItemCodeInFlightRef.current) return; | |||
| const code = bomByItemCodeInput.trim(); | |||
| @@ -768,6 +806,39 @@ export default function TestingPage() { | |||
| <Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 600 }}> | |||
| M18 udfBomForShop sync | |||
| </Typography> | |||
| <Typography variant="body2" color="textSecondary" sx={{ mb: 1 }}> | |||
| GET <code>/scheduler/trigger/bom-shop-sync-all</code>: push{" "} | |||
| <strong>all</strong> non-deleted BOMs (same nightly job path; | |||
| respects <code>M18.bom.shop.sync.enabled</code>). Check{" "} | |||
| <code>scheduler_sync_log</code> (<code>M18_BOM_SHOP</code>) for row | |||
| counts. | |||
| </Typography> | |||
| <Stack direction="row" spacing={2} sx={{ mb: 2, flexWrap: "wrap" }}> | |||
| <Button | |||
| variant="outlined" | |||
| color="secondary" | |||
| onClick={() => void handleBomShopSyncAllM18()} | |||
| disabled={bomShopSyncAllLoading} | |||
| > | |||
| {bomShopSyncAllLoading | |||
| ? "Syncing all BOMs…" | |||
| : "Sync all BOMs to M18"} | |||
| </Button> | |||
| </Stack> | |||
| {bomShopSyncAllResult ? ( | |||
| <TextField | |||
| fullWidth | |||
| multiline | |||
| minRows={3} | |||
| label="Bulk trigger response" | |||
| value={bomShopSyncAllResult} | |||
| InputProps={{ readOnly: true }} | |||
| sx={{ | |||
| mb: 2, | |||
| fontFamily: "monospace", | |||
| }} | |||
| /> | |||
| ) : null} | |||
| <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{" "} | |||