@chenchaolong/plugin-trade-compliance-workbench 1.0.87 → 1.0.89

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 (41) hide show
  1. package/dist/lib/entities/procurement.entity.d.ts +2 -1
  2. package/dist/lib/entities/procurement.entity.d.ts.map +1 -1
  3. package/dist/lib/entities/procurement.entity.js +4 -0
  4. package/dist/lib/entities/procurement.entity.js.map +1 -1
  5. package/dist/lib/import-jobs/import-command.builder.js +1 -1
  6. package/dist/lib/import-jobs/import-command.builder.js.map +1 -1
  7. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  8. package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
  9. package/dist/lib/remote-ui/app-source.js +2 -2
  10. package/dist/lib/remote-ui/app-source.js.map +1 -1
  11. package/dist/lib/remote-ui/components/forms.d.ts +3 -1
  12. package/dist/lib/remote-ui/components/forms.d.ts.map +1 -1
  13. package/dist/lib/remote-ui/components/forms.js +3 -2
  14. package/dist/lib/remote-ui/components/forms.js.map +1 -1
  15. package/dist/lib/remote-ui/pages/catalogs.js +2 -2
  16. package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
  17. package/dist/lib/remote-ui/pages/procurement-contracts.css +66 -11
  18. package/dist/lib/remote-ui/pages/procurement.d.ts +28 -0
  19. package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
  20. package/dist/lib/remote-ui/pages/procurement.js +121 -18
  21. package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
  22. package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
  23. package/dist/lib/remote-ui/pages/sales.js +1 -2
  24. package/dist/lib/remote-ui/pages/sales.js.map +1 -1
  25. package/dist/lib/remote-ui/procurement-contracts.d.ts +3 -1
  26. package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -1
  27. package/dist/lib/remote-ui/procurement-contracts.js.map +1 -1
  28. package/dist/lib/remote-ui/purchase-review-adapter.js +4 -3
  29. package/dist/lib/remote-ui/purchase-review-adapter.js.map +1 -1
  30. package/dist/lib/schema-maintenance.d.ts.map +1 -1
  31. package/dist/lib/schema-maintenance.js +2 -0
  32. package/dist/lib/schema-maintenance.js.map +1 -1
  33. package/dist/lib/workbench-view.provider.d.ts +1 -0
  34. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  35. package/dist/lib/workbench.middleware.js +4 -4
  36. package/dist/lib/workbench.middleware.js.map +1 -1
  37. package/dist/lib/workbench.service.d.ts +1 -0
  38. package/dist/lib/workbench.service.d.ts.map +1 -1
  39. package/dist/lib/workbench.service.js +4 -3
  40. package/dist/lib/workbench.service.js.map +1 -1
  41. package/package.json +1 -1
@@ -11,6 +11,7 @@ import './procurement-contracts.css';
11
11
  const PAGE_SIZE = 20;
12
12
  const sanctionLabels = { UNCHECKED: '未检查', CLEAR: '无命中', HIT: '命中提醒' };
13
13
  const hsStatusLabels = { UNCONFIRMED: '待审核', AI_CONFIRMED: 'AI确认', MANUALLY_CONFIRMED: '人工确认' };
14
+ const reviewStatusLabels = { PENDING: '待审核', REVIEWED: '已审核' };
14
15
  const complianceLabels = { DUAL_USE: '军民两用', OVERSEAS_SANCTION: '海外制裁' };
15
16
  const sourceTypeLabels = { CONTRACT_EXTRACTED: '合同提取', MANUAL_ENTRY: '手工新增' };
