@chenchaolong/plugin-trade-compliance-workbench 1.0.24 → 1.0.26
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.
- package/dist/lib/import-jobs/import-job-callback.processor.d.ts.map +1 -1
- package/dist/lib/import-jobs/import-job-callback.processor.js +5 -4
- package/dist/lib/import-jobs/import-job-callback.processor.js.map +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
- package/dist/lib/remote-ui/action-execution.d.ts +5 -2
- package/dist/lib/remote-ui/action-execution.d.ts.map +1 -1
- package/dist/lib/remote-ui/action-execution.js +2 -2
- package/dist/lib/remote-ui/action-execution.js.map +1 -1
- package/dist/lib/remote-ui/app-source.d.ts.map +1 -1
- package/dist/lib/remote-ui/app-source.js +134 -35
- package/dist/lib/remote-ui/app-source.js.map +1 -1
- package/dist/lib/remote-ui/bulk-delete.d.ts +56 -0
- package/dist/lib/remote-ui/bulk-delete.d.ts.map +1 -0
- package/dist/lib/remote-ui/bulk-delete.js +138 -0
- package/dist/lib/remote-ui/bulk-delete.js.map +1 -0
- package/dist/lib/remote-ui/components/bulk-delete.d.ts +21 -0
- package/dist/lib/remote-ui/components/bulk-delete.d.ts.map +1 -0
- package/dist/lib/remote-ui/components/bulk-delete.js +9 -0
- package/dist/lib/remote-ui/components/bulk-delete.js.map +1 -0
- package/dist/lib/remote-ui/components/data-grid.d.ts +12 -1
- package/dist/lib/remote-ui/components/data-grid.d.ts.map +1 -1
- package/dist/lib/remote-ui/components/data-grid.js +24 -5
- package/dist/lib/remote-ui/components/data-grid.js.map +1 -1
- package/dist/lib/remote-ui/components/task-status.d.ts +10 -1
- package/dist/lib/remote-ui/components/task-status.d.ts.map +1 -1
- package/dist/lib/remote-ui/components/task-status.js +79 -10
- package/dist/lib/remote-ui/components/task-status.js.map +1 -1
- package/dist/lib/remote-ui/customer-contract-contracts.d.ts +7 -2
- package/dist/lib/remote-ui/customer-contract-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/customer-contract-contracts.js.map +1 -1
- package/dist/lib/remote-ui/import-task-status.d.ts +1 -1
- package/dist/lib/remote-ui/import-task-status.d.ts.map +1 -1
- package/dist/lib/remote-ui/import-task-status.js +2 -2
- package/dist/lib/remote-ui/import-task-status.js.map +1 -1
- package/dist/lib/remote-ui/model.d.ts +1 -0
- package/dist/lib/remote-ui/model.d.ts.map +1 -1
- package/dist/lib/remote-ui/model.js +3 -0
- package/dist/lib/remote-ui/model.js.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.css +91 -0
- package/dist/lib/remote-ui/pages/catalogs.d.ts +49 -3
- package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.js +123 -12
- package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
- package/dist/lib/remote-ui/pages/contracts.css +42 -6
- package/dist/lib/remote-ui/pages/contracts.d.ts +61 -3
- package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/contracts.js +213 -11
- package/dist/lib/remote-ui/pages/contracts.js.map +1 -1
- package/dist/lib/remote-ui/pages/files.d.ts +52 -10
- package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/files.js +122 -12
- package/dist/lib/remote-ui/pages/files.js.map +1 -1
- package/dist/lib/remote-ui/pages/home.js +4 -4
- package/dist/lib/remote-ui/pages/home.js.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.d.ts +87 -2
- package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.js +225 -21
- package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
- package/dist/lib/remote-ui/pages/settings.d.ts +39 -6
- package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/settings.js +94 -5
- package/dist/lib/remote-ui/pages/settings.js.map +1 -1
- package/dist/lib/remote-ui/polling.d.ts +1 -0
- package/dist/lib/remote-ui/polling.d.ts.map +1 -1
- package/dist/lib/remote-ui/polling.js +6 -1
- package/dist/lib/remote-ui/polling.js.map +1 -1
- package/dist/lib/remote-ui/procurement-contracts.d.ts +6 -2
- package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/procurement-contracts.js +11 -1
- package/dist/lib/remote-ui/procurement-contracts.js.map +1 -1
- package/dist/lib/remote-ui/styles.css +55 -19
- package/dist/lib/workbench.service.d.ts.map +1 -1
- package/dist/lib/workbench.service.js +20 -23
- package/dist/lib/workbench.service.js.map +1 -1
- package/package.json +2 -1
|
@@ -13,14 +13,17 @@ type ActionNotificationPolicy = 'silent' | {
|
|
|
13
13
|
tone: 'success';
|
|
14
14
|
fallback: string;
|
|
15
15
|
};
|
|
16
|
+
export type ActionExecutionOptions = {
|
|
17
|
+
suppressRefresh?: boolean;
|
|
18
|
+
};
|
|
16
19
|
export declare const ACTION_NOTIFICATION_POLICIES: Record<WorkbenchActionKey, ActionNotificationPolicy>;
|
|
17
|
-
export declare function executeWorkbenchAction<T>({ actionKey, mutate, refresh, notify, onResult, resolveMessage }: {
|
|
20
|
+
export declare function executeWorkbenchAction<T>({ actionKey, mutate, refresh, notify, onResult, resolveMessage, suppressRefresh }: {
|
|
18
21
|
actionKey: WorkbenchActionKey;
|
|
19
22
|
mutate: () => Promise<ActionResult<T>>;
|
|
20
23
|
refresh: () => void | Promise<void>;
|
|
21
24
|
notify?: Notify;
|
|
22
25
|
onResult?: (result: ActionResult<T>) => void;
|
|
23
26
|
resolveMessage?: (result: ActionResult<T>) => string;
|
|
24
|
-
}): Promise<ActionResult<T>>;
|
|
27
|
+
} & ActionExecutionOptions): Promise<ActionResult<T>>;
|
|
25
28
|
export {};
|
|
26
29
|
//# sourceMappingURL=action-execution.d.ts.map
|
|
@@ -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;
|
|
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"}
|
|
@@ -52,7 +52,7 @@ const refreshActions = new Set([
|
|
|
52
52
|
'update_customer_contract_review',
|
|
53
53
|
'delete_entity'
|
|
54
54
|
]);
|
|
55
|
-
export async function executeWorkbenchAction({ actionKey, mutate, refresh, notify, onResult, resolveMessage = defaultActionMessage }) {
|
|
55
|
+
export async function executeWorkbenchAction({ actionKey, mutate, refresh, notify, onResult, resolveMessage = defaultActionMessage, suppressRefresh }) {
|
|
56
56
|
try {
|
|
57
57
|
if (!isWorkbenchActionKey(actionKey))
|
|
58
58
|
throw new Error(`未配置操作通知策略:${String(actionKey)}`);
|
|
@@ -61,7 +61,7 @@ export async function executeWorkbenchAction({ actionKey, mutate, refresh, notif
|
|
|
61
61
|
if (result.success === false)
|
|
62
62
|
throw new Error(resolveMessage(result) || '操作失败');
|
|
63
63
|
onResult?.(result);
|
|
64
|
-
if (result.refresh && refreshActions.has(actionKey)) {
|
|
64
|
+
if (result.refresh && refreshActions.has(actionKey) && !suppressRefresh) {
|
|
65
65
|
try {
|
|
66
66
|
await refresh();
|
|
67
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-execution.js","sourceRoot":"","sources":["../../../src/lib/remote-ui/action-execution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAA2B,MAAM,iCAAiC,CAAA;
|
|
1
|
+
{"version":3,"file":"action-execution.js","sourceRoot":"","sources":["../../../src/lib/remote-ui/action-execution.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-source.d.ts","sourceRoot":"","sources":["../../../src/lib/remote-ui/app-source.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app-source.d.ts","sourceRoot":"","sources":["../../../src/lib/remote-ui/app-source.tsx"],"names":[],"mappings":"AA+BA,OAAO,cAAc,CAAA"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } 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';
|
|
5
5
|
import { resolvePaginationPageSize } from './contracts.js';
|
|
6
|
-
import { clampPageAfterSingleDeletion, createBlobUrlOwner, createKeyedLatestRequestGate, createLatestCommittedRequest, createLatestRequestGate, generatedFileHistoryKey, normalizeWorkbenchData, routeDataRequest, runKeyedLatestRequest, unwrapResponse } from './model.js';
|
|
6
|
+
import { clampPageAfterSingleDeletion, createBlobUrlOwner, createKeyedLatestRequestGate, createLatestCommittedRequest, createLatestRequestGate, generatedFileHistoryKey, isCurrentSettingsRouteRevision, normalizeWorkbenchData, routeDataRequest, runKeyedLatestRequest, unwrapResponse } from './model.js';
|
|
7
7
|
import { encodeViewFilters } from '../view-parameters.js';
|
|
8
8
|
import { InfoNotice } from './components/data-grid.js';
|
|
9
9
|
import { LoadingBlocker, ToastStack } from './components/forms.js';
|
|
@@ -13,8 +13,9 @@ import { buildBreadcrumbs, WorkbenchShell } from './components/layout.js';
|
|
|
13
13
|
import { CatalogCommands, CatalogImportError, ControlledGoodsPage, findActiveCatalogImportTasks, findLatestFailedCatalogImportTask, runCatalogImportOrchestration, SanctionedCompaniesPage } from './pages/catalogs.js';
|
|
14
14
|
import { HomePage } from './pages/home.js';
|
|
15
15
|
import { ProcurementPage } from './pages/procurement.js';
|
|
16
|
+
import { procurementRouteForQuery } from './procurement-contracts.js';
|
|
16
17
|
import { adaptPurchaseReview } from './purchase-review-adapter.js';
|
|
17
|
-
import { CustomerContractsPage, adaptCustomerContractReview } from './pages/contracts.js';
|
|
18
|
+
import { CustomerContractsPage, adaptCustomerContractReview, createCustomerContractQueryCancelCoordinator, customerContractRouteEntryQuery, reconcileLatestCustomerContractQuery } from './pages/contracts.js';
|
|
18
19
|
import { SalesPage } from './pages/sales.js';
|
|
19
20
|
import { SalesFilesPage } from './pages/files.js';
|
|
20
21
|
import { AnalyticsPage, AnalyticsPrototypeBoard } from './pages/analytics.js';
|
|
@@ -23,6 +24,7 @@ import { ContractImportError, findRecoverableContractImport, findResumableContra
|
|
|
23
24
|
import { createTaskChatSynchronizer, importTaskFromStart, importTaskProgressPercent, isLocalUploadPlaceholder, mergeImportTaskItems, removeImportTaskItem } from './import-task-status.js';
|
|
24
25
|
import { compareTaskBusinessOrder, createPendingResultQueue, createPollingTaskTracker, isAbortError, isPollingObservationTimeout, submitAndRefreshTaskState } from './polling.js';
|
|
25
26
|
import { ACTION_NOTIFICATION_POLICIES, executeWorkbenchAction } from './action-execution.js';
|
|
27
|
+
import { createDeferredBulkReconcileCoordinator, runBulkDelete } from './bulk-delete.js';
|
|
26
28
|
import './styles.css';
|
|
27
29
|
const pageMeta = {
|
|
28
30
|
home: { title: '首页', subtitle: '查看系统风险操作提示与外贸合规业务流程。' },
|
|
@@ -60,6 +62,7 @@ let excelEditorRuntimePromise = null;
|
|
|
60
62
|
function App() {
|
|
61
63
|
const [route, setRoute] = React.useState({ type: 'page', page: 'home' });
|
|
62
64
|
const routeRef = React.useRef(route);
|
|
65
|
+
const routeRevisionRef = React.useRef(0);
|
|
63
66
|
const contextRef = React.useRef(null);
|
|
64
67
|
const requestGateRef = React.useRef(createLatestRequestGate());
|
|
65
68
|
const taskStateRequestGateRef = React.useRef(createLatestRequestGate());
|
|
@@ -68,7 +71,15 @@ function App() {
|
|
|
68
71
|
const settingsRatesRequestGateRef = React.useRef(createLatestRequestGate());
|
|
69
72
|
const generatedFilesRequestGateRef = React.useRef(createKeyedLatestRequestGate());
|
|
70
73
|
const activeGeneratedFilesKeyRef = React.useRef(null);
|
|
74
|
+
const latestGeneratedFilesQueryRef = React.useRef({ page: 1, pageSize: 20, filters: {} });
|
|
75
|
+
const generatedFilesBulkViewActiveRef = React.useRef(false);
|
|
76
|
+
const generatedFilesBulkReconcileRef = React.useRef(createDeferredBulkReconcileCoordinator());
|
|
77
|
+
const latestCustomerContractQueryRef = React.useRef({ page: 1, pageSize: 20, filters: {} });
|
|
78
|
+
const customerContractQueryCancelCoordinatorRef = React.useRef(createCustomerContractQueryCancelCoordinator());
|
|
71
79
|
const settingsRatesQueryRef = React.useRef({ page: 1, pageSize: 20 });
|
|
80
|
+
const latestSettingsRatesQueryRef = React.useRef({ page: 1, pageSize: 20 });
|
|
81
|
+
const settingsRatesBulkViewActiveRef = React.useRef(false);
|
|
82
|
+
const settingsRatesBulkReconcileRef = React.useRef(createDeferredBulkReconcileCoordinator());
|
|
72
83
|
const analyticsRequestsRef = React.useRef(createLatestCommittedRequest(initialAnalyticsQuery));
|
|
73
84
|
const [analyticsCommittedQuery, setAnalyticsCommittedQuery] = React.useState(initialAnalyticsQuery);
|
|
74
85
|
const analyticsPrototypeListenerCleanupRef = React.useRef(null);
|
|
@@ -77,8 +88,14 @@ function App() {
|
|
|
77
88
|
const [salesFilesPrototypeState, setSalesFilesPrototypeState] = React.useState(undefined);
|
|
78
89
|
const [data, setData] = React.useState(() => normalizeWorkbenchData(null));
|
|
79
90
|
const importTaskItemsRef = React.useRef([]);
|
|
91
|
+
const deletedImportTaskIdsRef = React.useRef(new Set());
|
|
80
92
|
const [busy, setBusy] = React.useState(false);
|
|
81
93
|
const [actionBusy, setActionBusy] = React.useState(false);
|
|
94
|
+
const [customerContractBulkBusy, setCustomerContractBulkBusy] = React.useState(false);
|
|
95
|
+
const [salesFileBulkBusy, setSalesFileBulkBusy] = React.useState(false);
|
|
96
|
+
const [settingsRateBulkBusy, setSettingsRateBulkBusy] = React.useState(false);
|
|
97
|
+
const [salesFileBulkReconcileBusy, setSalesFileBulkReconcileBusy] = React.useState(false);
|
|
98
|
+
const [settingsRateBulkReconcileBusy, setSettingsRateBulkReconcileBusy] = React.useState(false);
|
|
82
99
|
const [catalogCommand, setCatalogCommand] = React.useState(null);
|
|
83
100
|
const [importCompletions, setImportCompletions] = React.useState({ CONTROLLED_CATALOG: null, SANCTION_CATALOG: null });
|
|
84
101
|
const [importFailures, setImportFailures] = React.useState({ CONTROLLED_CATALOG: null, SANCTION_CATALOG: null });
|
|
@@ -135,7 +152,7 @@ function App() {
|
|
|
135
152
|
}, [setToast]);
|
|
136
153
|
const upsertLocalImportTask = React.useCallback((task) => {
|
|
137
154
|
setData(current => {
|
|
138
|
-
const items = mergeImportTaskItems(current.importTasks.items, [task]);
|
|
155
|
+
const items = mergeImportTaskItems(current.importTasks.items, [task], deletedImportTaskIdsRef.current);
|
|
139
156
|
return {
|
|
140
157
|
...current,
|
|
141
158
|
importTasks: {
|
|
@@ -207,7 +224,7 @@ function App() {
|
|
|
207
224
|
const requestedPageSize = resolvePaginationPageSize(pageSize);
|
|
208
225
|
const request = routeDataRequest(targetRoute, page, requestedPageSize, filters);
|
|
209
226
|
if (!request)
|
|
210
|
-
return;
|
|
227
|
+
return false;
|
|
211
228
|
const collection = String(asRecord(request.parameters).collection);
|
|
212
229
|
const requests = collection === 'analytics' ? analyticsRequestsRef.current : routeRequestGateRef.current;
|
|
213
230
|
const analyticsCandidate = collection === 'analytics' ? analyticsQueryFromRoute(page, requestedPageSize, filters) : null;
|
|
@@ -216,16 +233,18 @@ function App() {
|
|
|
216
233
|
try {
|
|
217
234
|
const payload = asRecord(unwrapResponse(await bridge.requestData(request)));
|
|
218
235
|
if (!requests.commit(requestId))
|
|
219
|
-
return;
|
|
236
|
+
return false;
|
|
220
237
|
if (routeRef.current !== targetRoute && JSON.stringify(routeRef.current) !== JSON.stringify(targetRoute))
|
|
221
|
-
return;
|
|
238
|
+
return false;
|
|
222
239
|
if (collection === 'analytics') {
|
|
223
240
|
setAnalyticsCommittedQuery(analyticsRequestsRef.current.committed());
|
|
224
241
|
setData(current => ({ ...current, analytics: normalizeWorkbenchData({ summary: { analytics: payload } }).analytics }));
|
|
225
242
|
}
|
|
226
243
|
else if (collection === 'settings') {
|
|
227
244
|
const settings = normalizeWorkbenchData({ summary: { settings: payload } }).settings;
|
|
228
|
-
|
|
245
|
+
const committedRatesQuery = { page: settings.rates.page, pageSize: settings.rates.pageSize };
|
|
246
|
+
settingsRatesQueryRef.current = committedRatesQuery;
|
|
247
|
+
latestSettingsRatesQueryRef.current = committedRatesQuery;
|
|
229
248
|
setData(current => ({ ...current, settings }));
|
|
230
249
|
}
|
|
231
250
|
else
|
|
@@ -243,6 +262,7 @@ function App() {
|
|
|
243
262
|
order: payload.order && typeof payload.order === 'object' ? payload.order : null,
|
|
244
263
|
metrics: payload.metrics && typeof payload.metrics === 'object' ? payload.metrics : targetRoute.level === 'orders' ? null : current.metrics
|
|
245
264
|
}));
|
|
265
|
+
return true;
|
|
246
266
|
}
|
|
247
267
|
catch (error) {
|
|
248
268
|
if (requests.isLatest(requestId)) {
|
|
@@ -250,6 +270,7 @@ function App() {
|
|
|
250
270
|
setAnalyticsCommittedQuery({ ...analyticsRequestsRef.current.committed() });
|
|
251
271
|
setToast({ message: error instanceof Error ? `页面加载失败:${error.message}` : '页面加载失败', tone: 'error' });
|
|
252
272
|
}
|
|
273
|
+
return false;
|
|
253
274
|
}
|
|
254
275
|
finally {
|
|
255
276
|
requests.finish(requestId);
|
|
@@ -269,7 +290,7 @@ function App() {
|
|
|
269
290
|
const normalized = normalizeWorkbenchData(response);
|
|
270
291
|
const importTasks = await loadImportTasksFallback(normalized);
|
|
271
292
|
bridge.configure({ uploadPolicy: normalized.uploadPolicy });
|
|
272
|
-
setData(current => mergeWorkbenchDataWithLocalTasks(current, { ...normalized, importTasks }));
|
|
293
|
+
setData(current => mergeWorkbenchDataWithLocalTasks(current, { ...normalized, importTasks }, deletedImportTaskIdsRef.current));
|
|
273
294
|
await loadRoute(routeRef.current);
|
|
274
295
|
}
|
|
275
296
|
}
|
|
@@ -302,7 +323,7 @@ function App() {
|
|
|
302
323
|
sanctionBatches: normalized.sanctionBatches,
|
|
303
324
|
importTasks: {
|
|
304
325
|
...importTasks,
|
|
305
|
-
items: mergeImportTaskItems(current.importTasks.items, importTasks.items),
|
|
326
|
+
items: mergeImportTaskItems(current.importTasks.items, importTasks.items, deletedImportTaskIdsRef.current),
|
|
306
327
|
total: Math.max(numberValue(importTasks.total, 0), importTasks.items.length, current.importTasks.items.length)
|
|
307
328
|
},
|
|
308
329
|
customerContractAnalyses: normalized.customerContractAnalyses
|
|
@@ -362,7 +383,7 @@ function App() {
|
|
|
362
383
|
contextRef.current = nextContext;
|
|
363
384
|
const normalized = normalizeWorkbenchData(nextContext.payload);
|
|
364
385
|
bridge.configure({ uploadPolicy: normalized.uploadPolicy });
|
|
365
|
-
setData(current => mergeWorkbenchDataWithLocalTasks(current, normalized));
|
|
386
|
+
setData(current => mergeWorkbenchDataWithLocalTasks(current, normalized, deletedImportTaskIdsRef.current));
|
|
366
387
|
void load(nextContext);
|
|
367
388
|
},
|
|
368
389
|
onHostEvent() {
|
|
@@ -386,7 +407,7 @@ function App() {
|
|
|
386
407
|
React.useEffect(() => {
|
|
387
408
|
toastTimers.sync(toasts);
|
|
388
409
|
}, [toastTimers, toasts]);
|
|
389
|
-
const executeAction = React.useCallback(async (actionKey, input) => {
|
|
410
|
+
const executeAction = React.useCallback(async (actionKey, input, options = {}) => {
|
|
390
411
|
const blocksPage = ACTION_NOTIFICATION_POLICIES[actionKey] !== 'silent';
|
|
391
412
|
if (blocksPage)
|
|
392
413
|
setActionBusy(true);
|
|
@@ -398,7 +419,8 @@ function App() {
|
|
|
398
419
|
notify: setToast,
|
|
399
420
|
resolveMessage: resolveActionMessage,
|
|
400
421
|
onResult: result => { if (actionKey === 'download_import_issues' || actionKey === 'download_sales_file')
|
|
401
|
-
downloadActionFile(result.data); }
|
|
422
|
+
downloadActionFile(result.data); },
|
|
423
|
+
...options
|
|
402
424
|
});
|
|
403
425
|
}
|
|
404
426
|
finally {
|
|
@@ -406,7 +428,7 @@ function App() {
|
|
|
406
428
|
setActionBusy(false);
|
|
407
429
|
}
|
|
408
430
|
}, [load]);
|
|
409
|
-
const executeSalesFileAction = React.useCallback((actionKey, input) => executeAction(actionKey, input), [executeAction]);
|
|
431
|
+
const executeSalesFileAction = React.useCallback((actionKey, input, options = {}) => executeAction(actionKey, input, options), [executeAction]);
|
|
410
432
|
const schedulePendingReviewPresentation = React.useCallback(() => {
|
|
411
433
|
if (reviewPresentationTimerRef.current != null)
|
|
412
434
|
return;
|
|
@@ -658,7 +680,7 @@ function App() {
|
|
|
658
680
|
throw error;
|
|
659
681
|
}
|
|
660
682
|
}, [loadTaskState, notifyTaskRecoveryRefreshError]);
|
|
661
|
-
const
|
|
683
|
+
const deleteImportTaskRecord = React.useCallback(async (task, options) => {
|
|
662
684
|
const result = asActionResult(unwrapResponse(await bridge.executeAction('delete_import_task', { taskId: task.id })));
|
|
663
685
|
if (result.success === false)
|
|
664
686
|
throw actionResponseError(result, '任务删除失败');
|
|
@@ -675,8 +697,23 @@ function App() {
|
|
|
675
697
|
clientMessageId: `trade-task:${task.id}:deleted`
|
|
676
698
|
}).catch(() => undefined);
|
|
677
699
|
}
|
|
678
|
-
|
|
700
|
+
if (options.refresh)
|
|
701
|
+
await loadTaskState();
|
|
679
702
|
}, [loadTaskState, removeLocalImportTask]);
|
|
703
|
+
const deleteImportTask = React.useCallback(async (task) => {
|
|
704
|
+
await deleteImportTaskRecord(task, { refresh: true });
|
|
705
|
+
}, [deleteImportTaskRecord]);
|
|
706
|
+
const bulkDeleteImportTasks = React.useCallback(async (items) => {
|
|
707
|
+
const tasksById = new Map(data.importTasks.items.map(task => [task.id, task]));
|
|
708
|
+
const result = await runBulkDelete(items.map(item => item.id), async (id) => {
|
|
709
|
+
const task = tasksById.get(id);
|
|
710
|
+
if (!task)
|
|
711
|
+
throw new Error('任务已不存在');
|
|
712
|
+
await deleteImportTaskRecord(task, { refresh: false });
|
|
713
|
+
});
|
|
714
|
+
await loadTaskState();
|
|
715
|
+
return result.failed.map(failure => ({ id: failure.key, message: failure.message }));
|
|
716
|
+
}, [data.importTasks.items, deleteImportTaskRecord, loadTaskState]);
|
|
680
717
|
const openImportTaskFromStatus = React.useCallback(async (task) => {
|
|
681
718
|
if (task.status === 'FAILED' || task.status === 'TIMED_OUT') {
|
|
682
719
|
if (task.documentType === 'PURCHASE_CONTRACT' || task.documentType === 'CUSTOMER_CONTRACT')
|
|
@@ -905,13 +942,13 @@ function App() {
|
|
|
905
942
|
const maxPage = Math.max(1, Math.ceil(total / requestedPageSize));
|
|
906
943
|
if (query.page > maxPage) {
|
|
907
944
|
if (!gate.isLatest(requestId))
|
|
908
|
-
return;
|
|
945
|
+
return false;
|
|
909
946
|
committedPage = maxPage;
|
|
910
947
|
payload = await requestCatalogPage(maxPage);
|
|
911
948
|
total = numberValue(payload.total, 0);
|
|
912
949
|
}
|
|
913
950
|
if (!gate.isLatest(requestId))
|
|
914
|
-
return;
|
|
951
|
+
return false;
|
|
915
952
|
setData((current) => ({
|
|
916
953
|
...current,
|
|
917
954
|
[collection]: {
|
|
@@ -922,10 +959,12 @@ function App() {
|
|
|
922
959
|
pageSize: requestedPageSize
|
|
923
960
|
}
|
|
924
961
|
}));
|
|
962
|
+
return true;
|
|
925
963
|
}
|
|
926
964
|
catch (error) {
|
|
927
965
|
if (gate.isLatest(requestId))
|
|
928
966
|
setToast({ message: error instanceof Error ? `查询失败:${error.message}` : '查询失败', tone: 'error' });
|
|
967
|
+
return false;
|
|
929
968
|
}
|
|
930
969
|
finally {
|
|
931
970
|
gate.finish(requestId);
|
|
@@ -933,44 +972,69 @@ function App() {
|
|
|
933
972
|
}
|
|
934
973
|
}, []);
|
|
935
974
|
const queryMonthlyRates = React.useCallback(async (query) => {
|
|
975
|
+
latestSettingsRatesQueryRef.current = query;
|
|
936
976
|
const requestedPageSize = resolvePaginationPageSize(query.pageSize);
|
|
937
977
|
const gate = settingsRatesRequestGateRef.current;
|
|
978
|
+
const requestRouteRevision = routeRevisionRef.current;
|
|
938
979
|
const requestId = gate.begin();
|
|
939
980
|
setBusy(true);
|
|
940
981
|
try {
|
|
941
982
|
const payload = asRecord(unwrapResponse(await bridge.requestData({ page: query.page, pageSize: requestedPageSize, parameters: { collection: 'monthlyRates' } })));
|
|
942
983
|
if (!gate.commit(requestId))
|
|
943
|
-
return;
|
|
944
|
-
|
|
984
|
+
return false;
|
|
985
|
+
if (!isCurrentSettingsRouteRevision(requestRouteRevision, routeRevisionRef.current, routeRef.current))
|
|
986
|
+
return false;
|
|
987
|
+
const committedRatesQuery = { page: numberValue(payload.page, query.page), pageSize: requestedPageSize };
|
|
988
|
+
settingsRatesQueryRef.current = committedRatesQuery;
|
|
989
|
+
latestSettingsRatesQueryRef.current = committedRatesQuery;
|
|
945
990
|
setData(current => ({ ...current, settings: normalizeWorkbenchData({ summary: { settings: { ...current.settings, rates: { ...payload, pageSize: requestedPageSize } } } }).settings }));
|
|
991
|
+
return true;
|
|
946
992
|
}
|
|
947
993
|
catch (error) {
|
|
948
994
|
if (gate.isLatest(requestId))
|
|
949
995
|
setToast({ message: error instanceof Error ? `月度汇率加载失败:${error.message}` : '月度汇率加载失败', tone: 'error' });
|
|
996
|
+
return false;
|
|
950
997
|
}
|
|
951
998
|
finally {
|
|
952
999
|
gate.finish(requestId);
|
|
953
1000
|
setBusy(gate.activeCount() + requestGateRef.current.activeCount() + routeRequestGateRef.current.activeCount() > 0);
|
|
954
1001
|
}
|
|
955
1002
|
}, []);
|
|
956
|
-
const queryProcurement = React.useCallback(async (
|
|
957
|
-
|
|
958
|
-
|
|
1003
|
+
const queryProcurement = React.useCallback(async (collection, query) => {
|
|
1004
|
+
const targetRoute = procurementRouteForQuery(collection, query);
|
|
1005
|
+
if (!targetRoute)
|
|
1006
|
+
return false;
|
|
1007
|
+
return await loadRoute(targetRoute, query.page, query.pageSize, query.filters);
|
|
1008
|
+
}, [loadRoute]);
|
|
959
1009
|
const queryCustomerContracts = React.useCallback(async (query) => {
|
|
960
|
-
|
|
1010
|
+
latestCustomerContractQueryRef.current = query;
|
|
1011
|
+
return await loadRoute({ type: 'page', page: 'customer-contract-review' }, query.page, query.pageSize, query.filters);
|
|
961
1012
|
}, [loadRoute]);
|
|
1013
|
+
const rememberCustomerContractQuery = React.useCallback((query) => {
|
|
1014
|
+
latestCustomerContractQueryRef.current = query;
|
|
1015
|
+
}, []);
|
|
1016
|
+
const registerCustomerContractQueryCancel = React.useCallback((cancel) => customerContractQueryCancelCoordinatorRef.current.register(cancel), []);
|
|
1017
|
+
const reconcileCustomerContractsAfterBulk = React.useCallback(() => {
|
|
1018
|
+
const isActive = routeRef.current.type === 'page' && routeRef.current.page === 'customer-contract-review';
|
|
1019
|
+
if (isActive)
|
|
1020
|
+
customerContractQueryCancelCoordinatorRef.current.cancelCurrent();
|
|
1021
|
+
return reconcileLatestCustomerContractQuery({
|
|
1022
|
+
isActive: () => isActive,
|
|
1023
|
+
latestQuery: () => latestCustomerContractQueryRef.current,
|
|
1024
|
+
query: queryCustomerContracts
|
|
1025
|
+
});
|
|
1026
|
+
}, [queryCustomerContracts]);
|
|
962
1027
|
const querySales = React.useCallback(async (_collection, query) => {
|
|
963
1028
|
await loadRoute(routeRef.current, query.page, query.pageSize, query.filters);
|
|
964
1029
|
}, [loadRoute]);
|
|
965
1030
|
const querySalesFiles = React.useCallback(async (collection, query) => {
|
|
966
1031
|
const requestedPageSize = resolvePaginationPageSize(query.pageSize);
|
|
967
|
-
if (collection === 'salesFileOrders')
|
|
968
|
-
await loadRoute({ type: 'page', page: 'sales-files' }, query.page, requestedPageSize, query.filters);
|
|
969
|
-
|
|
970
|
-
}
|
|
1032
|
+
if (collection === 'salesFileOrders')
|
|
1033
|
+
return await loadRoute({ type: 'page', page: 'sales-files' }, query.page, requestedPageSize, query.filters);
|
|
1034
|
+
latestGeneratedFilesQueryRef.current = { ...query, pageSize: requestedPageSize };
|
|
971
1035
|
const key = generatedFileHistoryKey(query.parameters);
|
|
972
1036
|
activeGeneratedFilesKeyRef.current = key;
|
|
973
|
-
await runKeyedLatestRequest({
|
|
1037
|
+
return await runKeyedLatestRequest({
|
|
974
1038
|
gate: generatedFilesRequestGateRef.current,
|
|
975
1039
|
key,
|
|
976
1040
|
request: async () => asRecord(unwrapResponse(await bridge.requestData({ page: query.page, pageSize: requestedPageSize, parameters: { collection, ...query.parameters, filters: encodeViewFilters(query.filters) } }))),
|
|
@@ -981,6 +1045,30 @@ function App() {
|
|
|
981
1045
|
onFinish: () => setBusy(generatedFilesRequestGateRef.current.activeCount() + requestGateRef.current.activeCount() + routeRequestGateRef.current.activeCount() + analyticsRequestsRef.current.activeCount() > 0)
|
|
982
1046
|
});
|
|
983
1047
|
}, [loadRoute]);
|
|
1048
|
+
const generatedFilesBulkReconcileOptions = React.useCallback(() => ({
|
|
1049
|
+
isActive: () => routeRef.current.type === 'page' && routeRef.current.page === 'sales-files' && generatedFilesBulkViewActiveRef.current,
|
|
1050
|
+
latestQuery: () => latestGeneratedFilesQueryRef.current,
|
|
1051
|
+
query: (query) => querySalesFiles('generatedFiles', query),
|
|
1052
|
+
onBusyChange: setSalesFileBulkReconcileBusy
|
|
1053
|
+
}), [querySalesFiles]);
|
|
1054
|
+
const settingsRatesBulkReconcileOptions = React.useCallback(() => ({
|
|
1055
|
+
isActive: () => routeRef.current.type === 'page' && routeRef.current.page === 'settings' && settingsRatesBulkViewActiveRef.current,
|
|
1056
|
+
latestQuery: () => latestSettingsRatesQueryRef.current,
|
|
1057
|
+
query: queryMonthlyRates,
|
|
1058
|
+
onBusyChange: setSettingsRateBulkReconcileBusy
|
|
1059
|
+
}), [queryMonthlyRates]);
|
|
1060
|
+
const setGeneratedFilesBulkViewActive = React.useCallback((active) => {
|
|
1061
|
+
generatedFilesBulkViewActiveRef.current = active;
|
|
1062
|
+
if (active)
|
|
1063
|
+
void generatedFilesBulkReconcileRef.current.activate(generatedFilesBulkReconcileOptions());
|
|
1064
|
+
}, [generatedFilesBulkReconcileOptions]);
|
|
1065
|
+
const setSettingsRatesBulkViewActive = React.useCallback((active) => {
|
|
1066
|
+
settingsRatesBulkViewActiveRef.current = active;
|
|
1067
|
+
if (active)
|
|
1068
|
+
void settingsRatesBulkReconcileRef.current.activate(settingsRatesBulkReconcileOptions());
|
|
1069
|
+
}, [settingsRatesBulkReconcileOptions]);
|
|
1070
|
+
const reconcileGeneratedFilesAfterBulk = React.useCallback(() => generatedFilesBulkReconcileRef.current.reconcile(generatedFilesBulkReconcileOptions()), [generatedFilesBulkReconcileOptions]);
|
|
1071
|
+
const reconcileSettingsRatesAfterBulk = React.useCallback(() => settingsRatesBulkReconcileRef.current.reconcile(settingsRatesBulkReconcileOptions()), [settingsRatesBulkReconcileOptions]);
|
|
984
1072
|
const queryAnalytics = React.useCallback(async (query) => {
|
|
985
1073
|
const requestedQuery = { ...query, pageSize: resolvePaginationPageSize(query.pageSize) };
|
|
986
1074
|
const requests = analyticsRequestsRef.current;
|
|
@@ -1040,10 +1128,12 @@ function App() {
|
|
|
1040
1128
|
throw new Error(resolveActionMessage(result) || '恢复系统默认失败');
|
|
1041
1129
|
await loadRoute({ type: 'page', page: 'settings' }, settingsRatesQueryRef.current.page, settingsRatesQueryRef.current.pageSize);
|
|
1042
1130
|
}, [loadRoute]);
|
|
1043
|
-
const deleteSettingsRate = React.useCallback(async (request) => {
|
|
1131
|
+
const deleteSettingsRate = React.useCallback(async (request, options = {}) => {
|
|
1044
1132
|
const result = asActionResult(unwrapResponse(await bridge.executeAction('delete_settings_rate', request)));
|
|
1045
1133
|
if (result.success === false)
|
|
1046
1134
|
throw new Error(resolveActionMessage(result) || '月度汇率删除失败');
|
|
1135
|
+
if (options.suppressRefresh)
|
|
1136
|
+
return;
|
|
1047
1137
|
const { page, pageSize } = settingsRatesQueryRef.current;
|
|
1048
1138
|
const clampedPage = clampPageAfterSingleDeletion(page, pageSize, data.settings.rates.total);
|
|
1049
1139
|
await loadRoute({ type: 'page', page: 'settings' }, clampedPage, pageSize);
|
|
@@ -1079,6 +1169,11 @@ function App() {
|
|
|
1079
1169
|
setActiveSalesFileOrder(order);
|
|
1080
1170
|
}, []);
|
|
1081
1171
|
const changeRoute = React.useCallback((nextRoute) => {
|
|
1172
|
+
routeRevisionRef.current += 1;
|
|
1173
|
+
settingsRatesRequestGateRef.current.invalidate();
|
|
1174
|
+
latestCustomerContractQueryRef.current = customerContractRouteEntryQuery(nextRoute) ?? latestCustomerContractQueryRef.current;
|
|
1175
|
+
if (nextRoute.type === 'page' && nextRoute.page === 'settings')
|
|
1176
|
+
latestSettingsRatesQueryRef.current = { page: 1, pageSize: 20 };
|
|
1082
1177
|
routeRef.current = nextRoute;
|
|
1083
1178
|
setRoute(nextRoute);
|
|
1084
1179
|
void loadRoute(nextRoute);
|
|
@@ -1092,11 +1187,11 @@ function App() {
|
|
|
1092
1187
|
? _jsx(ContractUploadCommand, { label: "\u4E0A\u4F20\u91C7\u8D2D\u5408\u540C", busy: busy || actionBusy, onFile: file => executeFileAction('upload_purchase_contract', file, { documentType: 'PURCHASE_CONTRACT' }), onError: message => setToast({ message, tone: 'error' }) })
|
|
1093
1188
|
: undefined
|
|
1094
1189
|
: page === 'customer-contract-review'
|
|
1095
|
-
? _jsx(ContractUploadCommand, { label: "\u4E0A\u4F20\u5BA2\u6237\u5408\u540C", busy: busy || actionBusy, onFile: file => executeFileAction('upload_customer_contract', file, { documentType: 'CUSTOMER_CONTRACT' }), onError: message => setToast({ message, tone: 'error' }) })
|
|
1190
|
+
? _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' }) })
|
|
1096
1191
|
: undefined;
|
|
1097
1192
|
if (analyticsPrototypeVisible)
|
|
1098
|
-
return (_jsx(WorkbenchShell, { route: route, breadcrumbs: buildBreadcrumbs(route), title: meta.title, topbarActions: _jsx(TaskStatusMenu, { items: taskStatusItems, busy: busy || actionBusy, onActionError: notifyTaskStatusActionError }), onRouteChange: changeRoute, onRefresh: () => void load(), children: _jsx(AnalyticsPrototypeBoard, { onClose: () => setAnalyticsPrototypeVisible(false) }) }));
|
|
1099
|
-
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, 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, importReviewState: customerImportState, onQuery: queryCustomerContracts, onAction: executeAction, onFileAction: executeFileAction, onReanalyze: reanalyzeCustomerContract, onLoadReview: loadCustomerContractReview, onReviewClose: closeCustomerReview, reanalysisState: customerReanalysisState, onRetryImport: retryContractImport, 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, onQuery: querySalesFiles, 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, onSave: saveSettings, onRestoreDefault: restoreSettings, onDeleteRate: deleteSettingsRate, onRateQuery: queryMonthlyRates, 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, label: actionBusy ? '操作处理中' : '数据加载中' }), _jsx(ToastStack, { items: toasts, onClose: closeToast }), editingSalesFile && _jsx(SalesFileEditor, { file: editingSalesFile, onNotify: setToast, onClose: () => setEditingSalesFile(null), onSaved: () => { setEditingSalesFile(null); if (activeSalesFileOrder)
|
|
1193
|
+
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 })] }));
|
|
1194
|
+
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)
|
|
1100
1195
|
void querySalesFiles('generatedFiles', { page: 1, pageSize: 20, filters: {}, parameters: { customerContractId: activeSalesFileOrder.customerContractId, purchaseOrderId: activeSalesFileOrder.purchaseOrderId } }); } })] }));
|
|
1101
1196
|
}
|
|
1102
1197
|
function routePage(route) {
|
|
@@ -1108,6 +1203,7 @@ function routePage(route) {
|
|
|
1108
1203
|
}
|
|
1109
1204
|
function buildTaskStatusItems(tasks, onOpen, onSelect, onDelete) {
|
|
1110
1205
|
return [...tasks]
|
|
1206
|
+
.filter(task => ['QUEUED', 'EXTRACTING', 'AI_PARSING', 'VALIDATING', 'PERSISTING', 'AWAITING_REVIEW', 'FAILED', 'TIMED_OUT'].includes(task.status))
|
|
1111
1207
|
.sort((left, right) => String(right.updatedAt ?? right.queuedAt ?? right.createdAt ?? '').localeCompare(String(left.updatedAt ?? left.queuedAt ?? left.createdAt ?? '')))
|
|
1112
1208
|
.map(task => {
|
|
1113
1209
|
const tone = taskTone(task.status);
|
|
@@ -1158,8 +1254,8 @@ function formatTaskTime(value) {
|
|
|
1158
1254
|
return value;
|
|
1159
1255
|
return new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }).format(date).replace(/\//g, '/');
|
|
1160
1256
|
}
|
|
1161
|
-
function mergeWorkbenchDataWithLocalTasks(current, incoming) {
|
|
1162
|
-
const items = mergeImportTaskItems(current.importTasks.items, incoming.importTasks.items);
|
|
1257
|
+
function mergeWorkbenchDataWithLocalTasks(current, incoming, deletedIds) {
|
|
1258
|
+
const items = mergeImportTaskItems(current.importTasks.items, incoming.importTasks.items, deletedIds);
|
|
1163
1259
|
return {
|
|
1164
1260
|
...incoming,
|
|
1165
1261
|
importTasks: {
|
|
@@ -1217,6 +1313,9 @@ function actionResponseError(result, fallback) {
|
|
|
1217
1313
|
function asRecord(value) {
|
|
1218
1314
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
1219
1315
|
}
|
|
1316
|
+
function stringArray(value) {
|
|
1317
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string' && Boolean(item.trim())) : [];
|
|
1318
|
+
}
|
|
1220
1319
|
function summaryRequestParameters(value) {
|
|
1221
1320
|
const parameters = { ...asRecord(value) };
|
|
1222
1321
|
delete parameters.collection;
|