@chenchaolong/plugin-trade-compliance-workbench 1.0.52 → 1.0.53

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.
Files changed (112) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/analytics-export.d.ts.map +1 -1
  3. package/dist/lib/analytics-export.js +1 -3
  4. package/dist/lib/analytics-export.js.map +1 -1
  5. package/dist/lib/analytics-query.d.ts.map +1 -1
  6. package/dist/lib/analytics-query.js +13 -20
  7. package/dist/lib/analytics-query.js.map +1 -1
  8. package/dist/lib/catalog-import/confidence.d.ts +1 -0
  9. package/dist/lib/catalog-import/confidence.d.ts.map +1 -1
  10. package/dist/lib/catalog-import/confidence.js +12 -0
  11. package/dist/lib/catalog-import/confidence.js.map +1 -1
  12. package/dist/lib/catalog-import/docx.parser.d.ts.map +1 -1
  13. package/dist/lib/catalog-import/docx.parser.js +7 -6
  14. package/dist/lib/catalog-import/docx.parser.js.map +1 -1
  15. package/dist/lib/catalog-import/pdf-layout.parser.d.ts.map +1 -1
  16. package/dist/lib/catalog-import/pdf-layout.parser.js +8 -7
  17. package/dist/lib/catalog-import/pdf-layout.parser.js.map +1 -1
  18. package/dist/lib/catalog-import/spreadsheet.parser.d.ts.map +1 -1
  19. package/dist/lib/catalog-import/spreadsheet.parser.js +12 -13
  20. package/dist/lib/catalog-import/spreadsheet.parser.js.map +1 -1
  21. package/dist/lib/demo-data.definitions.js +1 -1
  22. package/dist/lib/demo-data.definitions.js.map +1 -1
  23. package/dist/lib/entities/catalog.entity.d.ts +7 -1
  24. package/dist/lib/entities/catalog.entity.d.ts.map +1 -1
  25. package/dist/lib/entities/catalog.entity.js +26 -2
  26. package/dist/lib/entities/catalog.entity.js.map +1 -1
  27. package/dist/lib/entities/customer.entity.js.map +1 -1
  28. package/dist/lib/entities/procurement.entity.js.map +1 -1
  29. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  30. package/dist/lib/remote-components/trade-compliance-workbench/app.js +8 -8
  31. package/dist/lib/remote-ui/action-execution.d.ts +3 -0
  32. package/dist/lib/remote-ui/action-execution.d.ts.map +1 -1
  33. package/dist/lib/remote-ui/action-execution.js +21 -1
  34. package/dist/lib/remote-ui/action-execution.js.map +1 -1
  35. package/dist/lib/remote-ui/app-source.js +168 -151
  36. package/dist/lib/remote-ui/app-source.js.map +1 -1
  37. package/dist/lib/remote-ui/bridge.d.ts.map +1 -1
  38. package/dist/lib/remote-ui/components/forms.d.ts +5 -2
  39. package/dist/lib/remote-ui/components/forms.d.ts.map +1 -1
  40. package/dist/lib/remote-ui/components/forms.js +4 -4
  41. package/dist/lib/remote-ui/components/forms.js.map +1 -1
  42. package/dist/lib/remote-ui/components/layout.d.ts.map +1 -1
  43. package/dist/lib/remote-ui/components/layout.js +31 -4
  44. package/dist/lib/remote-ui/components/layout.js.map +1 -1
  45. package/dist/lib/remote-ui/components/task-status.js +2 -2
  46. package/dist/lib/remote-ui/components/task-status.js.map +1 -1
  47. package/dist/lib/remote-ui/contract-import.d.ts +1 -0
  48. package/dist/lib/remote-ui/contract-import.d.ts.map +1 -1
  49. package/dist/lib/remote-ui/contract-import.js.map +1 -1
  50. package/dist/lib/remote-ui/contracts.d.ts +11 -0
  51. package/dist/lib/remote-ui/contracts.d.ts.map +1 -1
  52. package/dist/lib/remote-ui/import-task-status.d.ts +5 -2
  53. package/dist/lib/remote-ui/import-task-status.d.ts.map +1 -1
  54. package/dist/lib/remote-ui/import-task-status.js +13 -8
  55. package/dist/lib/remote-ui/import-task-status.js.map +1 -1
  56. package/dist/lib/remote-ui/pages/analytics-detail-list.d.ts.map +1 -1
  57. package/dist/lib/remote-ui/pages/analytics-detail-list.js.map +1 -1
  58. package/dist/lib/remote-ui/pages/catalog-import-history.d.ts +47 -2
  59. package/dist/lib/remote-ui/pages/catalog-import-history.d.ts.map +1 -1
  60. package/dist/lib/remote-ui/pages/catalog-import-history.js +128 -8
  61. package/dist/lib/remote-ui/pages/catalog-import-history.js.map +1 -1
  62. package/dist/lib/remote-ui/pages/catalogs.css +55 -1
  63. package/dist/lib/remote-ui/pages/catalogs.d.ts +7 -4
  64. package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
  65. package/dist/lib/remote-ui/pages/catalogs.js +40 -19
  66. package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
  67. package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -1
  68. package/dist/lib/remote-ui/pages/contracts.js +0 -16
  69. package/dist/lib/remote-ui/pages/contracts.js.map +1 -1
  70. package/dist/lib/remote-ui/pages/files-prototype.css +2 -2
  71. package/dist/lib/remote-ui/pages/files.d.ts +1 -1
  72. package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
  73. package/dist/lib/remote-ui/pages/files.js +6 -6
  74. package/dist/lib/remote-ui/pages/files.js.map +1 -1
  75. package/dist/lib/remote-ui/pages/home.d.ts.map +1 -1
  76. package/dist/lib/remote-ui/pages/home.js +2 -2
  77. package/dist/lib/remote-ui/pages/home.js.map +1 -1
  78. package/dist/lib/remote-ui/pages/procurement.d.ts +6 -1
  79. package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
  80. package/dist/lib/remote-ui/pages/procurement.js +77 -16
  81. package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
  82. package/dist/lib/remote-ui/procurement-contracts.d.ts +7 -1
  83. package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -1
  84. package/dist/lib/remote-ui/procurement-contracts.js.map +1 -1
  85. package/dist/lib/remote-ui/sidebar-state.d.ts +1 -0
  86. package/dist/lib/remote-ui/sidebar-state.d.ts.map +1 -1
  87. package/dist/lib/remote-ui/sidebar-state.js +3 -0
  88. package/dist/lib/remote-ui/sidebar-state.js.map +1 -1
  89. package/dist/lib/sales-file.service.js +1 -1
  90. package/dist/lib/sales-file.service.js.map +1 -1
  91. package/dist/lib/schema-maintenance.d.ts.map +1 -1
  92. package/dist/lib/schema-maintenance.js +125 -0
  93. package/dist/lib/schema-maintenance.js.map +1 -1
  94. package/dist/lib/types.d.ts +3 -0
  95. package/dist/lib/types.d.ts.map +1 -1
  96. package/dist/lib/types.js +1 -0
  97. package/dist/lib/types.js.map +1 -1
  98. package/dist/lib/workbench-action-contract.d.ts +1 -1
  99. package/dist/lib/workbench-action-contract.d.ts.map +1 -1
  100. package/dist/lib/workbench-action-contract.js +3 -1
  101. package/dist/lib/workbench-action-contract.js.map +1 -1
  102. package/dist/lib/workbench-view.provider.d.ts +12 -9
  103. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  104. package/dist/lib/workbench-view.provider.js +38 -6
  105. package/dist/lib/workbench-view.provider.js.map +1 -1
  106. package/dist/lib/workbench.middleware.js +7 -7
  107. package/dist/lib/workbench.middleware.js.map +1 -1
  108. package/dist/lib/workbench.service.d.ts +31 -3
  109. package/dist/lib/workbench.service.d.ts.map +1 -1
  110. package/dist/lib/workbench.service.js +756 -145
  111. package/dist/lib/workbench.service.js.map +1 -1
  112. package/package.json +2 -1
