@chenchaolong/plugin-trade-compliance-workbench 1.0.25 → 1.0.27

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 (64) hide show
  1. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  2. package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
  3. package/dist/lib/remote-ui/action-execution.d.ts +5 -2
  4. package/dist/lib/remote-ui/action-execution.d.ts.map +1 -1
  5. package/dist/lib/remote-ui/action-execution.js +2 -2
  6. package/dist/lib/remote-ui/action-execution.js.map +1 -1
  7. package/dist/lib/remote-ui/app-source.d.ts.map +1 -1
  8. package/dist/lib/remote-ui/app-source.js +122 -28
  9. package/dist/lib/remote-ui/app-source.js.map +1 -1
  10. package/dist/lib/remote-ui/bulk-delete.d.ts +56 -0
  11. package/dist/lib/remote-ui/bulk-delete.d.ts.map +1 -0
  12. package/dist/lib/remote-ui/bulk-delete.js +138 -0
  13. package/dist/lib/remote-ui/bulk-delete.js.map +1 -0
  14. package/dist/lib/remote-ui/components/bulk-delete.d.ts +21 -0
  15. package/dist/lib/remote-ui/components/bulk-delete.d.ts.map +1 -0
  16. package/dist/lib/remote-ui/components/bulk-delete.js +9 -0
  17. package/dist/lib/remote-ui/components/bulk-delete.js.map +1 -0
  18. package/dist/lib/remote-ui/components/data-grid.d.ts +12 -1
  19. package/dist/lib/remote-ui/components/data-grid.d.ts.map +1 -1
  20. package/dist/lib/remote-ui/components/data-grid.js +24 -5
  21. package/dist/lib/remote-ui/components/data-grid.js.map +1 -1
  22. package/dist/lib/remote-ui/components/task-status.d.ts +10 -1
  23. package/dist/lib/remote-ui/components/task-status.d.ts.map +1 -1
  24. package/dist/lib/remote-ui/components/task-status.js +79 -10
  25. package/dist/lib/remote-ui/components/task-status.js.map +1 -1
  26. package/dist/lib/remote-ui/customer-contract-contracts.d.ts +7 -2
  27. package/dist/lib/remote-ui/customer-contract-contracts.d.ts.map +1 -1
  28. package/dist/lib/remote-ui/customer-contract-contracts.js.map +1 -1
  29. package/dist/lib/remote-ui/import-task-status.d.ts.map +1 -1
  30. package/dist/lib/remote-ui/import-task-status.js +2 -0
  31. package/dist/lib/remote-ui/import-task-status.js.map +1 -1
  32. package/dist/lib/remote-ui/model.d.ts +1 -0
  33. package/dist/lib/remote-ui/model.d.ts.map +1 -1
  34. package/dist/lib/remote-ui/model.js +3 -0
  35. package/dist/lib/remote-ui/model.js.map +1 -1
  36. package/dist/lib/remote-ui/pages/catalogs.d.ts +31 -3
  37. package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
  38. package/dist/lib/remote-ui/pages/catalogs.js +119 -9
  39. package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
  40. package/dist/lib/remote-ui/pages/contracts.d.ts +61 -3
  41. package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -1
  42. package/dist/lib/remote-ui/pages/contracts.js +203 -9
  43. package/dist/lib/remote-ui/pages/contracts.js.map +1 -1
  44. package/dist/lib/remote-ui/pages/files.d.ts +52 -10
  45. package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
  46. package/dist/lib/remote-ui/pages/files.js +122 -12
  47. package/dist/lib/remote-ui/pages/files.js.map +1 -1
  48. package/dist/lib/remote-ui/pages/home.js +4 -4
  49. package/dist/lib/remote-ui/pages/home.js.map +1 -1
  50. package/dist/lib/remote-ui/pages/procurement.d.ts +87 -2
  51. package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
  52. package/dist/lib/remote-ui/pages/procurement.js +225 -21
  53. package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
  54. package/dist/lib/remote-ui/pages/settings.css +3 -0
  55. package/dist/lib/remote-ui/pages/settings.d.ts +39 -6
  56. package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -1
  57. package/dist/lib/remote-ui/pages/settings.js +98 -5
  58. package/dist/lib/remote-ui/pages/settings.js.map +1 -1
  59. package/dist/lib/remote-ui/procurement-contracts.d.ts +6 -2
  60. package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -1
  61. package/dist/lib/remote-ui/procurement-contracts.js +11 -1
  62. package/dist/lib/remote-ui/procurement-contracts.js.map +1 -1
  63. package/dist/lib/remote-ui/styles.css +30 -11
  64. package/package.json +2 -1
@@ -2,6 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import React from 'react';
3
3
  import { DataGrid, FilterPanel, InfoNotice, RiskNotice } from '../components/data-grid.js';
4
4
  import { ConfirmDialog, FieldGrid, FormField, Modal } from '../components/forms.js';
5
+ import { BulkDeleteDialog } from '../components/bulk-delete.js';
6
+ import { pageAfterBulkDelete, runBulkDelete, toggleBulkKey, toggleBulkPage } from '../bulk-delete.js';
5
7
  import { HsCodeLookupDialog } from './catalogs.js';
6
8
  import { formatBeijingDateTime, formatBusinessDate } from '../date-time.js';
7
9
  import { runNotifiedOperation } from '../toast-queue.js';
@@ -76,8 +78,8 @@ export function createProcurementQueryScheduler(query, debounceMs = 300) {
76
78
  };
77
79
  }
