@chenchaolong/plugin-trade-compliance-workbench 1.0.51 → 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 (130) 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/constants.d.ts +1 -0
  22. package/dist/lib/constants.d.ts.map +1 -1
  23. package/dist/lib/constants.js +1 -0
  24. package/dist/lib/constants.js.map +1 -1
  25. package/dist/lib/demo-data.definitions.js +1 -1
  26. package/dist/lib/demo-data.definitions.js.map +1 -1
  27. package/dist/lib/entities/catalog.entity.d.ts +7 -1
  28. package/dist/lib/entities/catalog.entity.d.ts.map +1 -1
  29. package/dist/lib/entities/catalog.entity.js +26 -2
  30. package/dist/lib/entities/catalog.entity.js.map +1 -1
  31. package/dist/lib/entities/customer.entity.js.map +1 -1
  32. package/dist/lib/entities/procurement.entity.js.map +1 -1
  33. package/dist/lib/import-jobs/import-command.builder.js +2 -2
  34. package/dist/lib/import-jobs/import-command.builder.js.map +1 -1
  35. package/dist/lib/import-jobs/import-job-callback.processor.d.ts.map +1 -1
  36. package/dist/lib/import-jobs/import-job-callback.processor.js +2 -0
  37. package/dist/lib/import-jobs/import-job-callback.processor.js.map +1 -1
  38. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  39. package/dist/lib/remote-components/trade-compliance-workbench/app.js +8 -8
  40. package/dist/lib/remote-components/trade-compliance-workbench/editor-source.mjs +17 -1
  41. package/dist/lib/remote-components/trade-compliance-workbench/editor.js +100 -100
  42. package/dist/lib/remote-ui/action-execution.d.ts +3 -0
  43. package/dist/lib/remote-ui/action-execution.d.ts.map +1 -1
  44. package/dist/lib/remote-ui/action-execution.js +21 -1
  45. package/dist/lib/remote-ui/action-execution.js.map +1 -1
  46. package/dist/lib/remote-ui/app-source.js +178 -153
  47. package/dist/lib/remote-ui/app-source.js.map +1 -1
  48. package/dist/lib/remote-ui/bridge.d.ts.map +1 -1
  49. package/dist/lib/remote-ui/components/forms.d.ts +5 -2
  50. package/dist/lib/remote-ui/components/forms.d.ts.map +1 -1
  51. package/dist/lib/remote-ui/components/forms.js +4 -4
  52. package/dist/lib/remote-ui/components/forms.js.map +1 -1
  53. package/dist/lib/remote-ui/components/layout.d.ts.map +1 -1
  54. package/dist/lib/remote-ui/components/layout.js +31 -4
  55. package/dist/lib/remote-ui/components/layout.js.map +1 -1
  56. package/dist/lib/remote-ui/components/task-status.js +2 -2
  57. package/dist/lib/remote-ui/components/task-status.js.map +1 -1
  58. package/dist/lib/remote-ui/contract-import.d.ts +1 -0
  59. package/dist/lib/remote-ui/contract-import.d.ts.map +1 -1
  60. package/dist/lib/remote-ui/contract-import.js +3 -1
  61. package/dist/lib/remote-ui/contract-import.js.map +1 -1
  62. package/dist/lib/remote-ui/contracts.d.ts +21 -0
  63. package/dist/lib/remote-ui/contracts.d.ts.map +1 -1
  64. package/dist/lib/remote-ui/import-task-status.d.ts +5 -2
  65. package/dist/lib/remote-ui/import-task-status.d.ts.map +1 -1
  66. package/dist/lib/remote-ui/import-task-status.js +13 -8
  67. package/dist/lib/remote-ui/import-task-status.js.map +1 -1
  68. package/dist/lib/remote-ui/pages/analytics-detail-list.d.ts.map +1 -1
  69. package/dist/lib/remote-ui/pages/analytics-detail-list.js.map +1 -1
  70. package/dist/lib/remote-ui/pages/catalog-import-history.d.ts +63 -0
  71. package/dist/lib/remote-ui/pages/catalog-import-history.d.ts.map +1 -0
  72. package/dist/lib/remote-ui/pages/catalog-import-history.js +158 -0
  73. package/dist/lib/remote-ui/pages/catalog-import-history.js.map +1 -0
  74. package/dist/lib/remote-ui/pages/catalogs.css +164 -0
  75. package/dist/lib/remote-ui/pages/catalogs.d.ts +12 -6
  76. package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
  77. package/dist/lib/remote-ui/pages/catalogs.js +79 -23
  78. package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
  79. package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -1
  80. package/dist/lib/remote-ui/pages/contracts.js +0 -16
  81. package/dist/lib/remote-ui/pages/contracts.js.map +1 -1
  82. package/dist/lib/remote-ui/pages/files-prototype.css +2 -2
  83. package/dist/lib/remote-ui/pages/files.d.ts +1 -1
  84. package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
  85. package/dist/lib/remote-ui/pages/files.js +6 -6
  86. package/dist/lib/remote-ui/pages/files.js.map +1 -1
  87. package/dist/lib/remote-ui/pages/home.d.ts.map +1 -1
  88. package/dist/lib/remote-ui/pages/home.js +2 -2
  89. package/dist/lib/remote-ui/pages/home.js.map +1 -1
  90. package/dist/lib/remote-ui/pages/procurement.d.ts +6 -1
  91. package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
  92. package/dist/lib/remote-ui/pages/procurement.js +77 -16
  93. package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
  94. package/dist/lib/remote-ui/pages/sales.d.ts +4 -1
  95. package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
  96. package/dist/lib/remote-ui/pages/sales.js +9 -4
  97. package/dist/lib/remote-ui/pages/sales.js.map +1 -1
  98. package/dist/lib/remote-ui/procurement-contracts.d.ts +7 -1
  99. package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -1
  100. package/dist/lib/remote-ui/procurement-contracts.js.map +1 -1
  101. package/dist/lib/remote-ui/sidebar-state.d.ts +1 -0
  102. package/dist/lib/remote-ui/sidebar-state.d.ts.map +1 -1
  103. package/dist/lib/remote-ui/sidebar-state.js +3 -0
  104. package/dist/lib/remote-ui/sidebar-state.js.map +1 -1
  105. package/dist/lib/remote-ui/styles.css +1 -1
  106. package/dist/lib/sales-file.service.d.ts.map +1 -1
  107. package/dist/lib/sales-file.service.js +33 -1
  108. package/dist/lib/sales-file.service.js.map +1 -1
  109. package/dist/lib/schema-maintenance.d.ts.map +1 -1
  110. package/dist/lib/schema-maintenance.js +125 -0
  111. package/dist/lib/schema-maintenance.js.map +1 -1
  112. package/dist/lib/types.d.ts +3 -0
  113. package/dist/lib/types.d.ts.map +1 -1
  114. package/dist/lib/types.js +1 -0
  115. package/dist/lib/types.js.map +1 -1
  116. package/dist/lib/workbench-action-contract.d.ts +1 -1
  117. package/dist/lib/workbench-action-contract.d.ts.map +1 -1
  118. package/dist/lib/workbench-action-contract.js +3 -1
  119. package/dist/lib/workbench-action-contract.js.map +1 -1
  120. package/dist/lib/workbench-view.provider.d.ts +19 -9
  121. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  122. package/dist/lib/workbench-view.provider.js +43 -7
  123. package/dist/lib/workbench-view.provider.js.map +1 -1
  124. package/dist/lib/workbench.middleware.js +8 -8
  125. package/dist/lib/workbench.middleware.js.map +1 -1
  126. package/dist/lib/workbench.service.d.ts +44 -3
  127. package/dist/lib/workbench.service.d.ts.map +1 -1
  128. package/dist/lib/workbench.service.js +788 -152
  129. package/dist/lib/workbench.service.js.map +1 -1
  130. 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)
