@chenchaolong/plugin-trade-compliance-workbench 1.0.48 → 1.0.50

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 (84) hide show
  1. package/dist/lib/entities/settings.entity.d.ts +2 -0
  2. package/dist/lib/entities/settings.entity.d.ts.map +1 -1
  3. package/dist/lib/entities/settings.entity.js +8 -0
  4. package/dist/lib/entities/settings.entity.js.map +1 -1
  5. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  6. package/dist/lib/remote-components/trade-compliance-workbench/app.js +4 -4
  7. package/dist/lib/remote-ui/analytics-contracts.d.ts +121 -145
  8. package/dist/lib/remote-ui/analytics-contracts.d.ts.map +1 -1
  9. package/dist/lib/remote-ui/analytics-contracts.js +8 -3
  10. package/dist/lib/remote-ui/analytics-contracts.js.map +1 -1
  11. package/dist/lib/remote-ui/app-source.js +130 -38
  12. package/dist/lib/remote-ui/app-source.js.map +1 -1
  13. package/dist/lib/remote-ui/model.d.ts.map +1 -1
  14. package/dist/lib/remote-ui/model.js +7 -43
  15. package/dist/lib/remote-ui/model.js.map +1 -1
  16. package/dist/lib/remote-ui/pages/analytics-charts.d.ts +12 -0
  17. package/dist/lib/remote-ui/pages/analytics-charts.d.ts.map +1 -0
  18. package/dist/lib/remote-ui/pages/analytics-charts.js +41 -0
  19. package/dist/lib/remote-ui/pages/analytics-charts.js.map +1 -0
  20. package/dist/lib/remote-ui/pages/analytics-dashboard.d.ts +11 -0
  21. package/dist/lib/remote-ui/pages/analytics-dashboard.d.ts.map +1 -0
  22. package/dist/lib/remote-ui/pages/analytics-dashboard.js +59 -0
  23. package/dist/lib/remote-ui/pages/analytics-dashboard.js.map +1 -0
  24. package/dist/lib/remote-ui/pages/analytics-detail-list.d.ts +13 -0
  25. package/dist/lib/remote-ui/pages/analytics-detail-list.d.ts.map +1 -0
  26. package/dist/lib/remote-ui/pages/analytics-detail-list.js +33 -0
  27. package/dist/lib/remote-ui/pages/analytics-detail-list.js.map +1 -0
  28. package/dist/lib/remote-ui/pages/analytics-format.d.ts +7 -0
  29. package/dist/lib/remote-ui/pages/analytics-format.d.ts.map +1 -0
  30. package/dist/lib/remote-ui/pages/analytics-format.js +31 -0
  31. package/dist/lib/remote-ui/pages/analytics-format.js.map +1 -0
  32. package/dist/lib/remote-ui/pages/analytics.d.ts +6 -9
  33. package/dist/lib/remote-ui/pages/analytics.d.ts.map +1 -1
  34. package/dist/lib/remote-ui/pages/analytics.js +77 -372
  35. package/dist/lib/remote-ui/pages/analytics.js.map +1 -1
  36. package/dist/lib/remote-ui/pages/catalogs.css +1 -3
  37. package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
  38. package/dist/lib/remote-ui/pages/catalogs.js +0 -2
  39. package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
  40. package/dist/lib/remote-ui/pages/files-prototype.css +1 -1
  41. package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
  42. package/dist/lib/remote-ui/pages/files.js +9 -2
  43. package/dist/lib/remote-ui/pages/files.js.map +1 -1
  44. package/dist/lib/remote-ui/pages/sales-files.css +6 -0
  45. package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
  46. package/dist/lib/remote-ui/pages/sales.js +1 -2
  47. package/dist/lib/remote-ui/pages/sales.js.map +1 -1
  48. package/dist/lib/remote-ui/pages/settings.d.ts +1 -0
  49. package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -1
  50. package/dist/lib/remote-ui/pages/settings.js +5 -2
  51. package/dist/lib/remote-ui/pages/settings.js.map +1 -1
  52. package/dist/lib/remote-ui/styles.css +160 -249
  53. package/dist/lib/runtime-validation.d.ts.map +1 -1
  54. package/dist/lib/runtime-validation.js +6 -35
  55. package/dist/lib/runtime-validation.js.map +1 -1
  56. package/dist/lib/sales-file.service.js +1 -1
  57. package/dist/lib/sales-file.service.js.map +1 -1
  58. package/dist/lib/schema-maintenance.d.ts +10 -0
  59. package/dist/lib/schema-maintenance.d.ts.map +1 -0
  60. package/dist/lib/schema-maintenance.js +34 -0
  61. package/dist/lib/schema-maintenance.js.map +1 -0
  62. package/dist/lib/types.d.ts +72 -63
  63. package/dist/lib/types.d.ts.map +1 -1
  64. package/dist/lib/types.js +2 -3
  65. package/dist/lib/types.js.map +1 -1
  66. package/dist/lib/workbench-view.provider.d.ts +12 -62
  67. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  68. package/dist/lib/workbench-view.provider.js +20 -4
  69. package/dist/lib/workbench-view.provider.js.map +1 -1
  70. package/dist/lib/workbench.middleware.d.ts +8 -8
  71. package/dist/lib/workbench.plugin.d.ts +3 -3
  72. package/dist/lib/workbench.plugin.d.ts.map +1 -1
  73. package/dist/lib/workbench.plugin.js +6 -6
  74. package/dist/lib/workbench.plugin.js.map +1 -1
  75. package/dist/lib/workbench.service.d.ts +4 -33
  76. package/dist/lib/workbench.service.d.ts.map +1 -1
  77. package/dist/lib/workbench.service.js +236 -265
  78. package/dist/lib/workbench.service.js.map +1 -1
  79. package/package.json +1 -1
  80. package/dist/lib/legacy-schema-cleanup.d.ts +0 -9
  81. package/dist/lib/legacy-schema-cleanup.d.ts.map +0 -1
  82. package/dist/lib/legacy-schema-cleanup.js +0 -28
  83. package/dist/lib/legacy-schema-cleanup.js.map +0 -1
  84. package/dist/lib/remote-ui/pages/analytics-prototype.css +0 -307
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { createRoot } from 'react-dom/client';
4
4
  import { createRemoteBridge } from './bridge.js';
