@chenchaolong/plugin-trade-compliance-workbench 1.0.13 → 1.0.15
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/adapters/hsbianma.provider.d.ts +11 -0
- package/dist/lib/adapters/hsbianma.provider.d.ts.map +1 -1
- package/dist/lib/adapters/hsbianma.provider.js +23 -0
- package/dist/lib/adapters/hsbianma.provider.js.map +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
- package/dist/lib/remote-ui/app-source.js +6 -3
- package/dist/lib/remote-ui/app-source.js.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.js +35 -24
- package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
- package/dist/lib/workbench-view.provider.js +1 -1
- package/dist/lib/workbench-view.provider.js.map +1 -1
- package/dist/lib/workbench.service.d.ts.map +1 -1
- package/dist/lib/workbench.service.js +53 -26
- package/dist/lib/workbench.service.js.map +1 -1
- package/package.json +1 -1
|
@@ -1480,13 +1480,11 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
1480
1480
|
const supplier = await this.upsertSupplier(manager, scope, draft.supplier);
|
|
1481
1481
|
task.status = 'PERSISTING';
|
|
1482
1482
|
await taskRepo.save(task);
|
|
1483
|
-
const entity = await orderRepo.save(orderRepo.create({ ...auditScope(scope), supplierId: supplier.id, contractNo: draft.order.contractNo.trim(), normalizedContractNo, orderDate: draft.order.orderDate
|
|
1483
|
+
const entity = await orderRepo.save(orderRepo.create({ ...auditScope(scope), supplierId: supplier.id, contractNo: draft.order.contractNo.trim(), normalizedContractNo, orderDate: optionalString(draft.order.orderDate), contractAmountCny: defaultDecimalText(draft.order.contractAmountCny), paidAmountCny: defaultDecimalText(draft.order.paidAmountCny), refundedAmountCny: defaultDecimalText(draft.order.refundedAmountCny), sourceFileId: task.sourceFileId, sourceFileName: task.sourceFileName, importTaskId: task.id, amountMismatchConfirmedAt: draft.order.amountMismatch === true ? confirmedAt : null, amountMismatchConfirmedById: draft.order.amountMismatch === true ? confirmedById : null }));
|
|
1484
1484
|
const lineRepo = manager.getRepository(PurchaseOrderLine);
|
|
1485
1485
|
for (let index = 0; index < draft.lines.length; index += 1) {
|
|
1486
1486
|
const line = draft.lines[index];
|
|
1487
|
-
|
|
1488
|
-
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '采购商品数量必须大于 0', { lineNumber: index + 1 });
|
|
1489
|
-
await lineRepo.save(lineRepo.create({ ...auditScope(scope), purchaseOrderId: entity.id, lineNumber: line.lineNumber ?? index + 1, sourceType: line.sourceType ?? 'CONTRACT_EXTRACTED', productName: line.productName.trim(), normalizedProductName: normalizeForProductMatch(line.productName), englishName: line.englishName ?? null, englishNameSource: line.englishNameSource ?? null, productDescription: line.productDescription ?? null, modelSpecification: line.modelSpecification ?? null, unit: line.unit ?? null, quantity: line.quantity, unitPriceTaxIncluded: line.unitPriceTaxIncluded, lineAmountTaxIncluded: line.lineAmountTaxIncluded, vatRate: line.vatRate ?? null, vatRateSource: line.vatRateSource ?? null, hsCode: line.hsCode ?? null, hsCodeStatus: line.hsCodeStatus ?? 'UNCONFIRMED', hsCodeSource: line.hsCodeSource ?? null, hsCodeReferenceSnapshotId: line.hsCodeReferenceSnapshotId ?? null, originCountry: line.originCountry ?? null, domesticSourceLocation: line.domesticSourceLocation ?? null, refundRate: line.refundRate ?? null, refundRateSource: line.refundRateSource ?? null, refundPolicyReference: line.refundPolicyReference ?? null, refundPolicyDate: line.refundPolicyDate ?? null, refundValidationMethod: line.refundValidationMethod, refundConfirmedAt: confirmedAt, refundConfirmedById: confirmedById, amountMismatchConfirmedAt: line.amountMismatch === true ? confirmedAt : null, amountMismatchConfirmedById: line.amountMismatch === true ? confirmedById : null, sourceLocation: line.sourceLocation ?? null, sourceContent: line.sourceContent ?? null }));
|
|
1487
|
+
await lineRepo.save(lineRepo.create({ ...auditScope(scope), purchaseOrderId: entity.id, lineNumber: line.lineNumber ?? index + 1, sourceType: line.sourceType ?? 'CONTRACT_EXTRACTED', productName: line.productName.trim(), normalizedProductName: normalizeForProductMatch(line.productName), englishName: line.englishName ?? null, englishNameSource: line.englishNameSource ?? null, productDescription: line.productDescription ?? null, modelSpecification: line.modelSpecification ?? null, unit: line.unit ?? null, quantity: defaultDecimalText(line.quantity), unitPriceTaxIncluded: defaultDecimalText(line.unitPriceTaxIncluded), lineAmountTaxIncluded: defaultDecimalText(line.lineAmountTaxIncluded), vatRate: optionalString(line.vatRate), vatRateSource: line.vatRateSource ?? null, hsCode: line.hsCode ?? null, hsCodeStatus: line.hsCodeStatus ?? 'UNCONFIRMED', hsCodeSource: line.hsCodeSource ?? null, hsCodeReferenceSnapshotId: line.hsCodeReferenceSnapshotId ?? null, originCountry: line.originCountry ?? null, domesticSourceLocation: line.domesticSourceLocation ?? null, refundRate: optionalString(line.refundRate), refundRateSource: line.refundRateSource ?? null, refundPolicyReference: line.refundPolicyReference ?? null, refundPolicyDate: optionalString(line.refundPolicyDate), refundValidationMethod: line.refundValidationMethod ?? null, refundConfirmedAt: confirmedAt, refundConfirmedById: confirmedById, amountMismatchConfirmedAt: line.amountMismatch === true ? confirmedAt : null, amountMismatchConfirmedById: line.amountMismatch === true ? confirmedById : null, sourceLocation: line.sourceLocation ?? null, sourceContent: line.sourceContent ?? null }));
|
|
1490
1488
|
}
|
|
1491
1489
|
task.status = 'SUCCEEDED';
|
|
1492
1490
|
task.resultEntityId = entity.id;
|
|
@@ -3425,14 +3423,20 @@ async function required(promise, message) { const value = await promise; if (!va
|
|
|
3425
3423
|
function pick(input, keys) { return Object.fromEntries(keys.filter(key => key in input).map(key => [key, input[key]])); }
|
|
3426
3424
|
function maxRisk(levels) { return levels.includes('HIGH') ? 'HIGH' : levels.includes('MEDIUM') ? 'MEDIUM' : levels.includes('LOW') ? 'LOW' : null; }
|
|
3427
3425
|
function assertPurchaseDraft(draft) {
|
|
3428
|
-
if (!
|
|
3429
|
-
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3426
|
+
if (!hasQueryValue(draft.supplier?.name))
|
|
3427
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '供应商名称为必填项');
|
|
3428
|
+
if (!hasQueryValue(draft.order?.contractNo))
|
|
3429
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '采购合同编号为必填项');
|
|
3430
|
+
if (!draft.lines.length)
|
|
3431
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '采购合同必须包含至少一条商品');
|
|
3432
|
+
const contractAmount = optionalDecimalInput(draft.order.contractAmountCny, '采购订单总额必须为有效金额') ?? new Decimal(0);
|
|
3433
|
+
const paidAmount = optionalDecimalInput(draft.order.paidAmountCny, '已付款必须为有效金额') ?? new Decimal(0);
|
|
3434
|
+
const refundedAmount = optionalDecimalInput(draft.order.refundedAmountCny, '已退税必须为有效金额') ?? new Decimal(0);
|
|
3435
|
+
if (contractAmount.isNegative())
|
|
3436
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '采购订单总额必须为有效金额');
|
|
3433
3437
|
if (paidAmount.isNegative())
|
|
3434
3438
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '已付款不能为负数');
|
|
3435
|
-
if (paidAmount.gt(contractAmount))
|
|
3439
|
+
if (hasQueryValue(draft.order.paidAmountCny) && hasQueryValue(draft.order.contractAmountCny) && paidAmount.gt(contractAmount))
|
|
3436
3440
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '已付款不能超过采购订单总额');
|
|
3437
3441
|
if (refundedAmount.isNegative())
|
|
3438
3442
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '已退税不能为负数');
|
|
@@ -3442,28 +3446,32 @@ function assertPurchaseDraft(draft) {
|
|
|
3442
3446
|
for (let index = 0; index < draft.lines.length; index += 1) {
|
|
3443
3447
|
const line = draft.lines[index];
|
|
3444
3448
|
const prefix = `第${index + 1}条商品`;
|
|
3445
|
-
if (!hasQueryValue(line.
|
|
3446
|
-
throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${prefix}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3449
|
+
if (!hasQueryValue(line.productName))
|
|
3450
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${prefix}名称为必填项`);
|
|
3451
|
+
const quantity = optionalDecimalInput(line.quantity, `${prefix}数量必须为有效数值`);
|
|
3452
|
+
const unitPrice = optionalDecimalInput(line.unitPriceTaxIncluded, `${prefix}含税单价必须为有效金额`);
|
|
3453
|
+
const lineAmount = optionalDecimalInput(line.lineAmountTaxIncluded, `${prefix}含税金额必须为有效金额`);
|
|
3454
|
+
validateOptionalTaxRate(line.vatRate, 'vatRate', '增值税率', { lineNumber: index + 1, productName: line.productName });
|
|
3455
|
+
const refundRate = validateOptionalTaxRate(line.refundRate, 'refundRate', '退税率', { lineNumber: index + 1, productName: line.productName });
|
|
3456
|
+
if (hasQueryValue(line.refundPolicyDate) && hasQueryValue(draft.order.orderDate) && String(line.refundPolicyDate) > String(draft.order.orderDate))
|
|
3452
3457
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${prefix}退税政策日期不得晚于下单日期`);
|
|
3453
|
-
if (
|
|
3454
|
-
throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${prefix}退税校验方式为必填项`);
|
|
3455
|
-
if (!RefundValidationMethods.some(method => method === line.refundValidationMethod))
|
|
3458
|
+
if (hasQueryValue(line.refundValidationMethod) && !RefundValidationMethods.some(method => method === line.refundValidationMethod))
|
|
3456
3459
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${prefix}退税校验方式无效`);
|
|
3457
3460
|
validatePurchaseLineDiscriminators({ ...line, sourceType: line.sourceType ?? 'CONTRACT_EXTRACTED', hsCodeStatus: line.hsCodeStatus ?? 'UNCONFIRMED' });
|
|
3458
3461
|
if (line.amountMismatch === true && line.amountMismatchConfirmed !== true)
|
|
3459
3462
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${prefix}金额差异必须确认`);
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3463
|
+
if (quantity && quantity.lte(0))
|
|
3464
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${prefix}数量必须大于0`);
|
|
3465
|
+
if (unitPrice && unitPrice.isNegative())
|
|
3466
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${prefix}含税单价不能为负数`);
|
|
3467
|
+
if (lineAmount && lineAmount.isNegative())
|
|
3468
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${prefix}含税金额不能为负数`);
|
|
3469
|
+
if (lineAmount && refundRate) {
|
|
3470
|
+
const vatRate = validateOptionalTaxRate(line.vatRate, 'vatRate', '增值税率', { lineNumber: index + 1, productName: line.productName });
|
|
3471
|
+
estimatedRefund = estimatedRefund.plus(lineAmount.div((vatRate ?? new Decimal(0)).add(1)).mul(refundRate));
|
|
3472
|
+
}
|
|
3465
3473
|
}
|
|
3466
|
-
if (refundedAmount.gt(estimatedRefund))
|
|
3474
|
+
if (hasQueryValue(draft.order.refundedAmountCny) && refundedAmount.gt(estimatedRefund))
|
|
3467
3475
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '已退税不能超过预计退税总额');
|
|
3468
3476
|
}
|
|
3469
3477
|
function decimalInput(value, message) {
|
|
@@ -3474,6 +3482,25 @@ function decimalInput(value, message) {
|
|
|
3474
3482
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, message);
|
|
3475
3483
|
}
|
|
3476
3484
|
}
|
|
3485
|
+
function optionalDecimalInput(value, message) {
|
|
3486
|
+
if (!hasQueryValue(value))
|
|
3487
|
+
return null;
|
|
3488
|
+
const parsed = decimalInput(value, message);
|
|
3489
|
+
if (!parsed.isFinite())
|
|
3490
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, message);
|
|
3491
|
+
return parsed;
|
|
3492
|
+
}
|
|
3493
|
+
function defaultDecimalText(value) {
|
|
3494
|
+
return optionalDecimalInput(value, '数值必须是有限金额')?.toString() ?? '0';
|
|
3495
|
+
}
|
|
3496
|
+
function validateOptionalTaxRate(value, field, label, context) {
|
|
3497
|
+
if (!hasQueryValue(value))
|
|
3498
|
+
return null;
|
|
3499
|
+
const parsed = decimalInput(value, `${label}必须是 0 至 1 之间的有限数值`);
|
|
3500
|
+
if (!parsed.isFinite() || parsed.lt(0) || parsed.gt(1))
|
|
3501
|
+
throw new DomainError(DomainErrorCodes.INVALID_TAX_RATE, `${label}必须是 0 至 1 之间的有限数值`, { field, value, ...context });
|
|
3502
|
+
return parsed;
|
|
3503
|
+
}
|
|
3477
3504
|
function completeCustomerClauseAnalyses(draft) {
|
|
3478
3505
|
return ClauseTypes.map(clauseType => {
|
|
3479
3506
|
const clause = draft.clauses.find(item => item.clauseType === clauseType);
|