16
17
  const supplierFilters = [
@@ -24,7 +25,7 @@ const orderFilters = [
24
25
  { type: 'text', key: 'contractNo', label: '采购订单编号' },
25
26
  { type: 'text', key: 'customerContractNo', label: '采购合同编号' },
26
27
  { type: 'text', key: 'supplierName', label: '供应商' },
27
- { type: 'date-range', key: 'createdAt', label: '创建时间' }
28
+ { type: 'date-range', key: 'orderDate', label: '下单日期' }
28
29
  ];
29
30
  const lineFilters = [
30
31
  { type: 'text', key: 'productName', label: '商品名称' },
@@ -45,7 +46,7 @@ export const procurementFilterMappings = {
45
46
  contractNo: { type: 'fuzzy', accessor: 'contractNo' },
46
47
  customerContractNo: { type: 'fuzzy', accessor: 'customerContractNo' },
47
48
  supplierName: { type: 'exact', accessor: 'supplierId' },
48
- createdAt: { type: 'range', accessor: 'createdAt' },
49
+ orderDate: { type: 'range', accessor: 'orderDate' },
49
50
  linkStatus: { type: 'exact', accessor: (row) => row.customerContractId || row.customerContractNo ? 'LINKED' : 'UNLINKED' }
50
51
  },
51
52
  purchaseLines: {
@@ -53,6 +54,7 @@ export const procurementFilterMappings = {
53
54
  productDescription: { type: 'fuzzy', accessor: 'productDescription' },
54
55
  hsCode: { type: 'fuzzy', accessor: 'hsCode' },
55
56
  hsCodeStatus: { type: 'exact', accessor: 'hsCodeStatus' },
57
+ reviewStatus: { type: 'exact', accessor: 'reviewStatus' },
56
58
  complianceCategory: { type: 'exact', accessor: (row) => row.complianceCategories ?? [] }
57
59
  }
58
60
  };
@@ -324,9 +326,80 @@ export function validatePurchaseContractReview(draft) {
324
326
  errors.push('已退税不能超过预计退税总额');
325
327
  return errors;
326
328
  }
329
+ export function buildPurchaseContractReviewFieldErrors(draft, contractNoError) {
330
+ const fieldErrors = emptyPurchaseContractReviewFieldErrors();
331
+ for (const error of validatePurchaseContractReview(draft))
332
+ assignPurchaseContractReviewFieldError(fieldErrors, error);
333
+ if (contractNoError)
334
+ fieldErrors.contractNo = contractNoError;
335
+ return fieldErrors;
336
+ }
337
+ export function clearPurchaseContractReviewFieldError(errors, path) {
338
+ if (path.kind === 'root')
339
+ return { ...errors, [path.field]: undefined };
340
+ const currentLine = errors.lines[path.index];
341
+ if (!currentLine)
342
+ return errors;
343
+ const nextLine = { ...currentLine, [path.field]: undefined };
344
+ return { ...errors, lines: { ...errors.lines, [path.index]: nextLine } };
345
+ }
346
+ export function purchaseContractReviewHasFieldErrors(errors) {
347
+ return Boolean(errors.supplierName || errors.contractNo || errors.contractAmountCny || errors.paidAmountCny || errors.refundedAmountCny || Object.values(errors.lines).some(lineErrors => Object.values(lineErrors).some(Boolean)));
348
+ }
349
+ function emptyPurchaseContractReviewFieldErrors() {
350
+ return { lines: {} };
351
+ }
352
+ function duplicateContractNoFieldError(message) {
353
+ return message ? { ...emptyPurchaseContractReviewFieldErrors(), contractNo: message } : emptyPurchaseContractReviewFieldErrors();
354
+ }
355
+ function assignPurchaseContractReviewFieldError(errors, message) {
356
+ if (message === '供应商名称为必填项') {
357
+ errors.supplierName = message;
358
+ return;
359
+ }
360
+ if (message.includes('采购合同编号')) {
361
+ errors.contractNo = message;
362
+ return;
363
+ }
364
+ if (message.includes('已付款')) {
365
+ errors.paidAmountCny = message;
366
+ return;
367
+ }
368
+ if (message.includes('已退税')) {
369
+ errors.refundedAmountCny = message;
370
+ return;
371
+ }
372
+ if (message.includes('采购订单总额')) {
373
+ errors.contractAmountCny = message;
374
+ return;
375
+ }
376
+ const lineMatch = message.match(/^第(\d+)条商品(.+)$/);
377
+ if (!lineMatch)
378
+ return;
379
+ const lineIndex = Number(lineMatch[1]) - 1;
380
+ const field = purchaseContractReviewLineErrorField(lineMatch[2]);
381
+ if (field)
382
+ errors.lines[lineIndex] = { ...errors.lines[lineIndex], [field]: message };
383
+ }
384
+ function purchaseContractReviewLineErrorField(message) {
385
+ if (message.includes('名称'))
386
+ return 'productName';
387
+ if (message.includes('数量'))
388
+ return 'quantity';
389
+ if (message.includes('含税单价'))
390
+ return 'unitPriceTaxIncluded';
391
+ if (message.includes('含税金额') || message.includes('金额差异'))
392
+ return 'lineAmountTaxIncluded';
393
+ if (message.includes('增值税率'))
394
+ return 'vatRate';
395
+ if (message.includes('退税率'))
396
+ return 'refundRate';
397
+ return null;
398
+ }
327
399
  export function ProcurementPage(props) {
328
400
  const routeIdentity = procurementRouteSelectionKey(props.route);
329
401
  const [filters, setFilters] = React.useState({});
402
+ const [lineReviewStatus, setLineReviewStatus] = React.useState('PENDING');
330
403
  const [selectedIds, setSelectedIds] = React.useState(new Set());
331
404
  const [bulkOpen, setBulkOpen] = React.useState(false);
332
405
  const [bulkBusy, setBulkBusy] = React.useState(false);
@@ -359,9 +432,9 @@ export function ProcurementPage(props) {
359
432
  const query = React.useCallback((page, nextFilters, pageSize) => selectionQuery(collectionKey, {
360
433
  page,
361
434
  pageSize,
362
- filters: nextFilters,
435
+ filters: collectionKey === 'purchaseLines' ? { ...nextFilters, reviewStatus: nextFilters.reviewStatus ?? lineReviewStatus } : nextFilters,
363
436
  parameters: props.route.level === 'orders' ? { supplierId: props.route.supplierId } : props.route.level === 'lines' ? { supplierId: props.route.supplierId, purchaseOrderId: props.route.orderId } : undefined
364
- }), [collectionKey, props.route, selectionQuery]);
437
+ }), [collectionKey, lineReviewStatus, props.route, selectionQuery]);
365
438
  const scheduler = React.useMemo(() => createProcurementQueryScheduler((page, nextFilters, pageSize) => void query(page, nextFilters, pageSize)), [query]);
366
439
  React.useEffect(() => () => scheduler.cancel(), [scheduler]);
367
440
  React.useEffect(() => { setDraft(props.reviewDraft); }, [props.reviewDraft]);
@@ -370,6 +443,10 @@ export function ProcurementPage(props) {
370
443
  setBulkFailures([]);
371
444
  setBulkOpen(false);
372
445
  }, [routeIdentity]);