@@ -18,8 +18,9 @@ import { adaptPurchaseReview } from './purchase-review-adapter.js';
18
18
  import { CustomerContractsPage, adaptCustomerContractReview, createCustomerContractQueryCancelCoordinator, customerContractRouteEntryQuery, reconcileLatestCustomerContractQuery } from './pages/contracts.js';
19
19
  import { SalesPage } from './pages/sales.js';
20
20
  import { SalesFilesPage } from './pages/files.js';
21
- import { AnalyticsPage, AnalyticsPrototypeBoard } from './pages/analytics.js';
21
+ import { AnalyticsPage } from './pages/analytics.js';
22
22
  import { SettingsPage } from './pages/settings.js';
23
+ import { AnalyticsComplianceCategoryKeys } from './analytics-contracts.js';
23
24
  import { ContractImportError, findRecoverableContractImport, findResumableContractImports, runContractImportOrchestration, runCustomerContractReanalysis, taskTransition } from './contract-import.js';
24
25
  import { createTaskChatSynchronizer, importTaskFromStart, importTaskProgressPercent, isLocalUploadPlaceholder, mergeImportTaskItems, removeImportTaskItem, taskBusinessRefreshRevision, taskRouteRefreshTarget } from './import-task-status.js';
25
26
  import { compareTaskBusinessOrder, createPendingResultQueue, createPollingTaskTracker, isAbortError, isPollingObservationTimeout, submitAndRefreshTaskState } from './polling.js';
@@ -43,7 +44,7 @@ const procurementPageMeta = {
43
44
  lines: { title: '采购商品列表', subtitle: '查看当前采购订单的商品明细及合规状态。' }
44
45
  };
45
46
  const bridge = createRemoteBridge();
