@chenchaolong/plugin-trade-compliance-workbench 1.0.76 → 1.0.78
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.
|
@@ -12,7 +12,7 @@ import { finished } from 'node:stream/promises';
|
|
|
12
12
|
import { createHash, randomUUID } from 'node:crypto';
|
|
13
13
|
import { BankAccountSettings, CatalogReviewBlock, CompanySanctionMatch, CompanySettings, ControlledCatalogBatch, ControlledCatalogImportIssue, ControlledGoodsRecord, Customer, CustomerContract, CustomerContractAnalysisRun, CustomerContractClause, CustomerContractClauseAnalysis, DerivedSalesOrderManualData, GeneratedSalesFile, ImportTask, ManagedAsset, MonthlySafeExchangeRate, ProductComplianceMatch, ProfitRuleSettings, PurchaseOrder, PurchaseOrderLine, SanctionCatalogBatch, SanctionCatalogImportIssue, SanctionedCompanyAlias, SanctionedCompanyRecord, SettingsHistory, Supplier } from './entities/index.js';
|
|
14
14
|
import { CATALOG_REVIEW_BATCH_LIMIT } from './constants.js';
|
|
15
|
-
import { CatalogTypes, ClauseTypes, CustomerProfitRateStatuses, DomainError, DomainErrorCodes, RefundValidationMethods, SalesPaymentStatuses, SanctionHitStatuses, StalledCatalogImportDecisions } from './types.js';
|
|
15
|
+
import { CatalogTypes, ClauseTypes, CustomerProfitRateStatuses, DomainError, DomainErrorCodes, RefundValidationMethods, RiskLevels, SalesPaymentStatuses, SanctionHitStatuses, StalledCatalogImportDecisions } from './types.js';
|
|
16
16
|
import { deriveSalesLine, assertReceivedAmount } from './domain/finance.js';
|
|
17
17
|
import { parseCustomerContractNo, parsePurchaseContractNo } from './domain/contract-number.js';
|
|
18
18
|
import { isProductNameMatch, normalizeExactName, normalizeForProductMatch, normalizeHsCode, normalizeSanctionSource } from './domain/normalization.js';
|
|
@@ -2483,7 +2483,7 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
2483
2483
|
return saved;
|
|
2484
2484
|
}
|
|
2485
2485
|
async saveCustomerContractDraft(scope, importTaskId, draft) {
|
|
2486
|
-
const normalizedDraft = { ...draft, clauses: draft.clauses.map(normalizeCustomerContractDraftClause) };
|
|
2486
|
+
const normalizedDraft = { ...draft, clauses: draft.clauses.map(clause => normalizeCustomerContractDraftClause(clause)) };
|
|
2487
2487
|
const effectiveAnalyses = completeCustomerClauseAnalyses(normalizedDraft);
|
|
2488
2488
|
const contract = await this.dataSource.transaction(async (manager) => {
|
|
2489
2489
|
const taskRepo = manager.getRepository(ImportTask);
|
|
@@ -4206,10 +4206,11 @@ function normalizeCustomerContractAnalysisInput(value, enforceInvariant = true)
|
|
|
4206
4206
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '未找到条款不能填写人工审核内容');
|
|
4207
4207
|
return { reviewedText, extractionStatus: value.extractionStatus };
|
|
4208
4208
|
}
|
|
4209
|
-
function normalizeCustomerContractDraftClause(value) {
|
|
4209
|
+
function normalizeCustomerContractDraftClause(value, options = {}) {
|
|
4210
4210
|
const sourceText = firstStringValue(value, ['sourceText', 'originalText', 'sourceContent']);
|
|
4211
4211
|
const reviewedText = firstStringValue(value, ['reviewedText', 'translatedText', 'clauseSummary', 'summary', 'originalText']);
|
|
4212
|
-
const
|
|
4212
|
+
const hasReviewableText = Boolean(sourceText || reviewedText || firstStringValue(value, ['sourceContent']));
|
|
4213
|
+
const extractionStatus = options.inferFoundFromText && hasReviewableText ? 'FOUND' : value.extractionStatus ?? (hasReviewableText ? 'FOUND' : 'NOT_FOUND');
|
|
4213
4214
|
const normalized = normalizeCustomerContractAnalysisInput({ extractionStatus, reviewedText });
|
|
4214
4215
|
return {
|
|
4215
4216
|
...value,
|
|
@@ -4220,16 +4221,49 @@ function normalizeCustomerContractDraftClause(value) {
|
|
|
4220
4221
|
};
|
|
4221
4222
|
}
|
|
4222
4223
|
function normalizeCustomerContractDraftPayload(payload) {
|
|
4223
|
-
|
|
4224
|
-
return payload;
|
|
4225
|
-
return {
|
|
4224
|
+
const normalized = {
|
|
4226
4225
|
...payload,
|
|
4227
|
-
clauses: payload.clauses.map(clause => {
|
|
4226
|
+
clauses: Array.isArray(payload.clauses) ? payload.clauses.map(clause => {
|
|
4228
4227
|
if (!clause || typeof clause !== 'object' || Array.isArray(clause))
|
|
4229
4228
|
return clause;
|
|
4230
|
-
return normalizeCustomerContractDraftClause(clause);
|
|
4231
|
-
})
|
|
4229
|
+
return normalizeCustomerContractDraftClause(clause, { inferFoundFromText: true });
|
|
4230
|
+
}) : payload.clauses
|
|
4232
4231
|
};
|
|
4232
|
+
assertCustomerContractImportDraftPayload(normalized);
|
|
4233
|
+
return normalized;
|
|
4234
|
+
}
|
|
4235
|
+
function assertCustomerContractImportDraftPayload(payload) {
|
|
4236
|
+
const customer = payload.customer && typeof payload.customer === 'object' && !Array.isArray(payload.customer) ? payload.customer : {};
|
|
4237
|
+
const contract = payload.contract && typeof payload.contract === 'object' && !Array.isArray(payload.contract) ? payload.contract : {};
|
|
4238
|
+
const customerName = firstStringValue(customer, ['name', 'customerName']);
|
|
4239
|
+
if (!customerName)
|
|
4240
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '客户名称为必填项');
|
|
4241
|
+
const contractNo = firstStringValue(contract, ['contractNo']);
|
|
4242
|
+
if (!contractNo)
|
|
4243
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '客户合同编码为必填项');
|
|
4244
|
+
const clauses = Array.isArray(payload.clauses) ? payload.clauses : [];
|
|
4245
|
+
if (!clauses.length)
|
|
4246
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '客户合同解析结果缺少可审核条款');
|
|
4247
|
+
const analyses = Array.isArray(payload.analyses) ? payload.analyses : [];
|
|
4248
|
+
let foundCount = 0;
|
|
4249
|
+
for (const clause of clauses) {
|
|
4250
|
+
if (!clause || typeof clause !== 'object' || Array.isArray(clause))
|
|
4251
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '客户合同条款无效');
|
|
4252
|
+
const item = clause;
|
|
4253
|
+
if (!ClauseTypes.includes(item.clauseType))
|
|
4254
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '客户合同条款类型无效');
|
|
4255
|
+
if (item.extractionStatus !== 'FOUND' && item.extractionStatus !== 'NOT_FOUND')
|
|
4256
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '客户合同条款提取状态无效');
|
|
4257
|
+
if (item.extractionStatus !== 'FOUND')
|
|
4258
|
+
continue;
|
|
4259
|
+
foundCount += 1;
|
|
4260
|
+
const analysis = analyses.find(value => value && typeof value === 'object' && !Array.isArray(value) && value.clauseType === item.clauseType);
|
|
4261
|
+
if (!analysis || !RiskLevels.includes(analysis.riskLevel) || !firstStringValue(analysis, ['riskMessageZh']) || !firstStringValue(analysis, ['suggestionZh'])) {
|
|
4262
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '已找到的关键条款缺少分析结果', { clauseType: item.clauseType });
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
if (foundCount === 0)
|
|
4266
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '客户合同解析结果缺少可审核条款');
|
|
4233
4267
|
}
|
|
4234
4268
|
function customerContractAnalysisInputChanged(currentByType, updates) {
|
|
4235
4269
|
const seen = new Set();
|