@@ -470,12 +468,14 @@ function App() {
470
468
  refresh: loadTaskState,
471
469
  signal,
472
470
  onTransition: (previous, next) => {
471
+ upsertLocalImportTask(next);
473
472
  const notice = taskTransition(previous, next);
474
473
  if (notice)
475
474
  setToast(notice);
476
475
  },
477
476
  onPollError: error => setToast({ message: `任务状态查询失败:${error.message}`, tone: 'error' })
478
477
  });
478
+ upsertLocalImportTask(completion.task);
479
479
  setImportCompletions(current => ({ ...current, [completion.task.documentType]: completion }));
480
480
  await refreshTaskRouteRef.current(completion.task);
481
481
  return completion;
@@ -496,7 +496,7 @@ function App() {
496
496
  setToast({ message: error instanceof Error ? error.message : '目录解析失败', tone: 'error' });
497
497
  throw error;
498
498
  }
499
- }, [loadTaskState, scheduleTaskStateRecovery]);
499
+ }, [loadTaskState, scheduleTaskStateRecovery, upsertLocalImportTask]);
500
500
  const orchestrateContractImport = React.useCallback(async (start, signal) => {
501
501
  const task = start.data?.task;
502
502
  if (!task)
@@ -600,35 +600,37 @@ function App() {
600
600
  return false;
601
601
  }, [upsertLocalImportTask]);
602
602
  const executeFileAction = React.useCallback(async (actionKey, file, input, notifyError = true) => {
603
- try {
604
- const raw = await bridge.executeFileAction(actionKey, file, input);
605
- const start = asActionResult(unwrapResponse(raw));
606
- const task = importTaskFromStart(start, actionKey, input, file);
607
- if (task) {
608
- upsertLocalImportTask(task);
609
- if (isLocalUploadPlaceholder(task))
610
- scheduleTaskStateRecovery();
611
- else
612
- 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;
613
623
  }
614
- if (start.success === false)
615
- throw actionResponseError(start, '上传失败');
616
- await loadTaskState().catch((refreshError) => {
617
- notifyTaskRecoveryRefreshError(refreshError instanceof Error ? refreshError : new Error(String(refreshError)));
618
- });
619
- await presentImmediateContractUploadReview(actionKey, start);
620
- setToast({ message: resolveActionMessage(start) || '解析任务已提交', tone: 'info' });
621
- return start;
622
- }
623
- catch (error) {
624
- await loadTaskState().catch((refreshError) => {
625
- notifyTaskRecoveryRefreshError(refreshError instanceof Error ? refreshError : new Error(String(refreshError)));
626
- });
627
- if (notifyError && !(error instanceof CatalogImportError))
628
- setToast({ message: error instanceof Error ? error.message : '上传失败', tone: 'error' });
629
- throw error;
630
- }
631
- }, [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]);
632
634
  const executeCatalogFileAction = React.useCallback((actionKey, file, input) => executeFileAction(actionKey, file, input, false), [executeFileAction]);
633
635
  React.useEffect(() => {
634
636
  const tasks = data.importTasks.items;
@@ -671,52 +673,58 @@ function App() {
671
673
  }
672
674
  }, [data.importTasks.items, handleBackgroundImportTrackingError, orchestrateContractImport]);
673
675
  const retryImport = React.useCallback(async (taskId) => {
674
- try {
675
- const raw = await submitAndRefreshTaskState(() => bridge.executeAction('retry_import_task', { taskId }), loadTaskState, notifyTaskRecoveryRefreshError);
676
- const start = asActionResult(unwrapResponse(raw));
677
- if (start.success === false)
678
- throw actionResponseError(start, '重新解析失败');
679
- setToast({ message: resolveActionMessage(start) || '重新解析任务已提交', tone: 'info' });
680
- }
681
- catch (error) {
682
- if (!(error instanceof CatalogImportError))
683
- setToast({ message: error instanceof Error ? error.message : '重新解析失败', tone: 'error' });
684
- throw error;
685
- }
686
- }, [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]);
687
691
  const retryContractImport = React.useCallback(async (taskId) => {
688
- try {
689
- const raw = await submitAndRefreshTaskState(() => bridge.executeAction('retry_import_task', { taskId }), loadTaskState, notifyTaskRecoveryRefreshError);
690
- const start = asActionResult(unwrapResponse(raw));
691
- if (start.success === false)
692
- throw actionResponseError(start, '重新解析失败');
693
- setToast({ message: resolveActionMessage(start) || '重新解析任务已提交', tone: 'info' });
694
- }
695
- catch (error) {
696
- setToast({ message: error instanceof Error ? error.message : '重新解析失败', tone: 'error' });
697
- throw error;
698
- }
699
- }, [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]);
700
706
  const deleteImportTaskRecord = React.useCallback(async (task, options) => {
701
- const result = asActionResult(unwrapResponse(await bridge.executeAction('delete_import_task', { taskId: task.id })));
702
- if (result.success === false)
703
- throw actionResponseError(result, '任务删除失败');
704
- catalogTaskTrackerRef.current.abort(task.id);
705
- contractTaskTrackerRef.current.abort(task.id);
706
- removeLocalImportTask(task.id);
707
- if (taskChatSynchronizerRef.current?.isSelected(task.id)) {
708
- taskChatSynchronizerRef.current.clear();
709
- await bridge.invokeClientCommand('assistant.context.set', {
710
- key: 'tradeComplianceTask', clear: true
711
- }).catch(() => undefined);
712
- await bridge.invokeClientCommand('assistant.chat.send_message', {
713
- text: `任务“${task.sourceFileName || documentTypeLabel(task.documentType)}”已取消并删除。`,
714
- clientMessageId: `trade-task:${task.id}:deleted`
715
- }).catch(() => undefined);
716
- }
717
- if (options.refresh)
718
- await loadTaskState();
719
- }, [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]);
720
728
  const deleteImportTask = React.useCallback(async (task) => {
721
729
  await deleteImportTaskRecord(task, { refresh: true });
722
730
  }, [deleteImportTaskRecord]);
@@ -779,14 +787,16 @@ function App() {
779
787
  return attached;
780
788
  }, []);