@@ -16,6 +16,9 @@ type ActionNotificationPolicy = 'silent' | {
16
16
  export type ActionExecutionOptions = {
17
17
  suppressRefresh?: boolean;
18
18
  };
19
+ export declare function createMutationCoordinator(): {
20
+ run<T>(operation: () => Promise<T>, onBusyChange?: (busy: boolean) => void): Promise<T>;
21
+ };
19
22
  export declare const ACTION_NOTIFICATION_POLICIES: Record<WorkbenchActionKey, ActionNotificationPolicy>;
20
23
  export declare function executeWorkbenchAction<T>({ actionKey, mutate, refresh, notify, onResult, resolveMessage, suppressRefresh }: {
21
24
  actionKey: WorkbenchActionKey;
@@ -1 +1 @@
1
- {"version":3,"file":"action-execution.d.ts","sourceRoot":"","sources":["../../../src/lib/remote-ui/action-execution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAC/F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAE9C,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI;IACtC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,CAAC,CAAA;IACR,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,KAAK,wBAAwB,GAAG,QAAQ,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAChF,MAAM,MAAM,sBAAsB,GAAG;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAElE,eAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC,kBAAkB,EAAE,wBAAwB,CAsC7F,CAAA;AAiBD,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAqC,EAAE,eAAe,EAAE,EAAE;IACxJ,SAAS,EAAE,kBAAkB,CAAA;IAC7B,MAAM,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;IACtC,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IAC5C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,MAAM,CAAA;CACrD,GAAG,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAiBpD"}
1
+ {"version":3,"file":"action-execution.d.ts","sourceRoot":"","sources":["../../../src/lib/remote-ui/action-execution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAC/F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAE9C,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI;IACtC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,CAAC,CAAA;IACR,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,KAAK,wBAAwB,GAAG,QAAQ,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAA;AAChF,MAAM,MAAM,sBAAsB,GAAG;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAElE,wBAAgB,yBAAyB;QAG3B,CAAC,aAAa,MAAM,OAAO,CAAC,CAAC,CAAC,iBAAgB,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,GAAqB,OAAO,CAAC,CAAC,CAAC;EAOjH;AAED,eAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC,kBAAkB,EAAE,wBAAwB,CAwC7F,CAAA;AAiBD,wBAAsB,sBAAsB,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAqC,EAAE,eAAe,EAAE,EAAE;IACxJ,SAAS,EAAE,kBAAkB,CAAA;IAC7B,MAAM,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;IACtC,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IAC5C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,MAAM,CAAA;CACrD,GAAG,sBAAsB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAiBpD"}
@@ -1,4 +1,22 @@
1
1
  import { isWorkbenchActionKey } from '../workbench-action-contract.js';
2
+ export function createMutationCoordinator() {
3
+ let running = false;
4
+ return {
5
+ async run(operation, onBusyChange = () => undefined) {
6
+ if (running)
7
+ throw new Error('操作正在执行,请稍候');
8
+ running = true;
9
+ onBusyChange(true);
10
+ try {
11
+ return await operation();
12
+ }
13
+ finally {
14
+ running = false;
15
+ onBusyChange(false);
16
+ }
17
+ }
18
+ };
19
+ }
2
20
  export const ACTION_NOTIFICATION_POLICIES = {
3
21
  refresh: 'silent',
4
22
  load_excel_editor_runtime: 'silent',
@@ -31,12 +49,14 @@ export const ACTION_NOTIFICATION_POLICIES = {
31
49
  download_sales_file: { tone: 'success', fallback: '销售文件下载成功' },
32
50
  download_import_issues: { tone: 'success', fallback: '导入问题 Excel 下载成功' },
33
51
  retry_import_task: 'silent',
52
+ resolve_stalled_catalog_import: 'silent',
34
53
  delete_import_task: 'silent',
35
54
  download_source_file: 'silent',
36
55
  preview_source_file: 'silent',
37
56
  save_edited_sales_file: 'silent',
38
57
  search_hs_code: 'silent',
39
- get_hs_code_detail: 'silent'
58
+ get_hs_code_detail: 'silent',
59
+ translate_purchase_product_name: 'silent'
40
60
  };
41
61
  const refreshActions = new Set([
42
62
  'save_controlled_goods',
@@ -1 +1 @@
1
- {"version":3,"file":"action-execution.js","sourceRoot":"","sources":["../../../src/lib/remote-ui/action-execution.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"action-execution.js","sourceRoot":"","sources":["../../../src/lib/remote-ui/action-execution.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@ import { AnalyticsComplianceCategoryKeys } from './analytics-contracts.js';
24
24
  import { ContractImportError, findRecoverableContractImport, findResumableContractImports, runContractImportOrchestration, runCustomerContractReanalysis, taskTransition } from './contract-import.js';
25
25
  import { createTaskChatSynchronizer, importTaskFromStart, importTaskProgressPercent, isLocalUploadPlaceholder, mergeImportTaskItems, removeImportTaskItem, taskBusinessRefreshRevision, taskRouteRefreshTarget } from './import-task-status.js';
26
26
  import { compareTaskBusinessOrder, createPendingResultQueue, createPollingTaskTracker, isAbortError, isPollingObservationTimeout, submitAndRefreshTaskState } from './polling.js';
27
- import { ACTION_NOTIFICATION_POLICIES, executeWorkbenchAction } from './action-execution.js';
27
+ import { createMutationCoordinator, executeWorkbenchAction } from './action-execution.js';
28
28
  import { createDeferredBulkReconcileCoordinator, runBulkDelete } from './bulk-delete.js';
29
29
  import './styles.css';
30
30
  const pageMeta = {
@@ -93,6 +93,7 @@ function App() {
93
93
  const deletedImportTaskIdsRef = React.useRef(new Set());
94
94
  const [busy, setBusy] = React.useState(false);
95
95
  const [actionBusy, setActionBusy] = React.useState(false);
96
+ const mutationCoordinatorRef = React.useRef(createMutationCoordinator());
96
97
  const [customerContractBulkBusy, setCustomerContractBulkBusy] = React.useState(false);
97
98
  const [salesFileBulkBusy, setSalesFileBulkBusy] = React.useState(false);
98
99
  const [settingsRateBulkBusy, setSettingsRateBulkBusy] = React.useState(false);
@@ -122,8 +123,13 @@ function App() {
122
123
  key: 'tradeComplianceTask', context: { task, relatedRecords }
123
124
  });
124
125
  },
125
- sendMessage: async (text, clientMessageId) => {
126
- await bridge.invokeClientCommand('assistant.chat.send_message', { text, clientMessageId });
126
+ openConversation: async (conversationId, task) => {
127
+ await bridge.invokeClientCommand('assistant.conversation.open', {
128
+ target: 'assistant.conversation',
129
+ conversationId,
130
+ contextKey: 'tradeComplianceTask',
131
+ context: { task }
132
+ });
127
133
  }
128
134
  });
129
135
  const changeRouteRef = React.useRef(() => undefined);
@@ -420,27 +426,19 @@ function App() {
420
426
  React.useEffect(() => {
421
427
  toastTimers.sync(toasts);
422
428
  }, [toastTimers, toasts]);
429
+ const runMutation = React.useCallback((operation) => mutationCoordinatorRef.current.run(operation, setActionBusy), []);
423
430
  const executeAction = React.useCallback(async (actionKey, input, options = {}) => {
424
- const blocksPage = ACTION_NOTIFICATION_POLICIES[actionKey] !== 'silent';
425
- if (blocksPage)
426
- setActionBusy(true);
427
- try {
428
- return await executeWorkbenchAction({
429
- actionKey,
430
- mutate: async () => asActionResult(unwrapResponse(await bridge.executeAction(actionKey, input))),
431
- refresh: load,
432
- notify: setToast,
433
- resolveMessage: resolveActionMessage,
434
- onResult: result => { if (actionKey === 'download_import_issues' || actionKey === 'download_sales_file')
435
- downloadActionFile(result.data); },
436
- ...options
437
- });
438
- }
439
- finally {
440
- if (blocksPage)
441
- setActionBusy(false);
442
- }
443
- }, [load]);
431
+ return runMutation(() => executeWorkbenchAction({
432
+ actionKey,
433
+ mutate: async () => asActionResult(unwrapResponse(await bridge.executeAction(actionKey, input))),
434
+ refresh: load,
435
+ notify: setToast,
436
+ resolveMessage: resolveActionMessage,
437
+ onResult: result => { if (actionKey === 'download_import_issues' || actionKey === 'download_sales_file')
438
+ downloadActionFile(result.data); },
439
+ ...options
440
+ }));
441
+ }, [load, runMutation]);
444
442
  const executeSalesFileAction = React.useCallback((actionKey, input, options = {}) => executeAction(actionKey, input, options), [executeAction]);
445
443
  const schedulePendingReviewPresentation = React.useCallback(() => {
446
444
  if (reviewPresentationTimerRef.current != null)
@@ -602,35 +600,37 @@ function App() {
602
600
  return false;
603
601
  }, [upsertLocalImportTask]);
604
602
  const executeFileAction = React.useCallback(async (actionKey, file, input, notifyError = true) => {
605
- try {
606
- const raw = await bridge.executeFileAction(actionKey, file, input);
607
- const start = asActionResult(unwrapResponse(raw));
608
- const task = importTaskFromStart(start, actionKey, input, file);
609
- if (task) {
610
- upsertLocalImportTask(task);
611
- if (isLocalUploadPlaceholder(task))
612
- scheduleTaskStateRecovery();
613
- else
614
- startTrackingUploadedImportTask(task);
603
+ return runMutation(async () => {
604
+ try {
605
+ const raw = await bridge.executeFileAction(actionKey, file, input);
606
+ const start = asActionResult(unwrapResponse(raw));
607
+ const task = importTaskFromStart(start, actionKey, input, file);
608
+ if (task) {
609
+ upsertLocalImportTask(task);
610
+ if (isLocalUploadPlaceholder(task))
611
+ scheduleTaskStateRecovery();
612
+ else
613
+ startTrackingUploadedImportTask(task);
614
+ }
615
+ if (start.success === false)
616
+ throw actionResponseError(start, '上传失败');
617
+ await loadTaskState().catch((refreshError) => {
618
+ notifyTaskRecoveryRefreshError(refreshError instanceof Error ? refreshError : new Error(String(refreshError)));
619
+ });
620
+ await presentImmediateContractUploadReview(actionKey, start);
621
+ setToast({ message: resolveActionMessage(start) || '解析任务已提交', tone: 'info' });
622
+ return start;
615
623
  }
616
- if (start.success === false)
617
- throw actionResponseError(start, '上传失败');
618
- await loadTaskState().catch((refreshError) => {
619
- notifyTaskRecoveryRefreshError(refreshError instanceof Error ? refreshError : new Error(String(refreshError)));
620
- });
621
- await presentImmediateContractUploadReview(actionKey, start);
622
- setToast({ message: resolveActionMessage(start) || '解析任务已提交', tone: 'info' });
623
- return start;
624
- }
625
- catch (error) {
626
- await loadTaskState().catch((refreshError) => {
627
- notifyTaskRecoveryRefreshError(refreshError instanceof Error ? refreshError : new Error(String(refreshError)));
628
- });
629
- if (notifyError && !(error instanceof CatalogImportError))
630
- setToast({ message: error instanceof Error ? error.message : '上传失败', tone: 'error' });
631
- throw error;
632
- }
633
- }, [loadTaskState, notifyTaskRecoveryRefreshError, presentImmediateContractUploadReview, scheduleTaskStateRecovery, startTrackingUploadedImportTask, upsertLocalImportTask]);
624
+ catch (error) {
625
+ await loadTaskState().catch((refreshError) => {
626
+ notifyTaskRecoveryRefreshError(refreshError instanceof Error ? refreshError : new Error(String(refreshError)));
627
+ });
628
+ if (notifyError && !(error instanceof CatalogImportError))
629
+ setToast({ message: error instanceof Error ? error.message : '上传失败', tone: 'error' });
630
+ throw error;
631
+ }
632
+ });
633
+ }, [loadTaskState, notifyTaskRecoveryRefreshError, presentImmediateContractUploadReview, runMutation, scheduleTaskStateRecovery, startTrackingUploadedImportTask, upsertLocalImportTask]);
634
634
  const executeCatalogFileAction = React.useCallback((actionKey, file, input) => executeFileAction(actionKey, file, input, false), [executeFileAction]);
635
635
  React.useEffect(() => {
636
636
  const tasks = data.importTasks.items;
@@ -673,52 +673,58 @@ function App() {
673
673
  }
674
674
  }, [data.importTasks.items, handleBackgroundImportTrackingError, orchestrateContractImport]);
675
675
  const retryImport = React.useCallback(async (taskId) => {
676
- try {
677
- const raw = await submitAndRefreshTaskState(() => bridge.executeAction('retry_import_task', { taskId }), loadTaskState, notifyTaskRecoveryRefreshError);
678
- const start = asActionResult(unwrapResponse(raw));
679
- if (start.success === false)
680
- throw actionResponseError(start, '重新解析失败');
681
- setToast({ message: resolveActionMessage(start) || '重新解析任务已提交', tone: 'info' });
682
- }
683
- catch (error) {
684
- if (!(error instanceof CatalogImportError))
685
- setToast({ message: error instanceof Error ? error.message : '重新解析失败', tone: 'error' });
686
- throw error;
687
- }
688
- }, [loadTaskState, notifyTaskRecoveryRefreshError]);
676
+ return runMutation(async () => {
677
+ try {
678
+ const raw = await submitAndRefreshTaskState(() => bridge.executeAction('retry_import_task', { taskId }), loadTaskState, notifyTaskRecoveryRefreshError);
679
+ const start = asActionResult(unwrapResponse(raw));
680
+ if (start.success === false)
681
+ throw actionResponseError(start, '重新解析失败');
682
+ setToast({ message: resolveActionMessage(start) || '重新解析任务已提交', tone: 'info' });
683
+ }
684
+ catch (error) {
685
+ if (!(error instanceof CatalogImportError))
686
+ setToast({ message: error instanceof Error ? error.message : '重新解析失败', tone: 'error' });
687
+ throw error;
688
+ }
689
+ });
690
+ }, [loadTaskState, notifyTaskRecoveryRefreshError, runMutation]);
689
691
  const retryContractImport = React.useCallback(async (taskId) => {
690
- try {
691
- const raw = await submitAndRefreshTaskState(() => bridge.executeAction('retry_import_task', { taskId }), loadTaskState, notifyTaskRecoveryRefreshError);
692
- const start = asActionResult(unwrapResponse(raw));
693
- if (start.success === false)
694
- throw actionResponseError(start, '重新解析失败');
695
- setToast({ message: resolveActionMessage(start) || '重新解析任务已提交', tone: 'info' });
696
- }
697
- catch (error) {
698
- setToast({ message: error instanceof Error ? error.message : '重新解析失败', tone: 'error' });
699
- throw error;
700
- }
701
- }, [loadTaskState, notifyTaskRecoveryRefreshError]);
692
+ return runMutation(async () => {
693
+ try {
694
+ const raw = await submitAndRefreshTaskState(() => bridge.executeAction('retry_import_task', { taskId }), loadTaskState, notifyTaskRecoveryRefreshError);
695
+ const start = asActionResult(unwrapResponse(raw));
696
+ if (start.success === false)
697
+ throw actionResponseError(start, '重新解析失败');
698
+ setToast({ message: resolveActionMessage(start) || '重新解析任务已提交', tone: 'info' });
699
+ }
700
+ catch (error) {
701
+ setToast({ message: error instanceof Error ? error.message : '重新解析失败', tone: 'error' });
702
+ throw error;
703
+ }
704
+ });
705
+ }, [loadTaskState, notifyTaskRecoveryRefreshError, runMutation]);
702
706
  const deleteImportTaskRecord = React.useCallback(async (task, options) => {
703
- const result = asActionResult(unwrapResponse(await bridge.executeAction('delete_import_task', { taskId: task.id })));
704
- if (result.success === false)
705
- throw actionResponseError(result, '任务删除失败');
706
- catalogTaskTrackerRef.current.abort(task.id);
707
- contractTaskTrackerRef.current.abort(task.id);
708
- removeLocalImportTask(task.id);
709
- if (taskChatSynchronizerRef.current?.isSelected(task.id)) {
710
- taskChatSynchronizerRef.current.clear();
711
- await bridge.invokeClientCommand('assistant.context.set', {
712
- key: 'tradeComplianceTask', clear: true
713
- }).catch(() => undefined);
714
- await bridge.invokeClientCommand('assistant.chat.send_message', {
715
- text: `任务“${task.sourceFileName || documentTypeLabel(task.documentType)}”已取消并删除。`,
716
- clientMessageId: `trade-task:${task.id}:deleted`
717
- }).catch(() => undefined);
718
- }
719
- if (options.refresh)
720
- await loadTaskState();
721
- }, [loadTaskState, removeLocalImportTask]);
707
+ return runMutation(async () => {
708
+ const result = asActionResult(unwrapResponse(await bridge.executeAction('delete_import_task', { taskId: task.id })));
709
+ if (result.success === false)
710
+ throw actionResponseError(result, '任务删除失败');
711
+ catalogTaskTrackerRef.current.abort(task.id);
712
+ contractTaskTrackerRef.current.abort(task.id);
713
+ removeLocalImportTask(task.id);
714
+ if (taskChatSynchronizerRef.current?.isSelected(task.id)) {
715
+ taskChatSynchronizerRef.current.clear();
716
+ await bridge.invokeClientCommand('assistant.context.set', {
717
+ key: 'tradeComplianceTask', clear: true
718
+ }).catch(() => undefined);
719
+ await bridge.invokeClientCommand('assistant.chat.send_message', {
720
+ text: `任务“${task.sourceFileName || documentTypeLabel(task.documentType)}”已取消并删除。`,
721
+ clientMessageId: `trade-task:${task.id}:deleted`
722
+ }).catch(() => undefined);
723
+ }
724
+ if (options.refresh)
725
+ await loadTaskState();
726
+ });
727
+ }, [loadTaskState, removeLocalImportTask, runMutation]);
722
728
  const deleteImportTask = React.useCallback(async (task) => {
723
729
  await deleteImportTaskRecord(task, { refresh: true });
724
730
  }, [deleteImportTaskRecord]);
@@ -781,14 +787,16 @@ function App() {
781
787
  return attached;
782
788
  }, []);
783
789
  const consumeCustomerContractReanalysisResult = React.useCallback(async (runId) => {
784
- const consumed = asActionResult(unwrapResponse(await bridge.executeAction('consume_customer_contract_reanalysis_result', { runId })));
785
- if (consumed.success === false)
786
- throw new Error(resolveActionMessage(consumed) || '确认重新分析结果失败');
787
- }, []);
790
+ return runMutation(async () => {
791
+ const consumed = asActionResult(unwrapResponse(await bridge.executeAction('consume_customer_contract_reanalysis_result', { runId })));
792
+ if (consumed.success === false)
793
+ throw new Error(resolveActionMessage(consumed) || '确认重新分析结果失败');
794
+ });
795
+ }, [runMutation]);
788
796
  const reanalyzeCustomerContract = React.useCallback(async (input) => {
789
797
  setCustomerReanalysisState({ status: 'RUNNING', message: 'AI 重新分析中' });
790
798
  try {
791
- const raw = await submitAndRefreshTaskState(() => bridge.executeAction('reanalyze_customer_contract', { reviewSource: input.reviewSource, ...(input.customerContractId ? { id: input.customerContractId } : {}), ...(input.importTaskId ? { importTaskId: input.importTaskId } : {}) }), loadTaskState, notifyTaskRecoveryRefreshError);
799
+ const raw = await runMutation(() => submitAndRefreshTaskState(() => bridge.executeAction('reanalyze_customer_contract', { reviewSource: input.reviewSource, ...(input.customerContractId ? { id: input.customerContractId } : {}), ...(input.importTaskId ? { importTaskId: input.importTaskId } : {}) }), loadTaskState, notifyTaskRecoveryRefreshError));
792
800
  const response = asActionResult(unwrapResponse(raw));
793
801
  if (response.success === false || !response.data)
794
802
  throw actionResponseError(response, 'AI 重新分析启动失败');
@@ -829,7 +837,7 @@ function App() {
829
837
  setToast({ message: error instanceof Error ? error.message : 'AI 重新分析失败', tone: 'error' });
830
838
  throw error;
831
839
  }
832
- }, [applyCustomerContractReanalysisResult, consumeCustomerContractReanalysisResult, loadTaskState, scheduleTaskStateRecovery]);
840
+ }, [applyCustomerContractReanalysisResult, consumeCustomerContractReanalysisResult, loadTaskState, runMutation, scheduleTaskStateRecovery]);
833
841
  React.useEffect(() => {
834
842
  for (const recovery of data.customerContractAnalyses.items) {
835
843
  const recoverable = recovery.recoveryType === 'ACTIVE_POLL' || recovery.recoveryType === 'SUCCEEDED_RESULT';
@@ -1000,6 +1008,9 @@ function App() {
1000
1008
  setBusy(gate.activeCount() > 0);
1001
1009
  }
1002
1010
  }, []);
1011
+ const resolveStalledCatalogImport = React.useCallback(async (taskId, decision) => {
1012
+ return runMutation(async () => asActionResult(unwrapResponse(await bridge.executeAction('resolve_stalled_catalog_import', { taskId, decision }))));
1013
+ }, [runMutation]);
1003
1014
  const queryMonthlyRates = React.useCallback(async (query) => {
1004
1015
  latestSettingsRatesQueryRef.current = query;
1005
1016
  const requestedPageSize = resolvePaginationPageSize(query.pageSize);
@@ -1164,24 +1175,21 @@ function App() {
1164
1175
  return analyticsDetailPage(payload, query.page, pageSize);
1165
1176
  }, []);
1166
1177
  const exportAnalytics = React.useCallback(async (_request) => {
1167
- setActionBusy(true);
1168
1178
  try {
1169
- const committed = analyticsRequestsRef.current.committed();
1170
- const request = { filters: committed.filters, format: 'XLSX' };
1171
- const result = asActionResult(unwrapResponse(await bridge.executeAction('export_analytics', request)));
1172
- if (result.success === false)
1173
- throw new Error(resolveActionMessage(result) || '分析导出失败');
1174
- downloadActionFile(result.data);
1175
- setToast({ message: '分析数据导出成功', tone: 'success' });
1179
+ await runMutation(async () => {
1180
+ const committed = analyticsRequestsRef.current.committed();
1181
+ const request = { filters: committed.filters, format: 'XLSX' };
1182
+ const result = asActionResult(unwrapResponse(await bridge.executeAction('export_analytics', request)));
1183
+ if (result.success === false)
1184
+ throw new Error(resolveActionMessage(result) || '分析导出失败');
1185
+ downloadActionFile(result.data);
1186
+ setToast({ message: '分析数据导出成功', tone: 'success' });
1187
+ });
1176
1188
  }
1177
1189
  catch (error) {
1178
1190
  setToast({ message: error instanceof Error ? error.message : '分析导出失败', tone: 'error' });
1179
- throw error;
1180
1191
  }
1181
- finally {
1182
- setActionBusy(false);
1183
- }
1184
- }, []);
1192
+ }, [runMutation]);
1185
1193
  const openAnalyticsDetailAction = React.useCallback((row) => {
1186
1194
  const action = row.action;
1187
1195
  if (action.kind === 'CUSTOMER_CONTRACT_REVIEW') {
@@ -1229,38 +1237,48 @@ function App() {
1229
1237
  setToast({ message: '当前明细缺少可跳转的业务标识', tone: 'error' });
1230
1238
  }, [openImportTaskFromStatus, setToast]);
1231
1239
  const saveSettings = React.useCallback(async (request) => {
1232
- const result = asActionResult(unwrapResponse(await bridge.executeAction('save_settings', request)));
1233
- if (result.success === false)
1234
- throw new Error(resolveActionMessage(result) || '设置保存失败');
1235
- await loadRoute({ type: 'page', page: 'settings' }, settingsRatesQueryRef.current.page, settingsRatesQueryRef.current.pageSize);
1236
- }, [loadRoute]);
1240
+ return runMutation(async () => {
1241
+ const result = asActionResult(unwrapResponse(await bridge.executeAction('save_settings', request)));
1242
+ if (result.success === false)
1243
+ throw new Error(resolveActionMessage(result) || '设置保存失败');
1244
+ await loadRoute({ type: 'page', page: 'settings' }, settingsRatesQueryRef.current.page, settingsRatesQueryRef.current.pageSize);
1245
+ });
1246
+ }, [loadRoute, runMutation]);
1237
1247
  const restoreSettings = React.useCallback(async (request) => {
1238
- const result = asActionResult(unwrapResponse(await bridge.executeAction('restore_settings_default', request)));
1239
- if (result.success === false)
1240
- throw new Error(resolveActionMessage(result) || '恢复系统默认失败');
1241
- await loadRoute({ type: 'page', page: 'settings' }, settingsRatesQueryRef.current.page, settingsRatesQueryRef.current.pageSize);
1242
- }, [loadRoute]);
1248
+ return runMutation(async () => {
1249
+ const result = asActionResult(unwrapResponse(await bridge.executeAction('restore_settings_default', request)));
1250
+ if (result.success === false)
1251
+ throw new Error(resolveActionMessage(result) || '恢复系统默认失败');
1252
+ await loadRoute({ type: 'page', page: 'settings' }, settingsRatesQueryRef.current.page, settingsRatesQueryRef.current.pageSize);
1253
+ });
1254
+ }, [loadRoute, runMutation]);
1243
1255
  const deleteSettingsRate = React.useCallback(async (request, options = {}) => {
1244
- const result = asActionResult(unwrapResponse(await bridge.executeAction('delete_settings_rate', request)));
1245
- if (result.success === false)
1246
- throw new Error(resolveActionMessage(result) || '月度汇率删除失败');
1247
- if (options.suppressRefresh)
1248
- return;
1249
- const { page, pageSize } = settingsRatesQueryRef.current;
1250
- const clampedPage = clampPageAfterSingleDeletion(page, pageSize, data.settings.rates.total);
1251
- await loadRoute({ type: 'page', page: 'settings' }, clampedPage, pageSize);
1252
- }, [data.settings.rates.total, loadRoute]);
1256
+ return runMutation(async () => {
1257
+ const result = asActionResult(unwrapResponse(await bridge.executeAction('delete_settings_rate', request)));
1258
+ if (result.success === false)
1259
+ throw new Error(resolveActionMessage(result) || '月度汇率删除失败');
1260
+ if (options.suppressRefresh)
1261
+ return;
1262
+ const { page, pageSize } = settingsRatesQueryRef.current;
1263
+ const clampedPage = clampPageAfterSingleDeletion(page, pageSize, data.settings.rates.total);
1264
+ await loadRoute({ type: 'page', page: 'settings' }, clampedPage, pageSize);
1265
+ });
1266
+ }, [data.settings.rates.total, loadRoute, runMutation]);
1253
1267
  const uploadCompanyLogo = React.useCallback(async (file) => {
1254
- const result = asActionResult(unwrapResponse(await bridge.executeFileAction('upload_company_logo', file, {})));
1255
- if (result.success === false || !result.data?.logoFileId)
1256
- throw new Error(resolveActionMessage(result) || '公司 Logo 上传失败');
1257
- return result.data.logoFileId;
1258
- }, []);
1268
+ return runMutation(async () => {
1269
+ const result = asActionResult(unwrapResponse(await bridge.executeFileAction('upload_company_logo', file, {})));
1270
+ if (result.success === false || !result.data?.logoFileId)
1271
+ throw new Error(resolveActionMessage(result) || '公司 Logo 上传失败');
1272
+ return result.data.logoFileId;
1273
+ });
1274
+ }, [runMutation]);
1259
1275
  const discardCompanyLogo = React.useCallback(async (logoFileId) => {
1260
- const result = asActionResult(unwrapResponse(await bridge.executeAction('discard_company_logo', { logoFileId })));
1261
- if (result.success === false)
1262
- throw new Error(resolveActionMessage(result) || '公司 Logo 草稿清理失败');
1263
- }, []);
1276
+ return runMutation(async () => {
1277
+ const result = asActionResult(unwrapResponse(await bridge.executeAction('discard_company_logo', { logoFileId })));
1278
+ if (result.success === false)
1279
+ throw new Error(resolveActionMessage(result) || '公司 Logo 草稿清理失败');
1280
+ });
1281
+ }, [runMutation]);
1264
1282
  const openSalesFiles = React.useCallback((order) => {
1265
1283
  setActiveSalesFileOrder(order);
1266
1284
  if (!order)
@@ -1301,7 +1319,7 @@ function App() {
1301
1319
  : page === 'customer-contract-review'
1302
1320
  ? _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' }) })
1303
1321
  : undefined;
1304
- 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') }) : 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, 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, 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, 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, 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, onNotify: setToast, onClose: () => setEditingSalesFile(null), onSaved: () => {
1322
+ 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') }) : 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, 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, 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: () => {
1305
1323
  setEditingSalesFile(null);
1306
1324
  if (page === 'analytics') {
1307
1325
  setAnalyticsDetailRefreshKey(current => current + 1);
@@ -1492,9 +1510,6 @@ function actionResponseError(result, fallback) {
1492
1510
  function asRecord(value) {
1493
1511
  return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
1494
1512
  }
1495
- function stringArray(value) {
1496
- return Array.isArray(value) ? value.filter((item) => typeof item === 'string' && Boolean(item.trim())) : [];
1497
- }
1498
1513
  function summaryRequestParameters(value) {
1499
1514
  const parameters = { ...asRecord(value) };
1500
1515
  delete parameters.collection;
@@ -1635,7 +1650,7 @@ async function ensureExcelEditorRuntime() {
1635
1650
  });
1636
1651
  return excelEditorRuntimePromise;
1637
1652
  }
1638
- function SalesFileEditor({ file, onClose, onSaved, onNotify }) {
1653
+ function SalesFileEditor({ file, runMutation, onClose, onSaved, onNotify }) {
1639
1654
  const containerRef = React.useRef(null);
1640
1655
  const editorRef = React.useRef(null);
1641
1656
  const [error, setError] = React.useState('');
@@ -1657,9 +1672,11 @@ function SalesFileEditor({ file, onClose, onSaved, onNotify }) {
1657
1672
  if (cancelled || !container)
1658
1673
  return;
1659
1674
  const editor = await runtime.open({ container, base64: payload.base64, onSave: async (base64) => {
1660
- const saved = asActionResult(unwrapResponse(await bridge.executeAction('save_edited_sales_file', { id: file.id, base64, editVersion: payload.editVersion, expectedStorageFileId: payload.storageFileId })));
1661
- if (saved.success === false)
1662
- throw new Error(resolveActionMessage(saved) || '工作簿保存失败');
1675
+ await runMutation(async () => {
1676
+ const saved = asActionResult(unwrapResponse(await bridge.executeAction('save_edited_sales_file', { id: file.id, base64, editVersion: payload.editVersion, expectedStorageFileId: payload.storageFileId })));
1677
+ if (saved.success === false)
1678
+ throw new Error(resolveActionMessage(saved) || '工作簿保存失败');
1679
+ });
1663
1680
  } });
1664
1681
  if (cancelled)
1665
1682
  editor.dispose();
@@ -1674,7 +1691,7 @@ function SalesFileEditor({ file, onClose, onSaved, onNotify }) {
1674
1691
  }
1675
1692
  })();
1676
1693
  return () => { cancelled = true; editorRef.current?.dispose(); editorRef.current = null; };
1677
- }, [file.id]);
1694
+ }, [file.id, runMutation]);
1678
1695
  const save = async () => { if (!editorRef.current)
1679
1696
  return; setSaving(true); try {
1680
1697
  await editorRef.current.save();