78
80
  export function createProcurementActionApi({ onAction, onFileAction }) {
79
- const mutate = async (actionKey, input) => {
80
- const result = await onAction(actionKey, input);
81
+ const mutate = async (actionKey, input, options) => {
82
+ const result = options ? await onAction(actionKey, input, options) : await onAction(actionKey, input);
81
83
  assertSucceeded(result);
82
84
  return result;
83
85
  };
@@ -92,9 +94,112 @@ export function createProcurementActionApi({ onAction, onFileAction }) {
92
94
  updatePurchaseOrder: (input) => mutate('update_purchase_order', { ...input }),
93
95
  addPurchaseLine: (purchaseOrderId, input) => mutate('add_purchase_line', { ...input, purchaseOrderId, sourceType: 'MANUAL_ENTRY' }),
94
96
  updatePurchaseLine: (input) => mutate('update_purchase_line', { ...input }),
95
- deleteEntity: (entityType, id) => mutate('delete_entity', { entityType, id })
97
+ deleteEntity: (entityType, id, options) => mutate('delete_entity', { entityType, id }, options)
96
98
  };
97
99
  }
100
+ export function procurementBulkEntity(collection) {
101
+ return collection === 'suppliers' ? 'supplier' : collection === 'purchaseOrders' ? 'purchaseOrder' : 'purchaseLine';
102
+ }
103
+ export function procurementRouteSelectionKey(route) {
104
+ if (route.level === 'suppliers')
105
+ return 'suppliers';
106
+ if (route.level === 'orders')
107
+ return `orders:${route.supplierId}`;
108
+ return `lines:${route.supplierId}:${route.orderId}`;
109
+ }
110
+ export function createProcurementSelectionQuery(query, clearSelection) {
111
+ return async (collection, request) => {
112
+ clearSelection();
113
+ return await query(collection, request);
114
+ };
115
+ }
116
+ export function createProcurementBulkConfirmGuard() {
117
+ let running = false;
118
+ return {
119
+ run(operation) {
120
+ if (running)
121
+ return null;
122
+ running = true;
123
+ return operation().finally(() => { running = false; });
124
+ }
125
+ };
126
+ }
127
+ export function createProcurementBulkGenerationGuard(initialIdentity) {
128
+ let identity = initialIdentity;
129
+ let revision = 0;
130
+ const isCurrent = (token) => token.identity === identity && token.revision === revision;
131
+ return {
132
+ observeIdentity(nextIdentity) {
133
+ if (nextIdentity === identity)
134
+ return false;
135
+ identity = nextIdentity;
136
+ revision += 1;
137
+ return true;
138
+ },
139
+ invalidate() { revision += 1; },
140
+ capture() { return { identity, revision }; },
141
+ isCurrent,
142
+ commit(token, apply) {
143
+ if (!isCurrent(token))
144
+ return false;
145
+ apply();
146
+ return true;
147
+ }
148
+ };
149
+ }
150
+ export function procurementInteractionBusy(pageBusy, bulkBusy) {
151
+ return pageBusy || bulkBusy;
152
+ }
153
+ export function runProcurementBulkGeneration({ confirmGuard, generationGuard, operation, onCurrentResult, onBusyChange }) {
154
+ return confirmGuard.run(async () => {
155
+ const generation = generationGuard.capture();
156
+ onBusyChange(true);
157
+ try {
158
+ const result = await operation(() => generationGuard.isCurrent(generation));
159
+ generationGuard.commit(generation, () => onCurrentResult(result));
160
+ return result;
161
+ }
162
+ finally {
163
+ onBusyChange(false);
164
+ }
165
+ });
166
+ }
167
+ export function reconcileProcurementBulkState(selectedIds, failures, rows) {
168
+ const currentIds = new Set(rows.map(row => row.id));
169
+ const retainedIds = [...selectedIds].filter(id => currentIds.has(id));
170
+ const retainedFailures = failures.filter(failure => currentIds.has(failure.key));
171
+ return {
172
+ selectedIds: retainedIds.length === selectedIds.size ? selectedIds : new Set(retainedIds),
173
+ failures: retainedFailures.length === failures.length ? failures : retainedFailures
174
+ };
175
+ }
176
+ export async function executeProcurementBulkDelete({ selectedIds, rows, entityType, labelFor, deleteEntity, refresh, collection, query, notify, isCurrent = () => true }) {
177
+ const keys = [...selectedIds];
178
+ const labels = new Map(rows.map(row => [row.id, labelFor(row)]));
179
+ const result = await runBulkDelete(keys, id => deleteEntity(entityType, id, { suppressRefresh: true }));
180
+ const remainingRows = rows.filter(row => !result.succeeded.includes(row.id));
181
+ const nextPage = pageAfterBulkDelete(query.page, remainingRows.length);
182
+ const failures = result.failed.map(failure => ({ ...failure, label: labels.get(failure.key) ?? failure.key }));
183
+ if (!isCurrent())
184
+ return { selectedIds: new Set(keys), failures, refreshFailed: false, stale: true };
185
+ try {
186
+ const refreshed = await refresh(collection, { ...query, page: nextPage });
187
+ if (!isCurrent())
188
+ return { selectedIds: new Set(keys), failures, refreshFailed: false, stale: true };
189
+ if (refreshed === false)
190
+ return { selectedIds: new Set(keys), failures, refreshFailed: true, stale: false };
191
+ }
192
+ catch {
193
+ if (!isCurrent())
194
+ return { selectedIds: new Set(keys), failures, refreshFailed: false, stale: true };
195
+ return { selectedIds: new Set(keys), failures, refreshFailed: true, stale: false };
196
+ }
197
+ if (result.failed.length === 0)
198
+ notify?.({ message: `已删除 ${result.succeeded.length} 条`, tone: 'success' });
199
+ else
200
+ notify?.({ message: `成功 ${result.succeeded.length}、失败 ${result.failed.length}`, tone: result.succeeded.length > 0 ? 'warning' : 'error' });
201
+ return { selectedIds: new Set(result.failed.map(failure => failure.key)), failures, refreshFailed: false, stale: false };
202
+ }
98
203
  export function buildProcurementMetrics(orders, lines, currentYear = Number(formatBeijingDateTime(new Date()).slice(0, 4))) {
99
204
  const currentYearPrefix = `${currentYear}-`;
100
205
  const productAmounts = new Map();
@@ -170,7 +275,19 @@ export function validatePurchaseContractReview(draft) {
170
275
  return errors;
171
276
  }
172
277
  export function ProcurementPage(props) {
278
+ const routeIdentity = procurementRouteSelectionKey(props.route);
173
279
  const [filters, setFilters] = React.useState({});
280
+ const [selectedIds, setSelectedIds] = React.useState(new Set());
281
+ const [bulkOpen, setBulkOpen] = React.useState(false);
282
+ const [bulkBusy, setBulkBusy] = React.useState(false);
283
+ const [bulkFailures, setBulkFailures] = React.useState([]);
284
+ const bulkConfirmGuardRef = React.useRef();
285
+ if (!bulkConfirmGuardRef.current)
286
+ bulkConfirmGuardRef.current = createProcurementBulkConfirmGuard();
287
+ const bulkGenerationGuardRef = React.useRef();
288
+ if (!bulkGenerationGuardRef.current)
289
+ bulkGenerationGuardRef.current = createProcurementBulkGenerationGuard(routeIdentity);
290
+ bulkGenerationGuardRef.current.observeIdentity(routeIdentity);
174
291
  const [supplierDialog, setSupplierDialog] = React.useState(null);
175
292
  const [orderDialog, setOrderDialog] = React.useState(null);
176
293
  const [lineDialog, setLineDialog] = React.useState(null);
@@ -180,16 +297,36 @@ export function ProcurementPage(props) {
180
297
  const api = React.useMemo(() => createProcurementActionApi(props), [props.onAction, props.onFileAction]);
181
298
  const collectionKey = routeCollection(props.route);
182
299
  const definitions = collectionKey === 'suppliers' ? supplierFilters : collectionKey === 'purchaseOrders' ? orderFilters : lineFilters;
183
- const currentPageSize = collectionKey === 'suppliers' ? pagination(props.suppliers).pageSize : collectionKey === 'purchaseOrders' ? pagination(props.purchaseOrders).pageSize : pagination(props.purchaseLines).pageSize;
184
- const query = React.useCallback((page, nextFilters, pageSize) => props.onQuery(collectionKey, {
300
+ const currentPagination = collectionKey === 'suppliers' ? pagination(props.suppliers) : collectionKey === 'purchaseOrders' ? pagination(props.purchaseOrders) : pagination(props.purchaseLines);
301
+ const currentPageSize = currentPagination.pageSize;
302
+ const activeRows = (collectionKey === 'suppliers' ? props.suppliers.items : collectionKey === 'purchaseOrders' ? props.purchaseOrders.items : props.purchaseLines.items);
303
+ const selectionQuery = React.useMemo(() => createProcurementSelectionQuery(props.onQuery, () => {
304
+ bulkGenerationGuardRef.current.invalidate();
305
+ setSelectedIds(new Set());
306
+ setBulkFailures([]);
307
+ setBulkOpen(false);
308
+ }), [props.onQuery]);
309
+ const query = React.useCallback((page, nextFilters, pageSize) => selectionQuery(collectionKey, {
185
310
  page,
186
311
  pageSize,
187
312
  filters: nextFilters,
188
313
  parameters: props.route.level === 'orders' ? { supplierId: props.route.supplierId } : props.route.level === 'lines' ? { supplierId: props.route.supplierId, purchaseOrderId: props.route.orderId } : undefined
189
- }), [collectionKey, props.onQuery, props.route]);
314
+ }), [collectionKey, props.route, selectionQuery]);
190
315
  const scheduler = React.useMemo(() => createProcurementQueryScheduler((page, nextFilters, pageSize) => void query(page, nextFilters, pageSize)), [query]);
191
316
  React.useEffect(() => () => scheduler.cancel(), [scheduler]);
192
317
  React.useEffect(() => { setDraft(props.reviewDraft); }, [props.reviewDraft]);
318
+ React.useEffect(() => {
319
+ setSelectedIds(new Set());
320
+ setBulkFailures([]);
321
+ setBulkOpen(false);
322
+ }, [routeIdentity]);
323
+ React.useEffect(() => {
324
+ const reconciled = reconcileProcurementBulkState(selectedIds, bulkFailures, activeRows);
325
+ if (reconciled.selectedIds !== selectedIds)
326
+ setSelectedIds(reconciled.selectedIds);
327
+ if (reconciled.failures !== bulkFailures)
328
+ setBulkFailures(reconciled.failures);
329
+ }, [activeRows, selectedIds, bulkFailures]);
193
330
  const changeFilter = (key, value) => {
194
331
  const next = { ...filters, [key]: value };
195
332
  setFilters(next);
@@ -206,19 +343,78 @@ export function ProcurementPage(props) {
206
343
  return Promise.reject(new Error('采购商品标识不能为空'));
207
344
  return api.updatePurchaseLine({ ...item, id: item.id });
208
345
  };
346
+ const changeRoute = (nextRoute) => {
347
+ if (nextRoute.type === 'procurement')
348
+ bulkGenerationGuardRef.current.observeIdentity(procurementRouteSelectionKey(nextRoute));
349
+ else
350
+ bulkGenerationGuardRef.current.invalidate();
351
+ setSelectedIds(new Set());
352
+ setBulkFailures([]);
353
+ setBulkOpen(false);
354
+ props.onRouteChange(nextRoute);
355
+ };
209
356
  const reviewVisible = Boolean(props.importTask && (draft || props.importTask.duplicateContractError));
210
- return (_jsxs("section", { className: "tcw-procurement-page tcw-procurement-prototype-surface", "data-procurement-level": props.route.level, children: [props.importTask && (props.importTask.status === 'FAILED' || props.importTask.status === 'TIMED_OUT') && _jsxs("div", { className: "tcw-procurement-import-failure", role: "alert", children: [props.importTask.errorMessage || '采购合同解析失败', props.onRetryImport && _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => void Promise.resolve(props.onRetryImport?.(props.importTask.id)).catch(cause => props.onNotify?.({ message: cause instanceof Error ? cause.message : '重新解析失败', tone: 'error' })), children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u65B0\u89E3\u6790"] })] }), props.route.level === 'suppliers' && _jsx(SupplierLevel, { props: props, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters, currentPageSize), onReset: reset, onPage: (page) => scheduler.page(page, filters, currentPageSize), onPageSize: (pageSize) => scheduler.pageSize(pageSize, filters), onSupplierDialog: setSupplierDialog, onDelete: (item) => setDeleteTarget({ entityType: 'supplier', item }) }), props.route.level === 'orders' && _jsx(OrderLevel, { props: props, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters, currentPageSize), onReset: reset, onPage: (page) => scheduler.page(page, filters, currentPageSize), onPageSize: (pageSize) => scheduler.pageSize(pageSize, filters), onOrderDialog: setOrderDialog, onDelete: (item) => setDeleteTarget({ entityType: 'purchaseOrder', item }) }), props.route.level === 'lines' && _jsx(LineLevel, { props: props, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters, currentPageSize), onReset: reset, onPage: (page) => scheduler.page(page, filters, currentPageSize), onPageSize: (pageSize) => scheduler.pageSize(pageSize, filters), onLineDialog: setLineDialog, onDelete: (item) => setDeleteTarget({ entityType: 'purchaseLine', item }) }), _jsx(SupplierDialog, { state: supplierDialog, busy: props.busy, onClose: () => setSupplierDialog(null), onSave: (item) => execute(() => api.updateSupplier(item), '供应商主档已保存').then((saved) => { if (saved)
211
- setSupplierDialog(null); }) }), _jsx(OrderDialog, { state: orderDialog, lines: props.purchaseLines.items, busy: props.busy, onClose: () => setOrderDialog(null), onSave: (item) => execute(() => api.updatePurchaseOrder(item), '采购订单已保存').then((saved) => { if (saved)
212
- setOrderDialog(null); }) }), _jsx(LineDialog, { state: lineDialog, order: props.order ?? null, busy: props.busy, onAction: props.onAction, onClose: () => setLineDialog(null), onSave: (item) => execute(() => saveLine(item), '采购商品已保存').then((saved) => { if (saved)
213
- setLineDialog(null); }) }), _jsx(PurchaseContractReviewDialog, { open: reviewVisible, task: props.importTask, draft: draft, busy: props.busy, onChange: setDraft, onClose: closeDraft, onSave: (nextDraft) => execute(() => api.savePurchaseReview(props.importTask.id, nextDraft), '采购合同审核已保存').then((saved) => { if (saved)
214
- closeDraft(); }) }), _jsx(DeleteDialog, { target: deleteTarget, supplier: props.supplier ?? null, busy: props.busy, onCancel: () => setDeleteTarget(null), onConfirm: () => {
215
- if (!deleteTarget?.item.id)
357
+ const levelProps = { ...props, onRouteChange: changeRoute };
358
+ const interactionBusy = procurementInteractionBusy(props.busy, bulkBusy);
359
+ const selection = {
360
+ selectedIds,
361
+ onToggle: (id) => setSelectedIds(current => toggleBulkKey(current, id)),
362
+ onTogglePage: (ids) => setSelectedIds(current => toggleBulkPage(current, ids)),
363
+ onClear: () => setSelectedIds(new Set()),
364
+ onBulkDelete: () => { setBulkFailures([]); setBulkOpen(true); },
365
+ disabled: interactionBusy
366
+ };
367
+ const bulkCopy = collectionKey === 'suppliers'
368
+ ? { title: '批量删除供应商', message: '所选供应商将被逻辑删除,后续采购合同不再归入这些供应商主档。' }
369
+ : collectionKey === 'purchaseOrders'
370
+ ? { title: '批量删除采购订单', message: '将同时逻辑删除所选采购订单及其全部商品明细;后续重新导入同编号合同将作为全新记录入库,不迁移旧订单人工金额数据。' }
371
+ : { title: '批量删除采购商品', message: '所选采购商品将被逻辑删除,不影响当前采购订单中的其他商品。' };
372
+ return (_jsxs("section", { className: "tcw-procurement-page tcw-procurement-prototype-surface", "data-procurement-level": props.route.level, children: [props.importTask && (props.importTask.status === 'FAILED' || props.importTask.status === 'TIMED_OUT') && _jsxs("div", { className: "tcw-procurement-import-failure", role: "alert", children: [props.importTask.errorMessage || '采购合同解析失败', props.onRetryImport && _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", disabled: interactionBusy, onClick: () => { if (!interactionBusy)
373
+ void Promise.resolve(props.onRetryImport?.(props.importTask.id)).catch(cause => props.onNotify?.({ message: cause instanceof Error ? cause.message : '重新解析失败', tone: 'error' })); }, children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u65B0\u89E3\u6790"] })] }), props.route.level === 'suppliers' && _jsx(SupplierLevel, { props: levelProps, ...selection, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters, currentPageSize), onReset: reset, onPage: (page) => scheduler.page(page, filters, currentPageSize), onPageSize: (pageSize) => scheduler.pageSize(pageSize, filters), onSupplierDialog: setSupplierDialog, onDelete: (item) => setDeleteTarget({ entityType: 'supplier', item }) }), props.route.level === 'orders' && _jsx(OrderLevel, { props: levelProps, ...selection, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters, currentPageSize), onReset: reset, onPage: (page) => scheduler.page(page, filters, currentPageSize), onPageSize: (pageSize) => scheduler.pageSize(pageSize, filters), onOrderDialog: setOrderDialog, onDelete: (item) => setDeleteTarget({ entityType: 'purchaseOrder', item }) }), props.route.level === 'lines' && _jsx(LineLevel, { props: levelProps, ...selection, filters: filters, definitions: definitions, onFilter: changeFilter, onSubmit: () => scheduler.flush(filters, currentPageSize), onReset: reset, onPage: (page) => scheduler.page(page, filters, currentPageSize), onPageSize: (pageSize) => scheduler.pageSize(pageSize, filters), onLineDialog: setLineDialog, onDelete: (item) => setDeleteTarget({ entityType: 'purchaseLine', item }) }), _jsx(SupplierDialog, { state: supplierDialog, busy: interactionBusy, onClose: () => setSupplierDialog(null), onSave: (item) => execute(() => api.updateSupplier(item), '供应商主档已保存').then((saved) => { if (saved)
374
+ setSupplierDialog(null); }) }), _jsx(OrderDialog, { state: orderDialog, lines: props.purchaseLines.items, busy: interactionBusy, onClose: () => setOrderDialog(null), onSave: (item) => execute(() => api.updatePurchaseOrder(item), '采购订单已保存').then((saved) => { if (saved)
375
+ setOrderDialog(null); }) }), _jsx(LineDialog, { state: lineDialog, order: props.order ?? null, busy: interactionBusy, onAction: props.onAction, onClose: () => setLineDialog(null), onSave: (item) => execute(() => saveLine(item), '采购商品已保存').then((saved) => { if (saved)
376
+ setLineDialog(null); }) }), _jsx(PurchaseContractReviewDialog, { open: reviewVisible, task: props.importTask, draft: draft, busy: interactionBusy, onChange: setDraft, onClose: closeDraft, onSave: (nextDraft) => execute(() => api.savePurchaseReview(props.importTask.id, nextDraft), '采购合同审核已保存').then((saved) => { if (saved)
377
+ closeDraft(); }) }), _jsx(DeleteDialog, { target: deleteTarget, supplier: props.supplier ?? null, busy: interactionBusy, onCancel: () => setDeleteTarget(null), onConfirm: () => {
378
+ if (interactionBusy || !deleteTarget?.item.id)
216
379
  return;
217
380
  void execute(() => api.deleteEntity(deleteTarget.entityType, deleteTarget.item.id), '已逻辑删除').then((deleted) => { if (deleted)
218
381
  setDeleteTarget(null); });
382
+ } }), _jsx(BulkDeleteDialog, { open: bulkOpen, title: bulkCopy.title, count: selectedIds.size, message: bulkCopy.message, busy: interactionBusy, failures: bulkFailures, onCancel: () => { if (!bulkBusy)
383
+ setBulkOpen(false); }, onConfirm: () => {
384
+ if (interactionBusy || selectedIds.size === 0)
385
+ return;
386
+ const entityType = procurementBulkEntity(collectionKey);
387
+ void runProcurementBulkGeneration({
388
+ confirmGuard: bulkConfirmGuardRef.current,
389
+ generationGuard: bulkGenerationGuardRef.current,
390
+ operation: isCurrent => executeProcurementBulkDelete({
391
+ selectedIds,
392
+ rows: activeRows,
393
+ entityType,
394
+ labelFor: row => procurementBulkLabel(entityType, row),
395
+ deleteEntity: api.deleteEntity,
396
+ refresh: props.onQuery,
397
+ collection: collectionKey,
398
+ query: {
399
+ page: currentPagination.page,
400
+ pageSize: currentPagination.pageSize,
401
+ filters,
402
+ parameters: props.route.level === 'orders' ? { supplierId: props.route.supplierId } : props.route.level === 'lines' ? { supplierId: props.route.supplierId, purchaseOrderId: props.route.orderId } : undefined
403
+ },
404
+ notify: props.onNotify,
405
+ isCurrent
406
+ }),
407
+ onCurrentResult: result => {
408
+ setSelectedIds(result.selectedIds);
409
+ setBulkFailures(result.failures);
410
+ if (!result.refreshFailed && result.failures.length === 0)
411
+ setBulkOpen(false);
412
+ },
413
+ onBusyChange: setBulkBusy
414
+ });
219
415
  } })] }));
220
416
  }
221
- function SupplierLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onSupplierDialog, onDelete }) {
417
+ function SupplierLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onSupplierDialog, onDelete, selectedIds, onToggle, onTogglePage, onClear, onBulkDelete, disabled }) {
222
418
  const openSupplierOrders = (row) => row.id && props.onRouteChange({ type: 'procurement', level: 'orders', supplierId: row.id });
223
419
  const columns = [
224
420
  { type: 'custom', key: 'code', header: '供应商代码', width: 130, title: (_, row) => row.code || undefined, render: (_, row) => _jsx(LinkButton, { icon: "ri-file-list-3-line", title: row.code || undefined, onClick: () => openSupplierOrders(row), children: row.code || '—' }) },
@@ -230,9 +426,9 @@ function SupplierLevel({ props, filters, definitions, onFilter, onSubmit, onRese
230
426
  { type: 'custom', key: 'sanctionStatus', header: '制裁风险', width: 110, truncate: false, render: (_, row) => _jsx(StatusTag, { tone: row.sanctionStatus === 'HIT' ? 'danger' : row.sanctionStatus === 'CLEAR' ? 'success' : 'muted', children: sanctionLabels[row.sanctionStatus ?? 'UNCHECKED'] }) },
231
427
  { type: 'text', key: 'createdAt', header: '创建时间', width: 170, truncate: false, format: formatBeijingDateTime }
232
428
  ];
233
- return _jsxs(_Fragment, { children: [_jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(InfoNotice, { children: "\u91C7\u8D2D\u5408\u540C\u4E0A\u4F20\u540E\u81EA\u52A8\u5F52\u5165\u4F9B\u5E94\u5546\uFF1B\u540C\u540D\u4F9B\u5E94\u5546\u5F52\u5E76\u5230\u540C\u4E00\u4E3B\u6863\u3002\u70B9\u51FB\u4F9B\u5E94\u5546\u4EE3\u7801\u3001\u4F9B\u5E94\u5546\u540D\u79F0\u6216\u8BA2\u5355\u8BE6\u60C5\u8FDB\u5165\u91C7\u8D2D\u8BA2\u5355\u5217\u8868\u3002" }), _jsx(DataGrid, { columns: columns, rows: props.suppliers.items, rowKey: (row, index) => row.id ?? index, pagination: pagination(props.suppliers), onPageChange: onPage, onPageSizeChange: onPageSize, emptyText: "\u6682\u65E0\u4F9B\u5E94\u5546\u6570\u636E", actionWidth: 190, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => openSupplierOrders(row), children: [_jsx("i", { className: "ri-file-list-3-line", "aria-hidden": "true" }), "\u8BA2\u5355\u8BE6\u60C5"] }), _jsxs("button", { type: "button", onClick: () => onSupplierDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
429
+ return _jsxs(_Fragment, { children: [_jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(InfoNotice, { children: "\u91C7\u8D2D\u5408\u540C\u4E0A\u4F20\u540E\u81EA\u52A8\u5F52\u5165\u4F9B\u5E94\u5546\uFF1B\u540C\u540D\u4F9B\u5E94\u5546\u5F52\u5E76\u5230\u540C\u4E00\u4E3B\u6863\u3002\u70B9\u51FB\u4F9B\u5E94\u5546\u4EE3\u7801\u3001\u4F9B\u5E94\u5546\u540D\u79F0\u6216\u8BA2\u5355\u8BE6\u60C5\u8FDB\u5165\u91C7\u8D2D\u8BA2\u5355\u5217\u8868\u3002" }), _jsx(DataGrid, { columns: columns, rows: props.suppliers.items, rowKey: "id", selection: { selectedKeys: selectedIds, onToggle, onTogglePage: () => onTogglePage(props.suppliers.items.map(row => row.id)), onClear, onDelete: onBulkDelete, labelFor: row => `选择供应商:${row.name || '未命名记录'}`, disabled }, pagination: pagination(props.suppliers), onPageChange: onPage, onPageSizeChange: onPageSize, emptyText: "\u6682\u65E0\u4F9B\u5E94\u5546\u6570\u636E", actionWidth: 190, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => openSupplierOrders(row), children: [_jsx("i", { className: "ri-file-list-3-line", "aria-hidden": "true" }), "\u8BA2\u5355\u8BE6\u60C5"] }), _jsxs("button", { type: "button", disabled: disabled, onClick: () => onSupplierDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", disabled: disabled, onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
234
430
  }
235
- function OrderLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onOrderDialog, onDelete }) {
431
+ function OrderLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onOrderDialog, onDelete, selectedIds, onToggle, onTogglePage, onClear, onBulkDelete, disabled }) {
236
432
  const openOrderLines = (row) => row.id && props.onRouteChange({ type: 'procurement', level: 'lines', supplierId: props.route.level === 'orders' ? props.route.supplierId : row.supplierId, orderId: row.id });
237
433
  const columns = [
238
434
  { type: 'custom', key: 'contractNo', header: '采购订单编号', width: 160, title: (_, row) => row.contractNo, render: (_, row) => _jsx(LinkButton, { icon: "ri-file-list-2-line", title: row.contractNo, onClick: () => openOrderLines(row), children: row.contractNo }) },
@@ -244,9 +440,9 @@ function OrderLevel({ props, filters, definitions, onFilter, onSubmit, onReset,
244
440
  { type: 'text', key: 'paidAmountCny', header: '已付款', width: 130, align: 'right', truncate: false, format: currency },
245
441
  { type: 'text', key: 'refundedAmountCny', header: '已退税', width: 130, align: 'right', truncate: false, format: currency }
246
442
  ];
247
- return _jsxs(_Fragment, { children: [props.supplier && _jsx(ProcurementContextBar, { level: "supplier", supplier: props.supplier, onRouteChange: props.onRouteChange }), _jsx(ProcurementLevelHeading, { title: "\u91C7\u8D2D\u8BA2\u5355\u5217\u8868", description: "\u7BA1\u7406\u6240\u6709\u91C7\u8D2D\u8BA2\u5355\uFF0C\u652F\u6301\u67E5\u770B\u8BA2\u5355\u72B6\u6001\u3001\u91D1\u989D\u3001\u4F9B\u5E94\u5546\u7B49\u4FE1\u606F\u3002" }), props.metrics && _jsx(MetricStrip, { metrics: props.metrics }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(DataGrid, { columns: columns, rows: props.purchaseOrders.items, rowKey: (row, index) => row.id ?? index, pagination: pagination(props.purchaseOrders), onPageChange: onPage, onPageSizeChange: onPageSize, emptyText: "\u6682\u65E0\u91C7\u8D2D\u8BA2\u5355\u6570\u636E", actionWidth: 190, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => openOrderLines(row), children: [_jsx("i", { className: "ri-file-list-2-line", "aria-hidden": "true" }), "\u5546\u54C1\u8BE6\u60C5"] }), _jsxs("button", { type: "button", onClick: () => onOrderDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
443
+ return _jsxs(_Fragment, { children: [props.supplier && _jsx(ProcurementContextBar, { level: "supplier", supplier: props.supplier, onRouteChange: props.onRouteChange }), _jsx(ProcurementLevelHeading, { title: "\u91C7\u8D2D\u8BA2\u5355\u5217\u8868", description: "\u7BA1\u7406\u6240\u6709\u91C7\u8D2D\u8BA2\u5355\uFF0C\u652F\u6301\u67E5\u770B\u8BA2\u5355\u72B6\u6001\u3001\u91D1\u989D\u3001\u4F9B\u5E94\u5546\u7B49\u4FE1\u606F\u3002" }), props.metrics && _jsx(MetricStrip, { metrics: props.metrics }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(DataGrid, { columns: columns, rows: props.purchaseOrders.items, rowKey: "id", selection: { selectedKeys: selectedIds, onToggle, onTogglePage: () => onTogglePage(props.purchaseOrders.items.map(row => row.id)), onClear, onDelete: onBulkDelete, labelFor: row => `选择采购订单:${row.contractNo || '未编号记录'}`, disabled }, pagination: pagination(props.purchaseOrders), onPageChange: onPage, onPageSizeChange: onPageSize, emptyText: "\u6682\u65E0\u91C7\u8D2D\u8BA2\u5355\u6570\u636E", actionWidth: 190, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => openOrderLines(row), children: [_jsx("i", { className: "ri-file-list-2-line", "aria-hidden": "true" }), "\u5546\u54C1\u8BE6\u60C5"] }), _jsxs("button", { type: "button", disabled: disabled, onClick: () => onOrderDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", disabled: disabled, onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
248
444
  }
249
- function LineLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onLineDialog, onDelete }) {
445
+ function LineLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onLineDialog, onDelete, selectedIds, onToggle, onTogglePage, onClear, onBulkDelete, disabled }) {
250
446
  const columns = [
251
447
  { type: 'ellipsis', key: 'productName', header: '商品名称', width: 170, maxWidth: 170 },
252
448
  { type: 'ellipsis', key: 'productDescription', header: '商品描述', width: 190, maxWidth: 190 },
@@ -262,7 +458,8 @@ function LineLevel({ props, filters, definitions, onFilter, onSubmit, onReset, o
262
458
  { type: 'text', key: 'complianceCategories', header: '命中合规分类', width: 150, format: (value) => Array.isArray(value) && value.length ? value.map((item) => complianceLabels[item]).join('、') : '无命中' }
263
459
  ];
264
460
  const newLine = { purchaseOrderId: props.order?.id ?? '', sourceType: 'MANUAL_ENTRY', productName: '', quantity: '', unitPriceTaxIncluded: '', lineAmountTaxIncluded: '', hsCodeStatus: 'UNCONFIRMED' };
265
- return _jsxs(_Fragment, { children: [props.supplier && props.order && _jsx(ProcurementContextBar, { level: "order", supplier: props.supplier, order: props.order, onRouteChange: props.onRouteChange }), _jsx(ProcurementLevelHeading, { title: "\u91C7\u8D2D\u5546\u54C1\u5217\u8868", description: "\u67E5\u770B\u5F53\u524D\u91C7\u8D2D\u8BA2\u5355\u7684\u5546\u54C1\u660E\u7EC6\uFF0C\u53EF\u65B0\u589E\u3001\u5BA1\u6838\u548C\u903B\u8F91\u5220\u9664\u5546\u54C1\u3002", commands: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: props.busy || !props.order?.id, onClick: () => onLineDialog({ mode: 'create', item: newLine }), children: [_jsx("i", { className: "ri-add-line", "aria-hidden": "true" }), "\u65B0\u589E\u5546\u54C1"] }) }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(InfoNotice, { children: "\u53EF\u901A\u8FC7\u5BA1\u6838\u5B8C\u5584\u82F1\u6587\u540D\u79F0\u3001\u6D77\u5173\u7F16\u7801\u3001\u9000\u7A0E\u7387\u548C\u98CE\u9669\u5339\u914D\u4FE1\u606F\u3002" }), _jsx(DataGrid, { columns: columns, rows: props.purchaseLines.items, rowKey: (row, index) => row.id ?? index, pagination: pagination(props.purchaseLines), onPageChange: onPage, onPageSizeChange: onPageSize, emptyText: "\u6682\u65E0\u91C7\u8D2D\u5546\u54C1\u6570\u636E", actionWidth: 120, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: () => onLineDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
461
+ return _jsxs(_Fragment, { children: [props.supplier && props.order && _jsx(ProcurementContextBar, { level: "order", supplier: props.supplier, order: props.order, onRouteChange: props.onRouteChange }), _jsx(ProcurementLevelHeading, { title: "\u91C7\u8D2D\u5546\u54C1\u5217\u8868", description: "\u67E5\u770B\u5F53\u524D\u91C7\u8D2D\u8BA2\u5355\u7684\u5546\u54C1\u660E\u7EC6\uFF0C\u53EF\u65B0\u589E\u3001\u5BA1\u6838\u548C\u903B\u8F91\u5220\u9664\u5546\u54C1\u3002", commands: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: disabled || !props.order?.id, onClick: () => { if (!disabled)
462
+ onLineDialog({ mode: 'create', item: newLine }); }, children: [_jsx("i", { className: "ri-add-line", "aria-hidden": "true" }), "\u65B0\u589E\u5546\u54C1"] }) }), _jsx(FilterPanel, { definitions: definitions, values: filters, onChange: onFilter, onQuery: onSubmit, onReset: onReset }), _jsx(InfoNotice, { children: "\u53EF\u901A\u8FC7\u5BA1\u6838\u5B8C\u5584\u82F1\u6587\u540D\u79F0\u3001\u6D77\u5173\u7F16\u7801\u3001\u9000\u7A0E\u7387\u548C\u98CE\u9669\u5339\u914D\u4FE1\u606F\u3002" }), _jsx(DataGrid, { columns: columns, rows: props.purchaseLines.items, rowKey: "id", selection: { selectedKeys: selectedIds, onToggle, onTogglePage: () => onTogglePage(props.purchaseLines.items.map(row => row.id)), onClear, onDelete: onBulkDelete, labelFor: row => `选择采购商品:${row.productName || '未命名记录'}`, disabled }, pagination: pagination(props.purchaseLines), onPageChange: onPage, onPageSizeChange: onPageSize, emptyText: "\u6682\u65E0\u91C7\u8D2D\u5546\u54C1\u6570\u636E", actionWidth: 120, actions: (row) => _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", disabled: disabled, onClick: () => onLineDialog({ mode: 'review', item: row }), children: [_jsx("i", { className: "ri-edit-line", "aria-hidden": "true" }), "\u5BA1\u6838"] }), _jsxs("button", { type: "button", className: "is-danger", disabled: disabled, onClick: () => onDelete(row), children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u5220\u9664"] })] }) })] });
266
463
  }
267
464
  function ProcurementLevelHeading({ title, description, commands }) {
268
465
  return _jsxs("header", { className: "tcw-procurement-level-heading", children: [_jsxs("div", { children: [_jsx("h2", { children: title }), _jsx("p", { children: description })] }), commands && _jsx("div", { className: "tcw-procurement-toolbar", children: commands })] });
@@ -279,7 +476,7 @@ function MetricStrip({ metrics }) {
279
476
  ];
280
477
  return _jsxs("section", { className: "tcw-procurement-metrics", "aria-label": "\u91C7\u8D2D\u7EDF\u8BA1", children: [items.map(([label, value, icon, tone]) => _jsxs("div", { className: `tcw-procurement-metric tcw-procurement-metric--${tone}`, children: [_jsx("i", { className: `${icon} tcw-procurement-metric-icon`, "aria-hidden": "true" }), _jsxs("div", { className: "tcw-procurement-metric-copy", children: [_jsx("span", { children: label }), _jsx("strong", { children: value })] })] }, label)), _jsxs("div", { className: "tcw-procurement-top", children: [_jsx("span", { children: "\u91C7\u8D2D\u5546\u54C1TOP5" }), metrics.topProducts.length ? _jsx("ol", { children: metrics.topProducts.map((item) => _jsxs("li", { children: [_jsx("span", { children: item.productName }), _jsx("strong", { children: currencyCompact(item.amountCny) })] }, item.productName)) }) : _jsx("small", { children: "\u6682\u65E0\u5546\u54C1\u6570\u636E" })] })] });
281
478
  }
282
- function SupplierDialog({ state, busy, onClose, onSave }) {
479
+ export function SupplierDialog({ state, busy, onClose, onSave }) {
283
480
  const [value, setValue] = React.useState(state?.item ?? null);
284
481
  React.useEffect(() => setValue(state?.item ?? null), [state]);
285
482
  if (!state || !value)
@@ -290,7 +487,7 @@ function SupplierDialog({ state, busy, onClose, onSave }) {
290
487
  ];
291
488
  return _jsxs(Modal, { open: true, title: state.mode === 'detail' ? '供应商详情' : '供应商详情审核', size: "wide", onClose: onClose, footer: disabled ? _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }) : _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy || !value.name.trim(), onClick: () => void onSave(value), children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] })] }), children: [_jsxs("p", { className: "tcw-procurement-dialog-kicker", children: ["\u6765\u6E90\uFF1A\u91C7\u8D2D\u5408\u540C\u5F52\u5E76 ", _jsxs("span", { children: ["\u6700\u8FD1\u5236\u88C1\u68C0\u67E5\uFF1A", value.complianceCheckedAt ? formatBeijingDateTime(value.complianceCheckedAt) : '尚未检查'] })] }), _jsxs("div", { className: "tcw-procurement-supplier-review", children: [_jsx(FieldGrid, { columns: 1, children: fields.map(([key, label, required]) => _jsx(FormField, { label: label, required: required, children: _jsx("input", { disabled: disabled || key === 'code', value: String(value[key] ?? ''), onChange: (event) => setValue({ ...value, [key]: event.target.value }) }) }, key)) }), _jsxs("aside", { children: [_jsx(FormField, { label: "\u5907\u6CE8", children: _jsx("textarea", { rows: 6, disabled: disabled, value: value.remarks ?? '', onChange: (event) => setValue({ ...value, remarks: event.target.value }) }) }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: "\u5236\u88C1\u98CE\u9669" }), _jsx("dd", { children: _jsx(StatusTag, { tone: value.sanctionStatus === 'HIT' ? 'danger' : 'success', children: sanctionLabels[value.sanctionStatus ?? 'UNCHECKED'] }) })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u521B\u5EFA\u4EBA" }), _jsx("dd", { children: value.createdByName || '—' })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u521B\u5EFA\u65F6\u95F4" }), _jsx("dd", { children: formatBeijingDateTime(value.createdAt) })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u6700\u540E\u7F16\u8F91\u4EBA" }), _jsx("dd", { children: value.updatedByName || '—' })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u6700\u540E\u7F16\u8F91\u65F6\u95F4" }), _jsx("dd", { children: formatBeijingDateTime(value.updatedAt) })] })] }), _jsx(InfoNotice, { children: "\u5236\u88C1\u98CE\u9669\u4E3A\u7CFB\u7EDF\u6700\u8FD1\u4E00\u6B21\u68C0\u67E5\u7ED3\u679C\uFF0C\u4FDD\u5B58\u540E\u5C06\u89E6\u53D1\u65B0\u7684\u5236\u88C1\u68C0\u67E5\u3002" })] })] })] });
292
489
  }
293
- function OrderDialog({ state, lines, busy, onClose, onSave }) {
490
+ export function OrderDialog({ state, lines, busy, onClose, onSave }) {
294
491
  const [value, setValue] = React.useState(state?.item ?? null);
295
492
  React.useEffect(() => setValue(state?.item ?? null), [state]);
296
493
  if (!state || !value)
@@ -316,7 +513,7 @@ export function LineDialog({ state, order, busy, onAction, onClose, onSave }) {
316
513
  };
317
514
  return _jsxs(_Fragment, { children: [_jsx(Modal, { open: true, title: state.mode === 'create' ? '新增商品' : '采购商品审核', size: "wide", onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy || errors.length > 0, onClick: () => void onSave(value), children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] })] }), children: _jsxs("div", { className: "tcw-procurement-line-form", children: [_jsxs("div", { className: "tcw-procurement-source-label", children: ["\u6765\u6E90\u7C7B\u578B\uFF1A", sourceTypeLabels[value.sourceType]] }), state.mode === 'review' && errors.length > 0 && _jsx(RiskNotice, { children: errors.join(';') }), _jsxs(FieldGrid, { columns: 2, children: [_jsx(TextField, { label: "\u5546\u54C1\u540D\u79F0", required: true, value: value.productName, onChange: (next) => setValue({ ...value, productName: next }) }), _jsx(TextField, { label: "\u5546\u54C1\u63CF\u8FF0", value: value.productDescription, onChange: (next) => setValue({ ...value, productDescription: next }) }), _jsx(TextField, { label: "\u89C4\u683C\u578B\u53F7", value: value.modelSpecification, onChange: (next) => setValue({ ...value, modelSpecification: next }) }), _jsx(TextField, { label: "\u5355\u4F4D", required: true, value: value.unit, onChange: (next) => setValue({ ...value, unit: next }) }), _jsx(TextField, { label: "\u6570\u91CF", required: true, type: "number", value: value.quantity, onChange: (next) => setValue({ ...value, quantity: next, lineAmountTaxIncluded: String(amount(next) * amount(value.unitPriceTaxIncluded)) }) }), _jsx(TextField, { label: "\u542B\u7A0E\u5355\u4EF7\uFF08\u4EBA\u6C11\u5E01\uFF09", required: true, type: "number", value: value.unitPriceTaxIncluded, onChange: (next) => setValue({ ...value, unitPriceTaxIncluded: next, lineAmountTaxIncluded: String(amount(value.quantity) * amount(next)) }) }), _jsx(TextField, { label: "\u542B\u7A0E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09", required: true, type: "number", value: value.lineAmountTaxIncluded || String(computedAmount), onChange: (next) => setValue({ ...value, lineAmountTaxIncluded: next }) }), _jsx(TextField, { label: "\u589E\u503C\u7A0E\u7387", required: true, type: "number", value: value.vatRate, onChange: (next) => setValue({ ...value, vatRate: next }) }), _jsx(FormField, { label: "\u6D77\u5173\u7F16\u7801", children: _jsxs("div", { className: "tcw-procurement-inline-field", children: [_jsx("input", { value: value.hsCode ?? '', onChange: (event) => setValue({ ...value, hsCode: event.target.value }) }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => setHsOpen(true), children: [_jsx("i", { className: "ri-search-line", "aria-hidden": "true" }), "HS\u7F16\u7801\u67E5\u8BE2"] })] }) }), _jsx(FormField, { label: "\u7F16\u7801\u786E\u8BA4\u72B6\u6001", children: _jsx("div", { className: "tcw-procurement-line-status", children: enumOptions(hsStatusLabels).map((item) => _jsxs("label", { children: [_jsx("input", { type: "radio", name: "line-hs-code-status", value: item.value, checked: (value.hsCodeStatus ?? 'UNCONFIRMED') === item.value, onChange: () => setValue({ ...value, hsCodeStatus: item.value }) }), item.label] }, item.value)) }) }), _jsx(TextField, { label: "\u9000\u7A0E\u7387", required: true, type: "number", value: value.refundRate, onChange: (next) => setValue({ ...value, refundRate: next }) }), _jsx(TextField, { label: "\u9000\u7A0E\u653F\u7B56\u4F9D\u636E", required: true, value: value.refundPolicyReference, onChange: (next) => setValue({ ...value, refundPolicyReference: next }) }), _jsx(TextField, { label: "\u653F\u7B56\u65E5\u671F", required: true, type: "date", value: value.refundPolicyDate, onChange: (next) => setValue({ ...value, refundPolicyDate: next, refundValidationMethod: 'REVIEW_CONFIRMED' }) }), _jsx(TextField, { label: "\u82F1\u6587\u540D\u79F0", value: value.englishName, onChange: (next) => setValue({ ...value, englishName: next }) })] })] }) }), _jsx(HsCodeLookupDialog, { open: hsOpen, initialQuery: value.hsCode || value.englishName || value.productName, onAction: hsAction, onClose: () => setHsOpen(false) })] });
