| @@ -95,12 +95,17 @@ export default function TestingPage() { | |||||
| const [laserLastReceive, setLaserLastReceive] = | const [laserLastReceive, setLaserLastReceive] = | ||||
| useState<LaserLastReceiveSuccess | null>(null); | useState<LaserLastReceiveSuccess | null>(null); | ||||
| const bomShopSyncInFlightRef = useRef(false); | const bomShopSyncInFlightRef = useRef(false); | ||||
| const bomShopSyncAllInFlightRef = useRef(false); | |||||
| const [bomShopSyncBomId, setBomShopSyncBomId] = useState("78"); | const [bomShopSyncBomId, setBomShopSyncBomId] = useState("78"); | ||||
| const [bomShopM18HeaderId, setBomShopM18HeaderId] = useState(""); | const [bomShopM18HeaderId, setBomShopM18HeaderId] = useState(""); | ||||
| const [bomShopSyncLoading, setBomShopSyncLoading] = useState(false); | const [bomShopSyncLoading, setBomShopSyncLoading] = useState(false); | ||||
| const [bomShopSyncResult, setBomShopSyncResult] = useState<string | null>( | const [bomShopSyncResult, setBomShopSyncResult] = useState<string | null>( | ||||
| null, | null, | ||||
| ); | ); | ||||
| const [bomShopSyncAllLoading, setBomShopSyncAllLoading] = useState(false); | |||||
| const [bomShopSyncAllResult, setBomShopSyncAllResult] = useState< | |||||
| string | null | |||||
| >(null); | |||||
| const bomByItemCodeInFlightRef = useRef(false); | const bomByItemCodeInFlightRef = useRef(false); | ||||
| const [bomByItemCodeInput, setBomByItemCodeInput] = useState(""); | const [bomByItemCodeInput, setBomByItemCodeInput] = useState(""); | ||||
| const [bomByItemCodeLoading, setBomByItemCodeLoading] = useState(false); | 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 () => { | const handleBomLookupByItemCode = async () => { | ||||
| if (bomByItemCodeInFlightRef.current) return; | if (bomByItemCodeInFlightRef.current) return; | ||||
| const code = bomByItemCodeInput.trim(); | const code = bomByItemCodeInput.trim(); | ||||
| @@ -768,6 +806,39 @@ export default function TestingPage() { | |||||
| <Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 600 }}> | <Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 600 }}> | ||||
| M18 udfBomForShop sync | M18 udfBomForShop sync | ||||
| </Typography> | </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 }}> | <Typography variant="body2" color="textSecondary" sx={{ mb: 1 }}> | ||||
| POST <code>/m18/test/bom-shop-sync/:bomId</code> with optional{" "} | POST <code>/m18/test/bom-shop-sync/:bomId</code> with optional{" "} | ||||
| <code>?m18HeaderId=</code> (M18 udfBomForShop header id for{" "} | <code>?m18HeaderId=</code> (M18 udfBomForShop header id for{" "} | ||||