46
- const initialAnalyticsQuery = { mode: 'PURCHASE', dimension: 'SUPPLIER', filters: {}, page: 1, pageSize: 20 };
47
+ const initialAnalyticsQuery = { filters: {}, page: 1, pageSize: 20 };
47
48
  let summaryRequestSequence = 0;
48
49
  const DOWNLOAD_URL_REVOKE_DELAY_MS = 30_000;
49
50
  function comparePendingReviewBusinessOrder(left, right) {
@@ -85,9 +86,7 @@ function App() {
85
86
  const settingsRatesBulkReconcileRef = React.useRef(createDeferredBulkReconcileCoordinator());
86
87
  const analyticsRequestsRef = React.useRef(createLatestCommittedRequest(initialAnalyticsQuery));
87
88
  const [analyticsCommittedQuery, setAnalyticsCommittedQuery] = React.useState(initialAnalyticsQuery);
88
- const analyticsPrototypeListenerCleanupRef = React.useRef(null);
89
89
  const salesFilesPrototypeListenerCleanupRef = React.useRef(null);
90
- const [analyticsPrototypeVisible, setAnalyticsPrototypeVisible] = React.useState(false);
91
90
  const [salesFilesPrototypeState, setSalesFilesPrototypeState] = React.useState(undefined);
92
91
  const [data, setData] = React.useState(() => normalizeWorkbenchData(null));
93
92
  const importTaskItemsRef = React.useRef([]);
@@ -138,6 +137,7 @@ function App() {
138
137
  const [toasts, setToasts] = React.useState([]);
139
138
  const [toastTimers] = React.useState(() => createToastTimerRegistry((id) => setToasts((items) => removeToast(items, id))));
140
139
  const [analyticsError, setAnalyticsError] = React.useState(null);
140
+ const [analyticsDetailRefreshKey, setAnalyticsDetailRefreshKey] = React.useState(0);
141
141
  const setToast = React.useCallback((toast) => {
142
142
  setToasts((items) => addToast(items, toast, createToastId));
143
143
  }, []);
@@ -205,22 +205,16 @@ function App() {
205
205
  return normalized.importTasks;
206
206
  }
207
207
  }, []);
208
- const configureAnalyticsPrototypeCapability = React.useCallback((parameters) => {
209
- analyticsPrototypeListenerCleanupRef.current?.();
210
- analyticsPrototypeListenerCleanupRef.current = null;
208
+ const configureSalesFilesPrototypeCapability = React.useCallback((parameters) => {
211
209
  salesFilesPrototypeListenerCleanupRef.current?.();
212
210
  salesFilesPrototypeListenerCleanupRef.current = null;
213
- setAnalyticsPrototypeVisible(false);
214
211
  setSalesFilesPrototypeState(undefined);
215
212
  if (parameters.visualCheck !== true)
216
213
  return;
217
- const showPrototypeBoard = () => setAnalyticsPrototypeVisible(true);
218
214
  const showSalesFileUpload = () => setSalesFilesPrototypeState('upload');
219
215
  const showSalesFileFormEditor = () => setSalesFilesPrototypeState('form-editor');
220
- window.addEventListener('analytics-prototype-board', showPrototypeBoard);
221
216
  window.addEventListener('sales-file-upload', showSalesFileUpload);
222
217
  window.addEventListener('sales-file-form-editor', showSalesFileFormEditor);
223
- analyticsPrototypeListenerCleanupRef.current = () => window.removeEventListener('analytics-prototype-board', showPrototypeBoard);
224
218
  salesFilesPrototypeListenerCleanupRef.current = () => {
225
219
  window.removeEventListener('sales-file-upload', showSalesFileUpload);
226
220
  window.removeEventListener('sales-file-form-editor', showSalesFileFormEditor);
@@ -400,7 +394,7 @@ function App() {
400
394
  React.useEffect(() => {
401
395
  bridge.start({
402
396
  onInit(nextContext) {
403
- configureAnalyticsPrototypeCapability(asRecord(nextContext.initialQuery?.parameters));
397
+ configureSalesFilesPrototypeCapability(asRecord(nextContext.initialQuery?.parameters));
404
398
  contextRef.current = nextContext;
405
399
  const normalized = normalizeWorkbenchData(nextContext.payload);
406
400
  bridge.configure({ uploadPolicy: normalized.uploadPolicy });
@@ -412,14 +406,12 @@ function App() {
412
406
  }
413
407
  });
414
408
  return () => {
415
- analyticsPrototypeListenerCleanupRef.current?.();
416
- analyticsPrototypeListenerCleanupRef.current = null;
417
409
  salesFilesPrototypeListenerCleanupRef.current?.();
418
410
  salesFilesPrototypeListenerCleanupRef.current = null;
419
411
  blobUrlOwnerRef.current.dispose();
420
412
  bridge.destroy();
421
413
  };
422
- }, [configureAnalyticsPrototypeCapability, load]);
414
+ }, [configureSalesFilesPrototypeCapability, load]);
423
415
  React.useEffect(() => () => toastTimers.dispose(), [toastTimers]);
424
416
  React.useEffect(() => {
425
417
  const timer = window.setTimeout(() => bridge.reportResize(), 0);
@@ -1135,7 +1127,7 @@ function App() {
1135
1127
  setBusy(true);
1136
1128
  setAnalyticsError(null);
1137
1129
  try {
1138
- const payload = asRecord(unwrapResponse(await bridge.requestData({ page: requestedQuery.page, pageSize: requestedQuery.pageSize, parameters: { collection: 'analytics', mode: requestedQuery.mode, dimension: requestedQuery.dimension, filters: encodeViewFilters(requestedQuery.filters) } })));
1130
+ const payload = asRecord(unwrapResponse(await bridge.requestData({ page: requestedQuery.page, pageSize: requestedQuery.pageSize, parameters: { collection: 'analytics', filters: encodeViewFilters(requestedQuery.filters) } })));
1139
1131
  if (!requests.commit(requestId))
1140
1132
  return;
1141
1133
  setAnalyticsCommittedQuery(requests.committed());
@@ -1154,13 +1146,20 @@ function App() {
1154
1146
  setBusy(requests.activeCount() + requestGateRef.current.activeCount() + routeRequestGateRef.current.activeCount() > 0);
1155
1147
  }
1156
1148
  }, [loadImportTasksFallback]);
1149
+ const queryAnalyticsDetails = React.useCallback(async (query) => {
1150
+ const pageSize = resolvePaginationPageSize(query.pageSize);
1151
+ const payload = asRecord(unwrapResponse(await bridge.requestData({
1152
+ page: query.page,
1153
+ pageSize,
1154
+ parameters: { collection: 'analyticsDetails', kind: query.kind, categoryKey: query.categoryKey, filters: encodeViewFilters(query.filters) }
1155
+ })));
1156
+ return analyticsDetailPage(payload, query.page, pageSize);
1157
+ }, []);
1157
1158
  const exportAnalytics = React.useCallback(async (_request) => {
1158
1159
  setActionBusy(true);
1159
1160
  try {
1160
1161
  const committed = analyticsRequestsRef.current.committed();
1161
- const request = committed.mode === 'PURCHASE'
1162
- ? { mode: committed.mode, dimension: committed.dimension, filters: committed.filters, format: 'XLSX' }
1163
- : { mode: committed.mode, dimension: committed.dimension, filters: committed.filters, format: 'XLSX' };
1162
+ const request = { filters: committed.filters, format: 'XLSX' };
1164
1163
  const result = asActionResult(unwrapResponse(await bridge.executeAction('export_analytics', request)));
1165
1164
  if (result.success === false)
1166
1165
  throw new Error(resolveActionMessage(result) || '分析导出失败');
@@ -1175,6 +1174,52 @@ function App() {
1175
1174
  setActionBusy(false);
1176
1175
  }
1177
1176
  }, []);
1177
+ const openAnalyticsDetailAction = React.useCallback((row) => {
1178
+ const action = row.action;
1179
+ if (action.kind === 'CUSTOMER_CONTRACT_REVIEW') {
1180
+ if (!action.importTaskId) {
1181
+ setToast({ message: '客户合同待审任务缺少任务标识', tone: 'error' });
1182
+ return;
1183
+ }
1184
+ void openImportTaskFromStatus({ id: action.importTaskId, documentType: 'CUSTOMER_CONTRACT', status: 'AWAITING_REVIEW', sourceFileName: row.referenceNo }).catch(error => setToast({ message: error instanceof Error ? error.message : '合同审核任务打开失败', tone: 'error' }));
1185
+ return;
1186
+ }
1187
+ if (action.kind === 'PURCHASE_CONTRACT_REVIEW') {
1188
+ if (!action.importTaskId) {
1189
+ setToast({ message: '采购合同待审任务缺少任务标识', tone: 'error' });
1190
+ return;
1191
+ }
1192
+ void openImportTaskFromStatus({ id: action.importTaskId, documentType: 'PURCHASE_CONTRACT', status: 'AWAITING_REVIEW', sourceFileName: row.referenceNo }).catch(error => setToast({ message: error instanceof Error ? error.message : '合同审核任务打开失败', tone: 'error' }));
1193
+ return;
1194
+ }
1195
+ if (action.kind === 'SALES_FILE_REVIEW') {
1196
+ if (!action.customerContractId || !action.purchaseOrderId || !action.salesOrderNoSnapshot || !action.fileType || !action.fileName || !action.storageFileId || action.editVersion == null || !action.generatedAt) {
1197
+ setToast({ message: '销售文件复核数据不完整', tone: 'error' });
1198
+ return;
1199
+ }
1200
+ setEditingSalesFile({
1201
+ id: row.id,
1202
+ customerContractId: action.customerContractId,
1203
+ purchaseOrderId: action.purchaseOrderId,
1204
+ salesOrderNoSnapshot: action.salesOrderNoSnapshot,
1205
+ fileType: action.fileType,
1206
+ fileName: action.fileName,
1207
+ storageFileId: action.storageFileId,
1208
+ editVersion: action.editVersion,
1209
+ generatedAt: action.generatedAt
1210
+ });
1211
+ return;
1212
+ }
1213
+ if (action.kind === 'PURCHASE_ORDER_VIEW' && action.supplierId && action.purchaseOrderId) {
1214
+ changeRouteRef.current({ type: 'procurement', level: 'lines', supplierId: action.supplierId, orderId: action.purchaseOrderId });
1215
+ return;
1216
+ }
1217
+ if (action.kind === 'SALES_ORDER_VIEW') {
1218
+ changeRouteRef.current({ type: 'sales', level: 'lines', customerId: action.customerId, orderKey: `${action.customerContractId}:${action.purchaseOrderId}` });
1219
+ return;
1220
+ }
1221
+ setToast({ message: '当前明细缺少可跳转的业务标识', tone: 'error' });
1222
+ }, [openImportTaskFromStatus, setToast]);
1178
1223
  const saveSettings = React.useCallback(async (request) => {
1179
1224
  const result = asActionResult(unwrapResponse(await bridge.executeAction('save_settings', request)));
1180
1225
  if (result.success === false)
@@ -1248,10 +1293,16 @@ function App() {
1248
1293
  : page === 'customer-contract-review'
1249
1294
  ? _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' }) })
1250
1295
  : undefined;
1251
- if (analyticsPrototypeVisible)
1252
- return (_jsxs(_Fragment, { children: [_jsx(WorkbenchShell, { route: route, breadcrumbs: buildBreadcrumbs(route), title: meta.title, topbarActions: _jsx(TaskStatusMenu, { items: taskStatusItems, busy: busy || actionBusy, onBulkDelete: bulkDeleteImportTasks, onActionError: notifyTaskStatusActionError }), onRouteChange: changeRoute, onRefresh: () => void load(), children: _jsx(AnalyticsPrototypeBoard, { onClose: () => setAnalyticsPrototypeVisible(false) }) }), _jsx(LoadingBlocker, { visible: busy || actionBusy || customerContractBulkBusy, label: actionBusy || customerContractBulkBusy ? '操作处理中' : '数据加载中' }), _jsx(ToastStack, { items: toasts, onClose: closeToast })] }));
1253
- 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, busy: busy || actionBusy, error: analyticsError, onQuery: queryAnalytics, 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: () => { setEditingSalesFile(null); if (activeSalesFileOrder)
1254
- void querySalesFiles('generatedFiles', { page: 1, pageSize: 20, filters: {}, parameters: { customerContractId: activeSalesFileOrder.customerContractId, purchaseOrderId: activeSalesFileOrder.purchaseOrderId } }); } })] }));
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: () => {
1297
+ setEditingSalesFile(null);
1298
+ if (page === 'analytics') {
1299
+ setAnalyticsDetailRefreshKey(current => current + 1);
1300
+ void queryAnalytics(analyticsRequestsRef.current.committed());
1301
+ }
1302
+ else if (activeSalesFileOrder) {
1303
+ void querySalesFiles('generatedFiles', { page: 1, pageSize: 20, filters: {}, parameters: { customerContractId: activeSalesFileOrder.customerContractId, purchaseOrderId: activeSalesFileOrder.purchaseOrderId } });
1304
+ }
1305
+ } })] }));
1255
1306
  }
