@chenchaolong/plugin-trade-compliance-workbench 1.0.149 → 1.0.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -104,8 +104,6 @@ function App() {
104
104
  const settingsRatesBulkReconcileRef = React.useRef(createDeferredBulkReconcileCoordinator());
105
105
  const analyticsRequestsRef = React.useRef(createLatestCommittedRequest(initialAnalyticsQuery));
106
106
  const [analyticsCommittedQuery, setAnalyticsCommittedQuery] = React.useState(initialAnalyticsQuery);
107
- const salesFilesPrototypeListenerCleanupRef = React.useRef(null);
108
- const [salesFilesPrototypeState, setSalesFilesPrototypeState] = React.useState(undefined);
109
107
  const [data, setData] = React.useState(() => normalizeWorkbenchData(null));
110
108
  const importTaskItemsRef = React.useRef([]);
111
109
  const deletedImportTaskIdsRef = React.useRef(new Set());
@@ -153,6 +151,8 @@ function App() {
153
151
  }
154
152
  });
155
153
  const changeRouteRef = React.useRef(() => undefined);
154
+ const taskStateRefreshInFlightRef = React.useRef(null);
155
+ const taskStateRefreshQueuedRef = React.useRef(false);
156
156
  const [procurementContext, setProcurementContext] = React.useState({ supplier: null, order: null, metrics: null });
157
157
  const [purchaseImportTask, setPurchaseImportTask] = React.useState(null);
158
158
  const [purchaseReviewDraft, setPurchaseReviewDraft] = React.useState(null);
@@ -280,20 +280,22 @@ function App() {
280
280
  return normalized.importTasks;
281
281
  }
282
282
  }, []);