781
789
  const consumeCustomerContractReanalysisResult = React.useCallback(async (runId) => {
782
- const consumed = asActionResult(unwrapResponse(await bridge.executeAction('consume_customer_contract_reanalysis_result', { runId })));
783
- if (consumed.success === false)
784
- throw new Error(resolveActionMessage(consumed) || '确认重新分析结果失败');
785
- }, []);
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]);
786
796
  const reanalyzeCustomerContract = React.useCallback(async (input) => {
787
797
  setCustomerReanalysisState({ status: 'RUNNING', message: 'AI 重新分析中' });
788
798
  try {
789
- 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));
790
800
  const response = asActionResult(unwrapResponse(raw));
791
801
  if (response.success === false || !response.data)
792
802
  throw actionResponseError(response, 'AI 重新分析启动失败');
@@ -827,7 +837,7 @@ function App() {
827
837
  setToast({ message: error instanceof Error ? error.message : 'AI 重新分析失败', tone: 'error' });
828
838
  throw error;
829
839
  }
830
- }, [applyCustomerContractReanalysisResult, consumeCustomerContractReanalysisResult, loadTaskState, scheduleTaskStateRecovery]);
840
+ }, [applyCustomerContractReanalysisResult, consumeCustomerContractReanalysisResult, loadTaskState, runMutation, scheduleTaskStateRecovery]);
831
841
  React.useEffect(() => {
832
842
  for (const recovery of data.customerContractAnalyses.items) {
833
843
  const recoverable = recovery.recoveryType === 'ACTIVE_POLL' || recovery.recoveryType === 'SUCCEEDED_RESULT';
@@ -942,6 +952,12 @@ function App() {
942
952
  const payload = asRecord(unwrapResponse(await bridge.requestData({ page: query.page, pageSize: requestedPageSize, parameters: { collection: 'importIssues', documentType: query.documentType, batchId: query.batchId, issueType: query.issueType } })));
943
953
  return { items: Array.isArray(payload.items) ? payload.items : [], total: numberValue(payload.total, 0), page: numberValue(payload.page, query.page), pageSize: requestedPageSize };
944
954
  }, []);
955
+ const queryCatalogHistory = React.useCallback(async (query) => {
956
+ const requestedPageSize = resolvePaginationPageSize(query.pageSize);
957
+ const collection = query.documentType === 'CONTROLLED_CATALOG' ? 'controlledBatchHistory' : 'sanctionBatchHistory';
958
+ const payload = asRecord(unwrapResponse(await bridge.requestData({ page: query.page, pageSize: requestedPageSize, parameters: { collection } })));
959
+ return { items: Array.isArray(payload.items) ? payload.items : [], total: numberValue(payload.total, 0), page: numberValue(payload.page, query.page), pageSize: requestedPageSize };
960
+ }, []);
945
961
  const queryCatalog = React.useCallback(async (collection, query) => {
946
962
  if (collection === 'controlledGoods')
947
963
  latestControlledCatalogQueryRef.current = query;
@@ -955,7 +971,7 @@ function App() {
955
971
  const requestCatalogPage = async (page) => asRecord(unwrapResponse(await bridge.requestData({
956
972
  page,
957
973
  pageSize: requestedPageSize,
958
- parameters: { collection, filters: encodeViewFilters(query.filters), refreshNonce: nextSummaryRequestNonce() }
974
+ parameters: { collection, filters: encodeViewFilters(query.filters), batchId: query.batchId, refreshNonce: nextSummaryRequestNonce() }
959
975
  })));
960
976
  let committedPage = query.page;
961
977
  let payload = await requestCatalogPage(committedPage);
@@ -992,6 +1008,9 @@ function App() {
992
1008
  setBusy(gate.activeCount() > 0);
993
1009
  }
994
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]);
995
1014
  const queryMonthlyRates = React.useCallback(async (query) => {
996
1015
  latestSettingsRatesQueryRef.current = query;
997
1016
  const requestedPageSize = resolvePaginationPageSize(query.pageSize);
@@ -1156,24 +1175,21 @@ function App() {
1156
1175
  return analyticsDetailPage(payload, query.page, pageSize);
1157
1176
  }, []);
1158
1177
  const exportAnalytics = React.useCallback(async (_request) => {
1159
- setActionBusy(true);
1160
1178
  try {
1161
- const committed = analyticsRequestsRef.current.committed();
1162
- const request = { filters: committed.filters, format: 'XLSX' };
1163
- const result = asActionResult(unwrapResponse(await bridge.executeAction('export_analytics', request)));
1164
- if (result.success === false)
1165
- throw new Error(resolveActionMessage(result) || '分析导出失败');
1166
- downloadActionFile(result.data);
1167
- 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
+ });
1168
1188
  }
1169
1189
  catch (error) {
1170
1190
  setToast({ message: error instanceof Error ? error.message : '分析导出失败', tone: 'error' });
1171
- throw error;
1172
- }
1173
- finally {
1174
- setActionBusy(false);
1175
1191
  }
1176
- }, []);
1192
+ }, [runMutation]);
1177
1193
  const openAnalyticsDetailAction = React.useCallback((row) => {
1178
1194
  const action = row.action;
1179
1195
  if (action.kind === 'CUSTOMER_CONTRACT_REVIEW') {
@@ -1221,38 +1237,48 @@ function App() {
1221
1237
  setToast({ message: '当前明细缺少可跳转的业务标识', tone: 'error' });
1222
1238
  }, [openImportTaskFromStatus, setToast]);
1223
1239
  const saveSettings = React.useCallback(async (request) => {
1224
- const result = asActionResult(unwrapResponse(await bridge.executeAction('save_settings', request)));
1225
- if (result.success === false)
1226
- throw new Error(resolveActionMessage(result) || '设置保存失败');
1227
- await loadRoute({ type: 'page', page: 'settings' }, settingsRatesQueryRef.current.page, settingsRatesQueryRef.current.pageSize);
1228
- }, [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]);
1229
1247
  const restoreSettings = React.useCallback(async (request) => {
1230
- const result = asActionResult(unwrapResponse(await bridge.executeAction('restore_settings_default', request)));
1231
- if (result.success === false)
1232
- throw new Error(resolveActionMessage(result) || '恢复系统默认失败');
1233
- await loadRoute({ type: 'page', page: 'settings' }, settingsRatesQueryRef.current.page, settingsRatesQueryRef.current.pageSize);
1234
- }, [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]);
1235
1255
  const deleteSettingsRate = React.useCallback(async (request, options = {}) => {
1236
- const result = asActionResult(unwrapResponse(await bridge.executeAction('delete_settings_rate', request)));
1237
- if (result.success === false)
1238
- throw new Error(resolveActionMessage(result) || '月度汇率删除失败');
1239
- if (options.suppressRefresh)
1240
- return;
1241
- const { page, pageSize } = settingsRatesQueryRef.current;
1242
- const clampedPage = clampPageAfterSingleDeletion(page, pageSize, data.settings.rates.total);
1243
- await loadRoute({ type: 'page', page: 'settings' }, clampedPage, pageSize);
1244
- }, [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]);
1245
1267
  const uploadCompanyLogo = React.useCallback(async (file) => {
1246
- const result = asActionResult(unwrapResponse(await bridge.executeFileAction('upload_company_logo', file, {})));
1247
- if (result.success === false || !result.data?.logoFileId)
1248
- throw new Error(resolveActionMessage(result) || '公司 Logo 上传失败');
1249
- return result.data.logoFileId;
1250
- }, []);
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]);
1251
1275
  const discardCompanyLogo = React.useCallback(async (logoFileId) => {
1252
- const result = asActionResult(unwrapResponse(await bridge.executeAction('discard_company_logo', { logoFileId })));
1253
- if (result.success === false)
1254
- throw new Error(resolveActionMessage(result) || '公司 Logo 草稿清理失败');
1255
- }, []);
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]);
1256
1282
  const openSalesFiles = React.useCallback((order) => {
1257
1283
  setActiveSalesFileOrder(order);
1258
1284
  if (!order)
@@ -1293,7 +1319,7 @@ function App() {
1293
1319
  : page === 'customer-contract-review'
1294
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' }) })
1295
1321
  : undefined;
1296
- 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') }) : 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, 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, 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: () => {
1297
1323
  setEditingSalesFile(null);
1298
1324
  if (page === 'analytics') {
1299
1325
  setAnalyticsDetailRefreshKey(current => current + 1);
@@ -1484,9 +1510,6 @@ function actionResponseError(result, fallback) {
1484
1510
  function asRecord(value) {
1485
1511
  return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
1486
1512
  }
1487
- function stringArray(value) {
1488
- return Array.isArray(value) ? value.filter((item) => typeof item === 'string' && Boolean(item.trim())) : [];
1489
- }
1490
1513
  function summaryRequestParameters(value) {
1491
1514
  const parameters = { ...asRecord(value) };
1492
1515
  delete parameters.collection;
@@ -1627,7 +1650,7 @@ async function ensureExcelEditorRuntime() {
1627
1650
  });
1628
1651
  return excelEditorRuntimePromise;
1629
1652
  }
1630
- function SalesFileEditor({ file, onClose, onSaved, onNotify }) {
1653
+ function SalesFileEditor({ file, runMutation, onClose, onSaved, onNotify }) {
1631
1654
  const containerRef = React.useRef(null);
1632
1655
  const editorRef = React.useRef(null);
1633
1656
  const [error, setError] = React.useState('');
@@ -1649,9 +1672,11 @@ function SalesFileEditor({ file, onClose, onSaved, onNotify }) {
1649
1672
  if (cancelled || !container)
1650
1673
  return;
1651
1674
  const editor = await runtime.open({ container, base64: payload.base64, onSave: async (base64) => {
1652
- const saved = asActionResult(unwrapResponse(await bridge.executeAction('save_edited_sales_file', { id: file.id, base64, editVersion: payload.editVersion, expectedStorageFileId: payload.storageFileId })));
1653
- if (saved.success === false)
1654
- 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
+ });
1655
1680
  } });
1656
1681
  if (cancelled)
1657
1682
  editor.dispose();
@@ -1666,7 +1691,7 @@ function SalesFileEditor({ file, onClose, onSaved, onNotify }) {
1666
1691
  }
1667
1692
  })();
1668
1693
  return () => { cancelled = true; editorRef.current?.dispose(); editorRef.current = null; };
1669
- }, [file.id]);
1694
+ }, [file.id, runMutation]);
1670
1695
  const save = async () => { if (!editorRef.current)
1671
1696
  return; setSaving(true); try {
1672
1697
  await editorRef.current.save();