1256
1307
  function routePage(route) {
1257
1308
  if (route.type === 'procurement')
@@ -1357,23 +1408,64 @@ function analyticsQueryFromRoute(page, pageSize, values) {
1357
1408
  const dateValue = values.orderDate;
1358
1409
  const date = dateValue && typeof dateValue === 'object' && !Array.isArray(dateValue) ? dateValue : null;
1359
1410
  const orderDate = date ? { from: optionalText(date.from), to: optionalText(date.to) } : undefined;
1360
- const common = {
1361
- ...(orderDate?.from || orderDate?.to ? { orderDate } : {}),
1362
- ...(optionalText(values.productName) ? { productName: optionalText(values.productName) } : {}),
1363
- ...(optionalText(values.hsCode) ? { hsCode: optionalText(values.hsCode) } : {})
1364
- };
1365
- if (values.mode === 'SALES')
1366
- return {
1367
- mode: 'SALES', dimension: values.dimension === 'PRODUCT' ? 'PRODUCT' : 'CUSTOMER', page, pageSize: requestedPageSize,
1368
- filters: { ...common, ...(optionalText(values.customer) ? { customer: optionalText(values.customer) } : {}), ...(enabled(values.unreceivedOnly) ? { unreceivedOnly: true } : {}) }
1369
- };
1411
+ return { page, pageSize: requestedPageSize, filters: orderDate?.from || orderDate?.to ? { orderDate } : {} };
1412
+ }
1413
+ function optionalText(value) { const text = typeof value === 'string' ? value.trim() : ''; return text || undefined; }
1414
+ function analyticsDetailPage(payload, fallbackPage, pageSize) {
1415
+ const items = Array.isArray(payload.items) ? payload.items.map(analyticsDetailRow) : [];
1416
+ const total = Math.max(0, numberValue(payload.total, items.length));
1417
+ const pageCount = Math.max(1, numberValue(payload.pageCount, Math.ceil(total / pageSize)));
1418
+ return { items, total, page: Math.max(1, numberValue(payload.page, fallbackPage)), pageSize, pageCount };
1419
+ }
1420
+ function analyticsDetailRow(value) {
1421
+ const row = asRecord(value);
1422
+ const categoryKey = AnalyticsComplianceCategoryKeys.find(item => item === row.categoryKey) ?? (row.categoryKey === 'risk-order' ? 'risk-order' : null);
1423
+ if (!categoryKey)
1424
+ throw new Error('分析明细业务分类无效');
1370
1425
  return {
1371
- mode: 'PURCHASE', dimension: values.dimension === 'PRODUCT' ? 'PRODUCT' : 'SUPPLIER', page, pageSize: requestedPageSize,
1372
- filters: { ...common, ...(optionalText(values.supplier) ? { supplier: optionalText(values.supplier) } : {}), ...(enabled(values.unpaidOnly) ? { unpaidOnly: true } : {}), ...(enabled(values.unrefundedOnly) ? { unrefundedOnly: true } : {}) }
1426
+ id: requiredAnalyticsText(row.id, '明细标识'),
1427
+ categoryKey,
1428
+ categoryLabel: requiredAnalyticsText(row.categoryLabel, '业务分类'),
1429
+ referenceNo: requiredAnalyticsText(row.referenceNo, '业务编号'),
1430
+ ...(optionalText(row.subjectName) ? { subjectName: optionalText(row.subjectName) } : {}),
1431
+ ...(optionalText(row.businessDate) ? { businessDate: optionalText(row.businessDate) } : {}),
1432
+ ...(optionalText(row.reason) ? { reason: optionalText(row.reason) } : {}),
1433
+ ...(row.amountCny == null ? {} : { amountCny: requiredAnalyticsText(row.amountCny, '风险金额') }),
1434
+ status: requiredAnalyticsText(row.status, '状态'),
1435
+ action: analyticsDetailAction(row.action)
1373
1436
  };
1374
1437
  }
1375
- function optionalText(value) { const text = typeof value === 'string' ? value.trim() : ''; return text || undefined; }
1376
- function enabled(value) { return value === true || value === 'true'; }
1438
+ function analyticsDetailAction(value) {
1439
+ const action = asRecord(value);
1440
+ const id = requiredAnalyticsText(action.id, '操作标识');
1441
+ if (action.kind === 'CUSTOMER_CONTRACT_REVIEW')
1442
+ return { kind: action.kind, label: '审核', id, importTaskId: requiredAnalyticsText(action.importTaskId, '客户合同任务标识') };
1443
+ if (action.kind === 'PURCHASE_CONTRACT_REVIEW')
1444
+ return { kind: action.kind, label: '审核', id, importTaskId: requiredAnalyticsText(action.importTaskId, '采购合同任务标识') };
1445
+ if (action.kind === 'PURCHASE_ORDER_VIEW')
1446
+ return { kind: action.kind, label: analyticsActionLabel(action.label), id, purchaseOrderId: requiredAnalyticsText(action.purchaseOrderId, '采购订单标识'), supplierId: requiredAnalyticsText(action.supplierId, '供应商标识') };
1447
+ if (action.kind === 'SALES_ORDER_VIEW')
1448
+ return { kind: action.kind, label: analyticsActionLabel(action.label), id, customerId: requiredAnalyticsText(action.customerId, '客户标识'), customerContractId: requiredAnalyticsText(action.customerContractId, '客户合同标识'), purchaseOrderId: requiredAnalyticsText(action.purchaseOrderId, '采购订单标识') };
1449
+ if (action.kind === 'SALES_FILE_REVIEW') {
1450
+ const fileType = ['PI', 'CI', 'PL', 'CUSTOMS_DECLARATION'].find(item => item === action.fileType);
1451
+ const editVersion = Number(action.editVersion);
1452
+ if (!fileType || !Number.isInteger(editVersion) || editVersion < 0)
1453
+ throw new Error('销售文件复核操作数据无效');
1454
+ return { kind: action.kind, label: '复核', id, customerContractId: requiredAnalyticsText(action.customerContractId, '客户合同标识'), purchaseOrderId: requiredAnalyticsText(action.purchaseOrderId, '采购订单标识'), salesOrderNoSnapshot: requiredAnalyticsText(action.salesOrderNoSnapshot, '销售订单编号'), fileType, fileName: requiredAnalyticsText(action.fileName, '文件名'), storageFileId: requiredAnalyticsText(action.storageFileId, '存储文件标识'), editVersion, generatedAt: requiredAnalyticsText(action.generatedAt, '生成时间') };
1455
+ }
1456
+ throw new Error('分析明细操作类型无效');
1457
+ }
1458
+ function analyticsActionLabel(value) {
1459
+ if (value === '查看' || value === '跟进')
1460
+ return value;
1461
+ throw new Error('分析明细操作名称无效');
1462
+ }
1463
+ function requiredAnalyticsText(value, label) {
1464
+ const text = optionalText(value);
1465
+ if (!text)
1466
+ throw new Error(`${label}不能为空`);
1467
+ return text;
1468
+ }
1377
1469
  function asActionResult(value) {
1378
1470
  return value && typeof value === 'object' && !Array.isArray(value) ? value : { success: true, data: value };
1379
1471
  }