446
+ React.useEffect(() => {
447
+ if (props.route.level === 'lines')
448
+ setLineReviewStatus('PENDING');
449
+ }, [props.route.level, routeIdentity]);
373
450
  React.useEffect(() => {
374
451
  const reconciled = reconcileProcurementBulkState(selectedIds, bulkFailures, activeRows);
375
452
  if (reconciled.selectedIds !== selectedIds)
@@ -420,7 +497,7 @@ export function ProcurementPage(props) {
420
497
  ? { title: '批量删除采购订单', message: '将同时逻辑删除所选采购订单及其全部商品明细;后续重新导入同编号合同将作为全新记录入库,不迁移旧订单人工金额数据。' }
421
498
  : { title: '批量删除采购商品', message: '所选采购商品将被逻辑删除,不影响当前采购订单中的其他商品。' };
422
499
  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)
423
- 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)
500
+ 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, reviewStatus: lineReviewStatus, onReviewStatus: (status) => { setLineReviewStatus(status); setSelectedIds(new Set()); scheduler.flush({ ...filters, reviewStatus: status }, currentPageSize); }, 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)
424
501
  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)
425
502
  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)
426
503
  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)
@@ -489,9 +566,9 @@ function OrderLevel({ props, filters, definitions, onFilter, onSubmit, onReset,
489
566
  { type: 'text', key: 'paidAmountCny', header: '已付款', width: 130, align: 'right', truncate: false, format: currency },
490
567
  { type: 'text', key: 'refundedAmountCny', header: '已退税', width: 130, align: 'right', truncate: false, format: currency }
491
568
  ];
492
- 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\u91D1\u989D\u3001\u4F9B\u5E94\u5546\u548C\u5173\u8054\u5408\u540C\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"] })] }) })] });
569
+ return _jsxs(_Fragment, { children: [props.supplier && _jsx(ProcurementContextBar, { level: "supplier", supplier: props.supplier, onRouteChange: props.onRouteChange }), 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"] })] }) })] });
493
570
  }
494
- function LineLevel({ props, filters, definitions, onFilter, onSubmit, onReset, onPage, onPageSize, onLineDialog, onDelete, selectedIds, onToggle, onTogglePage, onClear, onBulkDelete, disabled }) {
571
+ function LineLevel({ props, filters, definitions, reviewStatus, onReviewStatus, onFilter, onSubmit, onReset, onPage, onPageSize, onLineDialog, onDelete, selectedIds, onToggle, onTogglePage, onClear, onBulkDelete, disabled }) {
495
572
  const columns = [
496
573
  { type: 'ellipsis', key: 'productName', header: '商品名称', width: 170, maxWidth: 170 },
497
574
  { type: 'ellipsis', key: 'productDescription', header: '商品描述', width: 190, maxWidth: 190 },
@@ -503,18 +580,20 @@ function LineLevel({ props, filters, definitions, onFilter, onSubmit, onReset, o
503
580
  { type: 'text', key: 'vatRate', header: '增值税率', width: 100, align: 'right', truncate: false, format: percent },
504
581
  { type: 'text', key: 'hsCode', header: '海关编码', width: 130 },
505
582
  { type: 'text', key: 'refundRate', header: '退税率', width: 90, align: 'right', truncate: false, format: percent },
583
+ { type: 'custom', key: 'reviewStatus', header: '审核状态', width: 100, align: 'center', truncate: false, render: (_, row) => _jsx(StatusTag, { tone: row.reviewStatus === 'REVIEWED' ? 'success' : 'warning', children: reviewStatusLabels[row.reviewStatus === 'REVIEWED' ? 'REVIEWED' : 'PENDING'] }) },
506
584
  { type: 'custom', key: 'hsCodeStatus', header: '编码确认状态', width: 120, align: 'center', truncate: false, render: (_, row) => _jsx(StatusTag, { tone: row.hsCodeStatus === 'UNCONFIRMED' || !row.hsCodeStatus ? 'warning' : 'success', children: hsStatusLabels[row.hsCodeStatus ?? 'UNCONFIRMED'] }) },
585
+ { type: 'text', key: 'sourceType', header: '来源类型', width: 110, truncate: false, format: (value) => sourceTypeLabels[value] ?? '—' },
507
586
  { type: 'text', key: 'complianceCategories', header: '命中合规分类', width: 150, format: (value) => Array.isArray(value) && value.length ? value.map((item) => complianceLabels[item]).join('、') : '无命中' }
508
587
  ];
509
588
  const newLine = { purchaseOrderId: props.order?.id ?? '', sourceType: 'MANUAL_ENTRY', productName: '', quantity: '', unitPriceTaxIncluded: '', lineAmountTaxIncluded: '', hsCodeStatus: 'UNCONFIRMED' };
510
- 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)
511
- 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"] })] }) })] });
589
+ return _jsxs(_Fragment, { children: [props.supplier && props.order && _jsx(ProcurementContextBar, { level: "order", supplier: props.supplier, order: props.order, onRouteChange: props.onRouteChange }), _jsx("div", { className: "tcw-procurement-toolbar", children: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: disabled || !props.order?.id, onClick: () => { if (!disabled)
590
+ 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("div", { className: "tcw-procurement-review-tabs", role: "tablist", "aria-label": "\u5BA1\u6838\u72B6\u6001", children: Object.keys(reviewStatusLabels).map(status => _jsxs("button", { type: "button", role: "tab", "aria-selected": reviewStatus === status, className: `tcw-procurement-review-tab${reviewStatus === status ? ' is-active' : ''}`, onClick: () => onReviewStatus(status), children: [_jsx("i", { className: status === 'PENDING' ? 'ri-time-line' : 'ri-checkbox-circle-line', "aria-hidden": "true" }), reviewStatusLabels[status]] }, status)) }), _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"] })] }) })] });
512
591
  }
513
592
  function ProcurementLevelHeading({ title, description, commands }) {
514
593
  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 })] });
515
594
  }
516
595
  export function ProcurementContextBar(props) {
517
- return _jsxs("section", { className: `tcw-procurement-context tcw-procurement-context--${props.level}`, "aria-label": props.level === 'supplier' ? '供应商上下文' : '采购订单上下文', children: [props.level === 'supplier' ? _jsxs(_Fragment, { children: [_jsx(ContextValue, { label: "\u4F9B\u5E94\u5546\u4EE3\u7801", value: props.supplier.code }), _jsx(ContextValue, { label: "\u4F9B\u5E94\u5546\u540D\u79F0", value: props.supplier.name }), _jsx(ContextValue, { label: "\u8054\u7CFB\u4EBA", value: props.supplier.contactName }), _jsx(ContextValue, { label: "\u8054\u7CFB\u7535\u8BDD", value: props.supplier.phone })] }) : _jsxs(_Fragment, { children: [_jsx(ContextValue, { label: "\u4F9B\u5E94\u5546", value: props.supplier.name }), _jsx(ContextValue, { label: "\u91C7\u8D2D\u5408\u540C\u7F16\u53F7", value: props.order.contractNo }), _jsx(ContextValue, { label: "\u4E0B\u5355\u65E5\u671F", value: formatBusinessDate(props.order.orderDate) }), _jsx(ContextValue, { label: "\u91C7\u8D2D\u91D1\u989D", value: currency(props.order.contractAmountCny) })] }), _jsx("div", { className: "tcw-procurement-context-actions", children: props.level === 'supplier' ? _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => props.onRouteChange({ type: 'procurement', level: 'suppliers' }), children: [_jsx("i", { className: "ri-arrow-left-line", "aria-hidden": "true" }), "\u8FD4\u56DE\u4F9B\u5E94\u5546\u5217\u8868"] }) : _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => props.onRouteChange({ type: 'procurement', level: 'orders', supplierId: props.supplier.id ?? props.order.supplierId }), children: [_jsx("i", { className: "ri-arrow-left-line", "aria-hidden": "true" }), "\u8FD4\u56DE\u8BA2\u5355\u5217\u8868"] }) })] });
596
+ return _jsx("section", { className: `tcw-procurement-context tcw-procurement-context--${props.level}`, "aria-label": props.level === 'supplier' ? '供应商上下文' : '采购订单上下文', children: props.level === 'supplier' ? _jsxs(_Fragment, { children: [_jsx(ContextValue, { label: "\u4F9B\u5E94\u5546\u4EE3\u7801", value: props.supplier.code }), _jsx(ContextValue, { label: "\u4F9B\u5E94\u5546\u540D\u79F0", value: props.supplier.name }), _jsx(ContextValue, { label: "\u8054\u7CFB\u4EBA", value: props.supplier.contactName }), _jsx(ContextValue, { label: "\u8054\u7CFB\u7535\u8BDD", value: props.supplier.phone })] }) : _jsxs(_Fragment, { children: [_jsx(ContextValue, { label: "\u4F9B\u5E94\u5546", value: props.supplier.name }), _jsx(ContextValue, { label: "\u91C7\u8D2D\u5408\u540C\u7F16\u53F7", value: props.order.contractNo }), _jsx(ContextValue, { label: "\u4E0B\u5355\u65E5\u671F", value: formatBusinessDate(props.order.orderDate) }), _jsx(ContextValue, { label: "\u91C7\u8D2D\u91D1\u989D", value: currency(props.order.contractAmountCny) })] }) });
518
597
  }
519
598
  function MetricStrip({ metrics }) {
520
599
  const items = [
@@ -588,21 +667,42 @@ async function translatePurchaseProductName(onAction, productName) {
588
667
  }
589
668
  }
590
669
  export function PurchaseContractReviewDialog({ open, task, draft, busy, onChange, onClose, onSave }) {
591
- const errors = draft ? validatePurchaseContractReview(draft) : [];
592
- const duplicate = task?.duplicateContractError;
593
- 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 })] })] })] });
670
+ const [showValidationErrors, setShowValidationErrors] = React.useState(false);
671
+ const [fieldErrors, setFieldErrors] = React.useState(() => duplicateContractNoFieldError(task?.duplicateContractError));
672
+ React.useEffect(() => {
673
+ setShowValidationErrors(false);
674
+ setFieldErrors(duplicateContractNoFieldError(task?.duplicateContractError));
675
+ }, [open, task?.id, task?.duplicateContractError]);
676
+ const changeDraft = (nextDraft, path) => {
677
+ if (path)
678
+ setFieldErrors(current => clearPurchaseContractReviewFieldError(current, path));
679
+ onChange(nextDraft);
680
+ };
681
+ const submit = () => {
682
+ if (!draft)
683
+ return;
684
+ const nextErrors = buildPurchaseContractReviewFieldErrors(draft, fieldErrors.contractNo === task?.duplicateContractError ? task?.duplicateContractError : null);
685
+ setShowValidationErrors(true);
686
+ setFieldErrors(nextErrors);
687
+ if (purchaseContractReviewHasFieldErrors(nextErrors))
688
+ return;
689
+ void onSave(draft);
690
+ };
691
+ const visibleErrors = showValidationErrors || fieldErrors.contractNo ? fieldErrors : emptyPurchaseContractReviewFieldErrors();
692
+ return _jsx(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, onClick: submit, children: [_jsx("i", { className: "ri-check-double-line", "aria-hidden": "true" }), "\u5BA1\u6838\u5E76\u5165\u5E93"] })] }), children: !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: [_jsx(ReviewSection, { title: "\u4F9B\u5E94\u5546\u4FE1\u606F", children: _jsx(SupplierDraftEditor, { supplier: draft.supplier, errors: visibleErrors, onChange: (supplier, path) => changeDraft({ ...draft, supplier }, path) }) }), _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, error: visibleErrors.contractNo, onChange: (value) => changeDraft({ ...draft, order: { ...draft.order, contractNo: value } }, { kind: 'root', field: 'contractNo' }) }), _jsx(TextField, { label: "\u4E0B\u5355\u65E5\u671F", type: "date", value: draft.order.orderDate, onChange: (value) => changeDraft({ ...draft, order: { ...draft.order, orderDate: value } }) }), _jsx(TextField, { label: "\u91C7\u8D2D\u603B\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09", type: "number", value: draft.order.contractAmountCny, error: visibleErrors.contractAmountCny, onChange: (value) => changeDraft({ ...draft, order: { ...draft.order, contractAmountCny: value } }, { kind: 'root', field: 'contractAmountCny' }) }), _jsx(TextField, { label: "\u5DF2\u4ED8\u6B3E", type: "number", value: draft.order.paidAmountCny, error: visibleErrors.paidAmountCny, onChange: (value) => changeDraft({ ...draft, order: { ...draft.order, paidAmountCny: value } }, { kind: 'root', field: 'paidAmountCny' }) }), _jsx(TextField, { label: "\u5DF2\u9000\u7A0E", type: "number", value: draft.order.refundedAmountCny, error: visibleErrors.refundedAmountCny, onChange: (value) => changeDraft({ ...draft, order: { ...draft.order, refundedAmountCny: value } }, { kind: 'root', field: 'refundedAmountCny' }) })] }) }), _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, errors: visibleErrors.lines[index] ?? {}, onChange: (next, path) => changeDraft({ ...draft, lines: draft.lines.map((item, lineIndex) => lineIndex === index ? next : item) }, path) }, 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 })] })] }) });
594
693
  }
