@chenchaolong/plugin-trade-compliance-workbench 1.0.25 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
- package/dist/lib/remote-ui/action-execution.d.ts +5 -2
- package/dist/lib/remote-ui/action-execution.d.ts.map +1 -1
- package/dist/lib/remote-ui/action-execution.js +2 -2
- package/dist/lib/remote-ui/action-execution.js.map +1 -1
- package/dist/lib/remote-ui/app-source.d.ts.map +1 -1
- package/dist/lib/remote-ui/app-source.js +122 -28
- package/dist/lib/remote-ui/app-source.js.map +1 -1
- package/dist/lib/remote-ui/bulk-delete.d.ts +56 -0
- package/dist/lib/remote-ui/bulk-delete.d.ts.map +1 -0
- package/dist/lib/remote-ui/bulk-delete.js +138 -0
- package/dist/lib/remote-ui/bulk-delete.js.map +1 -0
- package/dist/lib/remote-ui/components/bulk-delete.d.ts +21 -0
- package/dist/lib/remote-ui/components/bulk-delete.d.ts.map +1 -0
- package/dist/lib/remote-ui/components/bulk-delete.js +9 -0
- package/dist/lib/remote-ui/components/bulk-delete.js.map +1 -0
- package/dist/lib/remote-ui/components/data-grid.d.ts +12 -1
- package/dist/lib/remote-ui/components/data-grid.d.ts.map +1 -1
- package/dist/lib/remote-ui/components/data-grid.js +24 -5
- package/dist/lib/remote-ui/components/data-grid.js.map +1 -1
- package/dist/lib/remote-ui/components/task-status.d.ts +10 -1
- package/dist/lib/remote-ui/components/task-status.d.ts.map +1 -1
- package/dist/lib/remote-ui/components/task-status.js +79 -10
- package/dist/lib/remote-ui/components/task-status.js.map +1 -1
- package/dist/lib/remote-ui/customer-contract-contracts.d.ts +7 -2
- package/dist/lib/remote-ui/customer-contract-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/customer-contract-contracts.js.map +1 -1
- package/dist/lib/remote-ui/model.d.ts +1 -0
- package/dist/lib/remote-ui/model.d.ts.map +1 -1
- package/dist/lib/remote-ui/model.js +3 -0
- package/dist/lib/remote-ui/model.js.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.d.ts +31 -3
- package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.js +119 -9
- package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
- package/dist/lib/remote-ui/pages/contracts.d.ts +61 -3
- package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/contracts.js +203 -9
- package/dist/lib/remote-ui/pages/contracts.js.map +1 -1
- package/dist/lib/remote-ui/pages/files.d.ts +52 -10
- package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/files.js +122 -12
- package/dist/lib/remote-ui/pages/files.js.map +1 -1
- package/dist/lib/remote-ui/pages/home.js +4 -4
- package/dist/lib/remote-ui/pages/home.js.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.d.ts +87 -2
- package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.js +225 -21
- package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
- package/dist/lib/remote-ui/pages/settings.d.ts +39 -6
- package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/settings.js +94 -5
- package/dist/lib/remote-ui/pages/settings.js.map +1 -1
- package/dist/lib/remote-ui/procurement-contracts.d.ts +6 -2
- package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/procurement-contracts.js +11 -1
- package/dist/lib/remote-ui/procurement-contracts.js.map +1 -1
- package/dist/lib/remote-ui/styles.css +30 -11
- 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
|
|
184
|
-
const
|
|
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.
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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)
|