318
515
  }
319
- function PurchaseContractReviewDialog({ open, task, draft, busy, onChange, onClose, onSave }) {
516
+ export function PurchaseContractReviewDialog({ open, task, draft, busy, onChange, onClose, onSave }) {
320
517
  const errors = draft ? validatePurchaseContractReview(draft) : [];
321
518
  const duplicate = task?.duplicateContractError;
322
519
  return _jsxs(Modal, { open: open, title: "\u91C7\u8D2D\u5408\u540C\u5BA1\u6838", size: "wide", onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: busy || !draft || Boolean(duplicate) || errors.length > 0, onClick: () => draft && void onSave(draft), children: [_jsx("i", { className: "ri-check-double-line", "aria-hidden": "true" }), "\u5BA1\u6838\u5E76\u5165\u5E93"] })] }), children: [duplicate && _jsx(RiskNotice, { children: duplicate }), !draft ? _jsx(RiskNotice, { children: task?.errorMessage || '采购合同解析结果不可用' }) : _jsxs("div", { className: "tcw-procurement-review-compact", children: [_jsxs("section", { className: "tcw-procurement-review-editor-pane", children: [_jsx("h3", { children: "\u8BC6\u522B\u5185\u5BB9" }), _jsxs("div", { className: "tcw-procurement-review-editor", children: [errors.length > 0 && _jsx(RiskNotice, { children: errors.join(';') }), _jsx(ReviewSection, { title: "\u4F9B\u5E94\u5546\u4FE1\u606F", children: _jsx(SupplierDraftEditor, { supplier: draft.supplier, onChange: (supplier) => onChange({ ...draft, supplier }) }) }), _jsx(ReviewSection, { title: "\u8BA2\u5355\u4FE1\u606F", children: _jsxs(FieldGrid, { columns: 3, children: [_jsx(TextField, { label: "\u91C7\u8D2D\u5408\u540C\u7F16\u53F7", required: true, value: draft.order.contractNo, onChange: (value) => onChange({ ...draft, order: { ...draft.order, contractNo: value } }) }), _jsx(TextField, { label: "\u4E0B\u5355\u65E5\u671F", type: "date", value: draft.order.orderDate, onChange: (value) => onChange({ ...draft, order: { ...draft.order, orderDate: value } }) }), _jsx(TextField, { label: "\u91C7\u8D2D\u603B\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09", type: "number", value: draft.order.contractAmountCny, onChange: (value) => onChange({ ...draft, order: { ...draft.order, contractAmountCny: value } }) }), _jsx(TextField, { label: "\u5DF2\u4ED8\u6B3E", type: "number", value: draft.order.paidAmountCny, onChange: (value) => onChange({ ...draft, order: { ...draft.order, paidAmountCny: value } }) }), _jsx(TextField, { label: "\u5DF2\u9000\u7A0E", type: "number", value: draft.order.refundedAmountCny, onChange: (value) => onChange({ ...draft, order: { ...draft.order, refundedAmountCny: value } }) })] }) }), _jsx(ReviewSection, { title: `全部商品(${draft.lines.length})`, children: _jsx("div", { className: "tcw-procurement-review-lines", children: draft.lines.map((line, index) => _jsx(ContractLineEditor, { index: index, line: line, onChange: (next) => onChange({ ...draft, lines: draft.lines.map((item, lineIndex) => lineIndex === index ? next : item) }) }, line.id ?? index)) }) })] })] }), _jsxs("section", { className: "tcw-procurement-review-source-pane", children: [_jsx("h3", { children: "\u6E90\u6587\u4EF6\u5185\u5BB9" }), _jsx(SourcePreview, { draft: draft, task: task })] })] })] });
@@ -353,6 +550,13 @@ function StatusTag({ children, tone }) { return _jsx("span", { className: `tcw-p
353
550
  function TextField({ label, value, required, type = 'text', onChange }) { return _jsx(FormField, { label: label, required: required, children: _jsx("input", { type: type, value: String(value ?? ''), onChange: (event) => onChange(event.target.value) }) }); }
354
551
  function MoneyField({ label, value, disabled, onChange }) { return _jsx(FormField, { label: label, children: _jsx("input", { type: "number", disabled: disabled, value: String(value ?? ''), onChange: (event) => onChange?.(event.target.value) }) }); }
355
552
  function routeCollection(route) { return route.level === 'suppliers' ? 'suppliers' : route.level === 'orders' ? 'purchaseOrders' : 'purchaseLines'; }
553
+ function procurementBulkLabel(entityType, row) {
554
+ if (entityType === 'supplier')
555
+ return row.name || row.id;
556
+ if (entityType === 'purchaseOrder')
557
+ return row.contractNo || row.id;
558
+ return row.productName || row.id;
559
+ }
356
560
  function stateIsCreate(state) { return state?.mode === 'create'; }
357
561
  function orderStatus(row) {
358
562
  if (row.amountMismatch && row.amountMismatchConfirmed)