595
- function ContractLineEditor({ line, index, onChange }) {
596
- return _jsxs("section", { className: "tcw-procurement-review-line", children: [_jsxs("h4", { children: ["\u5546\u54C1 ", index + 1, " ", _jsx(StatusTag, { tone: "muted", children: sourceTypeLabels[line.sourceType] })] }), _jsxs(FieldGrid, { columns: 3, children: [_jsx(TextField, { label: "\u5546\u54C1\u540D\u79F0", required: true, value: line.productName, onChange: (value) => onChange({ ...line, productName: value }) }), _jsx(TextField, { label: "\u5546\u54C1\u63CF\u8FF0", value: line.productDescription, onChange: (value) => onChange({ ...line, productDescription: value }) }), _jsx(TextField, { label: "\u89C4\u683C\u578B\u53F7", value: line.modelSpecification, onChange: (value) => onChange({ ...line, modelSpecification: value }) }), _jsx(TextField, { label: "\u5355\u4F4D", value: line.unit, onChange: (value) => onChange({ ...line, unit: value }) }), _jsx(TextField, { label: "\u6570\u91CF", type: "number", value: line.quantity, onChange: (value) => onChange({ ...line, quantity: value }) }), _jsx(TextField, { label: "\u542B\u7A0E\u91D1\u989D", type: "number", value: line.lineAmountTaxIncluded, onChange: (value) => onChange({ ...line, lineAmountTaxIncluded: value }) }), _jsx(TextField, { label: "\u6D77\u5173\u7F16\u7801", value: line.hsCode, onChange: (value) => onChange(applyManualHsCode(line, value)) }), line.amountMismatch && _jsx(FormField, { label: "\u91D1\u989D\u5DEE\u5F02\u786E\u8BA4", required: true, children: _jsxs("label", { className: "tcw-procurement-checkbox", children: [_jsx("input", { type: "checkbox", checked: Boolean(line.amountMismatchConfirmed), onChange: (event) => onChange({ ...line, amountMismatchConfirmed: event.target.checked }) }), "\u786E\u8BA4\u4FDD\u7559\u5408\u540C\u539F\u59CB\u91D1\u989D"] }) })] }), _jsxs("details", { className: "tcw-procurement-review-more", children: [_jsx("summary", { children: "\u66F4\u591A\u5546\u54C1\u4FE1\u606F" }), _jsxs(FieldGrid, { columns: 3, children: [_jsx(TextField, { label: "\u82F1\u6587\u540D\u79F0", value: line.englishName, onChange: (value) => onChange({ ...line, englishName: value }) }), _jsx(TextField, { label: "\u542B\u7A0E\u5355\u4EF7", type: "number", value: line.unitPriceTaxIncluded, onChange: (value) => onChange({ ...line, unitPriceTaxIncluded: value }) }), _jsx(TextField, { label: "\u589E\u503C\u7A0E\u7387", type: "number", value: line.vatRate, onChange: (value) => onChange({ ...line, vatRate: value }) }), _jsx(TextField, { label: "\u9000\u7A0E\u7387", type: "number", value: line.refundRate, onChange: (value) => onChange({ ...line, refundRate: value }) })] })] })] });
694
+ function ContractLineEditor({ line, index, errors, onChange }) {
695
+ const linePath = (field) => ({ kind: 'line', index, field });
696
+ return _jsxs("section", { className: "tcw-procurement-review-line", children: [_jsxs("h4", { children: ["\u5546\u54C1 ", index + 1, " ", _jsx(StatusTag, { tone: "muted", children: sourceTypeLabels[line.sourceType] })] }), _jsxs(FieldGrid, { columns: 3, children: [_jsx(TextField, { label: "\u5546\u54C1\u540D\u79F0", required: true, value: line.productName, error: errors.productName, onChange: (value) => onChange({ ...line, productName: value }, linePath('productName')) }), _jsx(TextField, { label: "\u5546\u54C1\u63CF\u8FF0", value: line.productDescription, onChange: (value) => onChange({ ...line, productDescription: value }) }), _jsx(TextField, { label: "\u89C4\u683C\u578B\u53F7", value: line.modelSpecification, onChange: (value) => onChange({ ...line, modelSpecification: value }) }), _jsx(TextField, { label: "\u5355\u4F4D", value: line.unit, onChange: (value) => onChange({ ...line, unit: value }) }), _jsx(TextField, { label: "\u6570\u91CF", type: "number", value: line.quantity, error: errors.quantity, onChange: (value) => onChange({ ...line, quantity: value }, linePath('quantity')) }), _jsx(TextField, { label: "\u542B\u7A0E\u91D1\u989D", type: "number", value: line.lineAmountTaxIncluded, error: errors.lineAmountTaxIncluded, onChange: (value) => onChange({ ...line, lineAmountTaxIncluded: value }, linePath('lineAmountTaxIncluded')) }), _jsx(TextField, { label: "\u6D77\u5173\u7F16\u7801", value: line.hsCode, onChange: (value) => onChange(applyManualHsCode(line, value)) }), line.amountMismatch && _jsx(FormField, { label: "\u91D1\u989D\u5DEE\u5F02\u786E\u8BA4", required: true, children: _jsxs("label", { className: "tcw-procurement-checkbox", children: [_jsx("input", { type: "checkbox", checked: Boolean(line.amountMismatchConfirmed), onChange: (event) => onChange({ ...line, amountMismatchConfirmed: event.target.checked }, linePath('lineAmountTaxIncluded')) }), "\u786E\u8BA4\u4FDD\u7559\u5408\u540C\u539F\u59CB\u91D1\u989D"] }) })] }), _jsxs("details", { className: "tcw-procurement-review-more", children: [_jsx("summary", { children: "\u66F4\u591A\u5546\u54C1\u4FE1\u606F" }), _jsxs(FieldGrid, { columns: 3, children: [_jsx(TextField, { label: "\u82F1\u6587\u540D\u79F0", value: line.englishName, onChange: (value) => onChange({ ...line, englishName: value }) }), _jsx(TextField, { label: "\u542B\u7A0E\u5355\u4EF7", type: "number", value: line.unitPriceTaxIncluded, error: errors.unitPriceTaxIncluded, onChange: (value) => onChange({ ...line, unitPriceTaxIncluded: value }, linePath('unitPriceTaxIncluded')) }), _jsx(TextField, { label: "\u589E\u503C\u7A0E\u7387", type: "number", value: line.vatRate, error: errors.vatRate, onChange: (value) => onChange({ ...line, vatRate: value }, linePath('vatRate')) }), _jsx(TextField, { label: "\u9000\u7A0E\u7387", type: "number", value: line.refundRate, error: errors.refundRate, onChange: (value) => onChange({ ...line, refundRate: value }, linePath('refundRate')) })] })] })] });
597
697
  }
598
- function SupplierDraftEditor({ supplier, onChange }) {
698
+ function SupplierDraftEditor({ supplier, errors, onChange }) {
599
699
  const coreFields = [
600
700
  ['name', '供应商名称', true], ['code', '供应商代码'], ['shortName', '简称']
601
701
  ];
602
702
  const moreFields = [
603
703
  ['countryOrRegion', '国别/地区'], ['address', '地址'], ['contactName', '联系人'], ['phone', '联系电话'], ['wechat', '微信'], ['email', '邮箱'], ['taxNumber', '税号'], ['paymentAccount', '收款账号'], ['bankName', '开户行'], ['taxpayerRating', '纳税等级'], ['website', '公司网站'], ['businessProducts', '经营产品']
604
704
  ];
605
- return _jsxs(_Fragment, { children: [_jsx(FieldGrid, { columns: 3, children: coreFields.map(([key, label, required]) => _jsx(TextField, { label: label, required: required, value: supplier[key], onChange: (value) => onChange({ ...supplier, [key]: value }) }, key)) }), _jsxs("details", { className: "tcw-procurement-review-more", children: [_jsx("summary", { children: "\u66F4\u591A\u4F9B\u5E94\u5546\u4FE1\u606F" }), _jsx(FieldGrid, { columns: 3, children: moreFields.map(([key, label]) => _jsx(TextField, { label: label, value: supplier[key], onChange: (value) => onChange({ ...supplier, [key]: value }) }, key)) })] })] });
705
+ return _jsxs(_Fragment, { children: [_jsx(FieldGrid, { columns: 3, children: coreFields.map(([key, label, required]) => _jsx(TextField, { label: label, required: required, value: supplier[key], error: key === 'name' ? errors.supplierName : undefined, onChange: (value) => onChange({ ...supplier, [key]: value }, key === 'name' ? { kind: 'root', field: 'supplierName' } : undefined) }, key)) }), _jsxs("details", { className: "tcw-procurement-review-more", children: [_jsx("summary", { children: "\u66F4\u591A\u4F9B\u5E94\u5546\u4FE1\u606F" }), _jsx(FieldGrid, { columns: 3, children: moreFields.map(([key, label]) => _jsx(TextField, { label: label, value: supplier[key], onChange: (value) => onChange({ ...supplier, [key]: value }) }, key)) })] })] });
606
706
  }
607
707
  function SourcePreview({ draft, task }) {
608
708
  const explicit = draft.lines.find(line => line.sourceContent)?.sourceContent ?? draft.sourceText;
@@ -621,7 +721,10 @@ function ReviewSection({ title, children }) { return _jsxs("section", { classNam
621
721
  function ContextValue({ label, value }) { return _jsxs("div", { children: [_jsx("span", { children: label }), _jsx("strong", { children: String(value || '—') })] }); }
622
722
  function LinkButton({ children, icon, title, onClick }) { return _jsxs("button", { type: "button", className: "tcw-procurement-link", title: title, onClick: onClick, children: [_jsx("i", { className: icon, "aria-hidden": "true" }), children] }); }
623
723
  function StatusTag({ children, tone }) { return _jsx("span", { className: `tcw-procurement-status tcw-procurement-status--${tone}`, children: children }); }
624
- 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) }) }); }
724
+ function TextField({ label, value, required, type = 'text', error, onChange }) {
725
+ const errorId = React.useId();
726
+ return _jsx(FormField, { label: label, required: required, error: error, errorId: errorId, children: _jsx("input", { type: type, value: String(value ?? ''), "aria-invalid": error ? true : undefined, "aria-describedby": error ? errorId : undefined, onChange: (event) => onChange(event.target.value) }) });
727
+ }
625
728
  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) }) }); }
626
729
  function routeCollection(route) { return route.level === 'suppliers' ? 'suppliers' : route.level === 'orders' ? 'purchaseOrders' : 'purchaseLines'; }
627
730
  function procurementBulkLabel(entityType, row) {