283
- const configureSalesFilesPrototypeCapability = React.useCallback((parameters) => {
284
- salesFilesPrototypeListenerCleanupRef.current?.();
285
- salesFilesPrototypeListenerCleanupRef.current = null;
286
- setSalesFilesPrototypeState(undefined);
287
- if (parameters.visualCheck !== true)
288
- return;
289
- const showSalesFileUpload = () => setSalesFilesPrototypeState('upload');
290
- const showSalesFileFormEditor = () => setSalesFilesPrototypeState('form-editor');
291
- window.addEventListener('sales-file-upload', showSalesFileUpload);
292
- window.addEventListener('sales-file-form-editor', showSalesFileFormEditor);
293
- salesFilesPrototypeListenerCleanupRef.current = () => {
294
- window.removeEventListener('sales-file-upload', showSalesFileUpload);
295
- window.removeEventListener('sales-file-form-editor', showSalesFileFormEditor);
296
- };
283
+ const pruneTaskTrackingState = React.useCallback((tasks) => {
284
+ const currentTaskIds = new Set(tasks.map(task => task.id));
285
+ for (const taskId of handledContractTasksRef.current) {
286
+ if (!currentTaskIds.has(taskId))
287
+ handledContractTasksRef.current.delete(taskId);
288
+ }
289
+ const currentRevisions = new Set();
290
+ for (const task of tasks) {
291
+ const revision = taskBusinessRefreshRevision(task);
292
+ if (revision)
293
+ currentRevisions.add(revision);
294
+ }
295
+ for (const revision of refreshedTaskBusinessRevisionsRef.current) {
296
+ if (!currentRevisions.has(revision))
297
+ refreshedTaskBusinessRevisionsRef.current.delete(revision);
298
+ }
297
299
  }, []);
298
300
  const loadRoute = React.useCallback(async (targetRoute, page = 1, pageSize = 20, filters = {}) => {
299
301
  const requestedPageSize = resolvePaginationPageSize(pageSize);
@@ -378,7 +380,7 @@ function App() {
378
380
  setBusy(gate.activeCount() + routeRequestGateRef.current.activeCount() + analyticsRequestsRef.current.activeCount() > 0);
379
381
  }
380
382
  }, [loadImportTasksFallback, loadRoute]);
381
- const loadTaskState = React.useCallback(async () => {
383
+ const loadTaskStateCore = React.useCallback(async () => {
382
384
  const active = contextRef.current;
383
385
  if (!active)
384
386
  return false;
@@ -414,6 +416,21 @@ function App() {
414
416
  gate.finish(requestId);
415
417
  }
416
418
  }, [loadImportTasksFallback]);
419
+ const loadTaskState = React.useCallback(async () => {
420
+ if (taskStateRefreshInFlightRef.current) {
421
+ taskStateRefreshQueuedRef.current = true;
422
+ return taskStateRefreshInFlightRef.current;
423
+ }
424
+ const run = loadTaskStateCore();
425
+ taskStateRefreshInFlightRef.current = run;
426
+ return run.finally(() => {
427
+ taskStateRefreshInFlightRef.current = null;
428
+ if (!taskStateRefreshQueuedRef.current)
429
+ return;
430
+ taskStateRefreshQueuedRef.current = false;
431
+ void loadTaskState();
432
+ });
433
+ }, [loadTaskStateCore]);
417
434
  const scheduleTaskStateRecovery = React.useCallback(() => {
418
435
  if (taskRecoveryTimerRef.current != null)
419
436
  return;
@@ -430,7 +447,8 @@ function App() {
430
447
  }, [loadTaskState]);
431
448
  React.useEffect(() => {
432
449
  importTaskItemsRef.current = data.importTasks.items;
433
- }, [data.importTasks.items]);
450
+ pruneTaskTrackingState(data.importTasks.items);
451
+ }, [data.importTasks.items, pruneTaskTrackingState]);
434
452
  React.useEffect(() => {
435
453
  void syncSelectedTaskForChat(taskChatSynchronizerRef.current, data.importTasks.items).catch(notifyTaskStatusActionError);
436
454
  }, [data.importTasks.items, notifyTaskStatusActionError]);
@@ -467,7 +485,6 @@ function App() {
467
485
  React.useEffect(() => {
468
486
  bridge.start({
469
487
  onInit(nextContext) {
470
- configureSalesFilesPrototypeCapability(asRecord(nextContext.initialQuery?.parameters));
471
488
  contextRef.current = nextContext;
472
489
  const normalized = normalizeWorkbenchData(nextContext.payload);
473
490
  bridge.configure({ uploadPolicy: normalized.uploadPolicy });
@@ -477,8 +494,6 @@ function App() {
477
494
  }
478
495
  });
479
496
  return () => {
480
- salesFilesPrototypeListenerCleanupRef.current?.();
481
- salesFilesPrototypeListenerCleanupRef.current = null;
482
497
  blobUrlOwnerRef.current.dispose();
483
498
  const previewRuntime = sourcePreviewRuntimePromise;
484
499
  sourcePreviewRuntimePromise = null;
@@ -488,7 +503,7 @@ function App() {
488
503
  void editorRuntime?.then(runtime => runtime.dispose?.(), () => undefined);
489
504
  bridge.destroy();
490
505
  };
491
- }, [configureSalesFilesPrototypeCapability, load]);
506
+ }, [load]);
492
507
  React.useEffect(() => () => toastTimers.dispose(), [toastTimers]);
493
508
  React.useEffect(() => {
494
509
  const timer = window.setTimeout(() => bridge.reportResize(), 0);
@@ -938,6 +953,11 @@ function App() {
938
953
  }
939
954
  }, [applyCustomerContractReanalysisResult, consumeCustomerContractReanalysisResult, loadTaskState, runMutation, scheduleTaskStateRecovery]);
940
955
  React.useEffect(() => {
956
+ const currentRunIds = new Set(data.customerContractAnalyses.items.map(recovery => recovery.run.id));
957
+ for (const runId of handledReanalysisResultsRef.current) {
958
+ if (!currentRunIds.has(runId))
959
+ handledReanalysisResultsRef.current.delete(runId);
960
+ }
941
961
  for (const recovery of data.customerContractAnalyses.items) {
942
962
  const recoverable = recovery.recoveryType === 'ACTIVE_POLL' || recovery.recoveryType === 'SUCCEEDED_RESULT';
943
963
  if (!recoverable)
@@ -1478,7 +1498,7 @@ function App() {
1478
1498
  : page === 'customer-contract-review'
1479
1499
  ? _jsx(ContractUploadCommand, { label: "\u4E0A\u4F20\u5BA2\u6237\u5408\u540C", busy: busy || actionBusy || customerContractBulkBusy, onFile: file => executeFileAction('upload_customer_contract', file, { documentType: 'CUSTOMER_CONTRACT' }), onError: message => setToast({ message, tone: 'error' }) })
1480
1500
  : undefined;
1481
- return (_jsxs(_Fragment, { children: [_jsxs(WorkbenchShell, { route: route, breadcrumbs: buildBreadcrumbs(route), title: meta.title, subtitle: page === 'home' ? undefined : meta.subtitle, topbarActions: _jsx(TaskStatusMenu, { items: taskStatusItems, busy: busy || actionBusy, onBulkDelete: bulkDeleteImportTasks, onActionError: notifyTaskStatusActionError }), commands: (page === 'controlled-goods' || page === 'sanctioned-companies') ? _jsx(CatalogCommands, { busy: busy || actionBusy, onUpload: () => setCatalogCommand('upload'), onCreate: () => setCatalogCommand('create'), onHistory: () => setCatalogCommand('history'), onClear: page === 'controlled-goods' ? () => setClearControlledCatalogOpen(true) : () => setClearSanctionCatalogOpen(true) }) : contractCommands, busy: busy || actionBusy, onRouteChange: changeRoute, onRefresh: () => void load(), children: [page === 'home' && _jsx(HomePage, {}), page === 'controlled-goods' && _jsx(ControlledGoodsPage, { collection: data.controlledGoods, batches: data.controlledBatches.items, busy: busy || actionBusy, onAction: executeAction, onFileAction: executeCatalogFileAction, onRefresh: load, onQuery: (query) => queryCatalog('controlledGoods', query), onIssueQuery: queryImportIssues, onHistoryQuery: queryCatalogHistory, tasks: data.importTasks.items, onResolveStalledImport: resolveStalledCatalogImport, onRefreshTasks: loadTaskState, importCompletion: importCompletions.CONTROLLED_CATALOG, importFailure: importFailures.CONTROLLED_CATALOG, onRetryImport: retryImport, command: catalogCommand, onCommandHandled: () => setCatalogCommand(null), onNotify: setToast }), page === 'sanctioned-companies' && _jsx(SanctionedCompaniesPage, { collection: data.sanctionedCompanies, batches: data.sanctionBatches.items, busy: busy || actionBusy, onAction: executeAction, onFileAction: executeCatalogFileAction, onRefresh: load, onQuery: (query) => queryCatalog('sanctionedCompanies', query), onIssueQuery: queryImportIssues, onHistoryQuery: queryCatalogHistory, tasks: data.importTasks.items, onResolveStalledImport: resolveStalledCatalogImport, onRefreshTasks: loadTaskState, importCompletion: importCompletions.SANCTION_CATALOG, importFailure: importFailures.SANCTION_CATALOG, onRetryImport: retryImport, command: catalogCommand, onCommandHandled: () => setCatalogCommand(null), onNotify: setToast }), page === 'procurement' && route.type === 'procurement' && _jsx(ProcurementPage, { route: route, suppliers: data.suppliers, purchaseOrders: data.purchaseOrders, purchaseLines: data.purchaseLines, supplier: procurementContext.supplier, order: procurementContext.order, metrics: procurementContext.metrics, importTask: purchaseImportTask, reviewDraft: purchaseReviewDraft, busy: busy || actionBusy, onQuery: queryProcurement, onAction: executeAction, onFileAction: executeFileAction, onRouteChange: changeRoute, onRetryImport: retryContractImport, onReviewClose: closePurchaseReview, onNotify: setToast }), page === 'customer-contract-review' && _jsx(CustomerContractsPage, { collection: data.customerContracts, busy: busy || actionBusy || customerContractBulkBusy, importReviewState: customerImportState, onQuery: queryCustomerContracts, onAction: executeAction, onFileAction: executeFileAction, onReanalyze: reanalyzeCustomerContract, onLoadReview: loadCustomerContractReview, onReviewClose: closeCustomerReview, reanalysisState: customerReanalysisState, onRetryImport: retryContractImport, onQueryRequested: rememberCustomerContractQuery, onQueryCancelRegistration: registerCustomerContractQueryCancel, onBulkReconcile: reconcileCustomerContractsAfterBulk, onBulkBusyChange: setCustomerContractBulkBusy, onNotify: setToast }), page === 'sales' && route.type === 'sales' && _jsx(SalesPage, { route: route, customers: data.salesCustomers, orders: data.salesOrders, lines: data.salesLines, busy: busy || actionBusy, onQuery: querySales, onAction: executeAction, onRouteChange: changeRoute, onOpenFiles: openSalesFiles, onNotify: setToast }), page === 'sales-files' && _jsx(SalesFilesPage, { orders: data.salesFileOrders, history: data.generatedFiles, activeOrder: activeSalesFileOrder, visualPrototypeState: salesFilesPrototypeState, onPrototypeStateClose: () => setSalesFilesPrototypeState(undefined), banks: data.settings.banks, busy: busy || actionBusy, externalBulkBusy: salesFileBulkBusy || salesFileBulkReconcileBusy, onQuery: querySalesFiles, onBulkReconcile: reconcileGeneratedFilesAfterBulk, onBulkBusyChange: setSalesFileBulkBusy, onBulkViewActiveChange: setGeneratedFilesBulkViewActive, onAction: executeSalesFileAction, onOpenSalesOrder: selectSalesFileOrder, onDownload: file => executeAction('download_sales_file', { id: file.id }).then(() => undefined), onEdit: async (file) => setEditingSalesFile(file), onNotify: setToast }), page === 'analytics' && _jsx(AnalyticsPage, { data: data.analytics, customerOptions: uniqueNames([...data.customers.items, ...data.analytics.dashboard.customerValueTop5, ...data.analytics.dashboard.receivableRiskTop5]), supplierOptions: uniqueNames([...data.suppliers.items, ...data.analytics.dashboard.supplierRiskTop5]), committedQuery: analyticsCommittedQuery, detailRefreshKey: analyticsDetailRefreshKey, busy: busy || actionBusy, error: analyticsError, onQuery: queryAnalytics, onDetailQuery: queryAnalyticsDetails, onDetailAction: openAnalyticsDetailAction, onExportXlsx: exportAnalytics }), page === 'settings' && _jsx(SettingsPage, { data: data.settings, busy: busy || actionBusy, externalBulkBusy: settingsRateBulkBusy || settingsRateBulkReconcileBusy, onSave: saveSettings, onRestoreDefault: restoreSettings, onDeleteRate: deleteSettingsRate, onRateQuery: queryMonthlyRates, onBulkReconcile: reconcileSettingsRatesAfterBulk, onBulkBusyChange: setSettingsRateBulkBusy, onBulkViewActiveChange: setSettingsRatesBulkViewActive, onUploadLogo: uploadCompanyLogo, onDiscardLogo: discardCompanyLogo, onClearBusinessData: clearBusinessData, onNotify: setToast }), !['home', 'controlled-goods', 'sanctioned-companies', 'procurement', 'customer-contract-review', 'sales', 'sales-files', 'analytics', 'settings'].includes(page) && _jsxs("section", { className: "tcw-page-placeholder", "data-page": page, children: [_jsx(InfoNotice, { children: "\u9875\u9762\u57FA\u7840\u7ED3\u6784\u5DF2\u5C31\u7EEA\uFF0C\u4E1A\u52A1\u5185\u5BB9\u5C06\u5728\u5BF9\u5E94\u9875\u9762\u6A21\u5757\u4E2D\u52A0\u8F7D\u3002" }), _jsx("div", { className: "tcw-placeholder-body", "aria-label": `${meta.title}页面内容` })] })] }), _jsx(LoadingBlocker, { visible: busy || actionBusy || customerContractBulkBusy, label: actionBusy || customerContractBulkBusy ? '操作处理中' : '数据加载中' }), _jsx(ToastStack, { items: toasts, onClose: closeToast }), editingSalesFile && _jsx(SalesFileEditor, { file: editingSalesFile, runMutation: runMutation, onNotify: setToast, onClose: () => setEditingSalesFile(null), onSaved: () => {
1501
+ return (_jsxs(_Fragment, { children: [_jsxs(WorkbenchShell, { route: route, breadcrumbs: buildBreadcrumbs(route), title: meta.title, subtitle: page === 'home' ? undefined : meta.subtitle, topbarActions: _jsx(TaskStatusMenu, { items: taskStatusItems, busy: busy || actionBusy, onBulkDelete: bulkDeleteImportTasks, onActionError: notifyTaskStatusActionError }), commands: (page === 'controlled-goods' || page === 'sanctioned-companies') ? _jsx(CatalogCommands, { busy: busy || actionBusy, onUpload: () => setCatalogCommand('upload'), onCreate: () => setCatalogCommand('create'), onHistory: () => setCatalogCommand('history'), onClear: page === 'controlled-goods' ? () => setClearControlledCatalogOpen(true) : () => setClearSanctionCatalogOpen(true) }) : contractCommands, busy: busy || actionBusy, onRouteChange: changeRoute, onRefresh: () => void load(), children: [page === 'home' && _jsx(HomePage, {}), page === 'controlled-goods' && _jsx(ControlledGoodsPage, { collection: data.controlledGoods, batches: data.controlledBatches.items, busy: busy || actionBusy, onAction: executeAction, onFileAction: executeCatalogFileAction, onRefresh: load, onQuery: (query) => queryCatalog('controlledGoods', query), onIssueQuery: queryImportIssues, onHistoryQuery: queryCatalogHistory, tasks: data.importTasks.items, onResolveStalledImport: resolveStalledCatalogImport, onRefreshTasks: loadTaskState, importCompletion: importCompletions.CONTROLLED_CATALOG, importFailure: importFailures.CONTROLLED_CATALOG, onRetryImport: retryImport, command: catalogCommand, onCommandHandled: () => setCatalogCommand(null), onNotify: setToast }), page === 'sanctioned-companies' && _jsx(SanctionedCompaniesPage, { collection: data.sanctionedCompanies, batches: data.sanctionBatches.items, busy: busy || actionBusy, onAction: executeAction, onFileAction: executeCatalogFileAction, onRefresh: load, onQuery: (query) => queryCatalog('sanctionedCompanies', query), onIssueQuery: queryImportIssues, onHistoryQuery: queryCatalogHistory, tasks: data.importTasks.items, onResolveStalledImport: resolveStalledCatalogImport, onRefreshTasks: loadTaskState, importCompletion: importCompletions.SANCTION_CATALOG, importFailure: importFailures.SANCTION_CATALOG, onRetryImport: retryImport, command: catalogCommand, onCommandHandled: () => setCatalogCommand(null), onNotify: setToast }), page === 'procurement' && route.type === 'procurement' && _jsx(ProcurementPage, { route: route, suppliers: data.suppliers, purchaseOrders: data.purchaseOrders, purchaseLines: data.purchaseLines, supplier: procurementContext.supplier, order: procurementContext.order, metrics: procurementContext.metrics, importTask: purchaseImportTask, reviewDraft: purchaseReviewDraft, busy: busy || actionBusy, onQuery: queryProcurement, onAction: executeAction, onFileAction: executeFileAction, onRouteChange: changeRoute, onRetryImport: retryContractImport, onReviewClose: closePurchaseReview, onNotify: setToast }), page === 'customer-contract-review' && _jsx(CustomerContractsPage, { collection: data.customerContracts, busy: busy || actionBusy || customerContractBulkBusy, importReviewState: customerImportState, onQuery: queryCustomerContracts, onAction: executeAction, onFileAction: executeFileAction, onReanalyze: reanalyzeCustomerContract, onLoadReview: loadCustomerContractReview, onReviewClose: closeCustomerReview, reanalysisState: customerReanalysisState, onRetryImport: retryContractImport, onQueryRequested: rememberCustomerContractQuery, onQueryCancelRegistration: registerCustomerContractQueryCancel, onBulkReconcile: reconcileCustomerContractsAfterBulk, onBulkBusyChange: setCustomerContractBulkBusy, onNotify: setToast }), page === 'sales' && route.type === 'sales' && _jsx(SalesPage, { route: route, customers: data.salesCustomers, orders: data.salesOrders, lines: data.salesLines, busy: busy || actionBusy, onQuery: querySales, onAction: executeAction, onRouteChange: changeRoute, onOpenFiles: openSalesFiles, onNotify: setToast }), page === 'sales-files' && _jsx(SalesFilesPage, { orders: data.salesFileOrders, history: data.generatedFiles, activeOrder: activeSalesFileOrder, banks: data.settings.banks, busy: busy || actionBusy, externalBulkBusy: salesFileBulkBusy || salesFileBulkReconcileBusy, onQuery: querySalesFiles, onBulkReconcile: reconcileGeneratedFilesAfterBulk, onBulkBusyChange: setSalesFileBulkBusy, onBulkViewActiveChange: setGeneratedFilesBulkViewActive, onAction: executeSalesFileAction, onOpenSalesOrder: selectSalesFileOrder, onDownload: file => executeAction('download_sales_file', { id: file.id }).then(() => undefined), onEdit: async (file) => setEditingSalesFile(file), onNotify: setToast }), page === 'analytics' && _jsx(AnalyticsPage, { data: data.analytics, customerOptions: uniqueNames([...data.customers.items, ...data.analytics.dashboard.customerValueTop5, ...data.analytics.dashboard.receivableRiskTop5]), supplierOptions: uniqueNames([...data.suppliers.items, ...data.analytics.dashboard.supplierRiskTop5]), committedQuery: analyticsCommittedQuery, detailRefreshKey: analyticsDetailRefreshKey, busy: busy || actionBusy, error: analyticsError, onQuery: queryAnalytics, onDetailQuery: queryAnalyticsDetails, onDetailAction: openAnalyticsDetailAction, onExportXlsx: exportAnalytics }), page === 'settings' && _jsx(SettingsPage, { data: data.settings, busy: busy || actionBusy, externalBulkBusy: settingsRateBulkBusy || settingsRateBulkReconcileBusy, onSave: saveSettings, onRestoreDefault: restoreSettings, onDeleteRate: deleteSettingsRate, onRateQuery: queryMonthlyRates, onBulkReconcile: reconcileSettingsRatesAfterBulk, onBulkBusyChange: setSettingsRateBulkBusy, onBulkViewActiveChange: setSettingsRatesBulkViewActive, onUploadLogo: uploadCompanyLogo, onDiscardLogo: discardCompanyLogo, onClearBusinessData: clearBusinessData, onNotify: setToast }), !['home', 'controlled-goods', 'sanctioned-companies', 'procurement', 'customer-contract-review', 'sales', 'sales-files', 'analytics', 'settings'].includes(page) && _jsxs("section", { className: "tcw-page-placeholder", "data-page": page, children: [_jsx(InfoNotice, { children: "\u9875\u9762\u57FA\u7840\u7ED3\u6784\u5DF2\u5C31\u7EEA\uFF0C\u4E1A\u52A1\u5185\u5BB9\u5C06\u5728\u5BF9\u5E94\u9875\u9762\u6A21\u5757\u4E2D\u52A0\u8F7D\u3002" }), _jsx("div", { className: "tcw-placeholder-body", "aria-label": `${meta.title}页面内容` })] })] }), _jsx(LoadingBlocker, { visible: busy || actionBusy || customerContractBulkBusy, label: actionBusy || customerContractBulkBusy ? '操作处理中' : '数据加载中' }), _jsx(ToastStack, { items: toasts, onClose: closeToast }), editingSalesFile && _jsx(SalesFileEditor, { file: editingSalesFile, runMutation: runMutation, onNotify: setToast, onClose: () => setEditingSalesFile(null), onSaved: () => {
1482
1502
  setEditingSalesFile(null);
1483
1503
  if (page === 'analytics') {
1484
1504
  setAnalyticsDetailRefreshKey(current => current + 1);