@chenchaolong/plugin-trade-compliance-workbench 1.0.119 → 1.0.121
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/constants.d.ts +2 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +2 -0
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/import-jobs/import-command.builder.js +1 -1
- package/dist/lib/import-jobs/import-command.builder.js.map +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.js +4 -4
- package/dist/lib/remote-ui/app-source.js +4 -1
- package/dist/lib/remote-ui/app-source.js.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.d.ts +1 -1
- package/dist/lib/remote-ui/pages/procurement.d.ts +2 -1
- package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/procurement.js +22 -5
- package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
- package/dist/lib/workbench.service.d.ts +12 -0
- package/dist/lib/workbench.service.d.ts.map +1 -1
- package/dist/lib/workbench.service.js +65 -16
- package/dist/lib/workbench.service.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ import { join } from 'node:path';
|
|
|
11
11
|
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
|
-
import { CATALOG_REVIEW_BATCH_LIMIT } from './constants.js';
|
|
14
|
+
import { CATALOG_REVIEW_BATCH_LIMIT, CATALOG_REVIEW_LEASE_MS, CATALOG_TRANSLATION_REVIEW_LEASE_MS } from './constants.js';
|
|
15
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';
|
|
@@ -699,6 +699,7 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
699
699
|
task ??= await taskRepo.findOne({ where: { ...activeScope(scope), id: taskId } });
|
|
700
700
|
if (!task)
|
|
701
701
|
throw new DomainError(DomainErrorCodes.NOT_FOUND, '解析任务不存在');
|
|
702
|
+
task = await this.recoverExpiredCatalogReviewLeases(scope, task) ?? task;
|
|
702
703
|
const publicTask = (await this.publicImportTaskDtos([task]))[0];
|
|
703
704
|
if (!task.resultEntityId)
|
|
704
705
|
return { task: publicTask };
|
|
@@ -1379,7 +1380,7 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
1379
1380
|
if (blocks.length === 0)
|
|
1380
1381
|
return null;
|
|
1381
1382
|
const leaseToken = randomUUID();
|
|
1382
|
-
const leaseExpiresAt = new Date(Date.now() +
|
|
1383
|
+
const leaseExpiresAt = new Date(Date.now() + CATALOG_TRANSLATION_REVIEW_LEASE_MS);
|
|
1383
1384
|
for (const block of blocks) {
|
|
1384
1385
|
block.status = 'LEASED';
|
|
1385
1386
|
block.leaseToken = leaseToken;
|
|
@@ -1407,12 +1408,45 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
1407
1408
|
async countCatalogReviewBlocks(scope, taskId) {
|
|
1408
1409
|
return this.dataSource.getRepository(CatalogReviewBlock).count({ where: { ...activeScope(scope), importTaskId: taskId, status: In(['PENDING', 'LEASED']) } });
|
|
1409
1410
|
}
|
|
1411
|
+
async recoverExpiredCatalogReviewLeases(scope, task) {
|
|
1412
|
+
if ((task.documentType !== 'CONTROLLED_CATALOG' && task.documentType !== 'SANCTION_CATALOG') ||
|
|
1413
|
+
task.status !== 'AWAITING_REVIEW' || !task.progress)
|
|
1414
|
+
return null;
|
|
1415
|
+
return this.dataSource.transaction(async (manager) => {
|
|
1416
|
+
const taskRepo = manager.getRepository(ImportTask);
|
|
1417
|
+
const locked = await taskRepo.findOne({
|
|
1418
|
+
where: { ...activeScope(scope), id: task.id, deletedAt: IsNull() },
|
|
1419
|
+
lock: { mode: 'pessimistic_write' }
|
|
1420
|
+
});
|
|
1421
|
+
if (!locked || locked.status !== 'AWAITING_REVIEW')
|
|
1422
|
+
return null;
|
|
1423
|
+
await this.recoverExpiredCatalogReviewLeasesInTransaction(manager, scope, locked);
|
|
1424
|
+
return locked;
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
async recoverExpiredCatalogReviewLeasesInTransaction(manager, scope, task) {
|
|
1428
|
+
if (task.documentType !== 'CONTROLLED_CATALOG' && task.documentType !== 'SANCTION_CATALOG')
|
|
1429
|
+
return task.progress?.reviewPendingCount ?? 0;
|
|
1430
|
+
const repo = manager.getRepository(CatalogReviewBlock);
|
|
1431
|
+
await repo.update({
|
|
1432
|
+
...activeScope(scope), importTaskId: task.id, status: 'LEASED', leaseExpiresAt: LessThanOrEqual(new Date())
|
|
1433
|
+
}, { status: 'PENDING', leaseToken: null, leaseExpiresAt: null });
|
|
1434
|
+
const previousPendingCount = task.progress?.reviewPendingCount;
|
|
1435
|
+
const pendingCount = await this.reconcileCatalogReviewPendingCount(repo, scope, task);
|
|
1436
|
+
if (task.progress && previousPendingCount !== pendingCount) {
|
|
1437
|
+
task.progress.progressVersion += 1;
|
|
1438
|
+
task.progress.lastProgressAt = new Date().toISOString();
|
|
1439
|
+
await manager.getRepository(ImportTask).save(task);
|
|
1440
|
+
}
|
|
1441
|
+
return pendingCount;
|
|
1442
|
+
}
|
|
1410
1443
|
async waitImportProgress(scope, taskId, afterProgressVersion = 0, waitMs = 10_000) {
|
|
1411
1444
|
const deadline = Date.now() + Math.max(0, Math.min(waitMs, 10_000));
|
|
1412
1445
|
while (true) {
|
|
1413
|
-
|
|
1446
|
+
let task = await required(this.dataSource.getRepository(ImportTask).findOne({
|
|
1414
1447
|
where: { ...activeScope(scope), id: taskId }
|
|
1415
1448
|
}), '解析任务不存在');
|
|
1449
|
+
task = await this.recoverExpiredCatalogReviewLeases(scope, task) ?? task;
|
|
1416
1450
|
const version = task.progress?.progressVersion ?? 0;
|
|
1417
1451
|
if (version > afterProgressVersion || TERMINAL_IMPORT_TASK_STATUSES.includes(task.status)) {
|
|
1418
1452
|
return { task: publicImportTaskDto(task) };
|
|
@@ -1433,16 +1467,7 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
1433
1467
|
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '仅目录任务包含复核块');
|
|
1434
1468
|
}
|
|
1435
1469
|
const repo = manager.getRepository(CatalogReviewBlock);
|
|
1436
|
-
await
|
|
1437
|
-
...activeScope(scope), importTaskId: taskId, status: 'LEASED', leaseExpiresAt: LessThanOrEqual(new Date())
|
|
1438
|
-
}, { status: 'PENDING', leaseToken: null, leaseExpiresAt: null });
|
|
1439
|
-
const previousPendingCount = task.progress?.reviewPendingCount;
|
|
1440
|
-
const pendingCount = await this.reconcileCatalogReviewPendingCount(repo, scope, task);
|
|
1441
|
-
if (task.progress && previousPendingCount !== pendingCount) {
|
|
1442
|
-
task.progress.progressVersion += 1;
|
|
1443
|
-
task.progress.lastProgressAt = new Date().toISOString();
|
|
1444
|
-
await manager.getRepository(ImportTask).save(task);
|
|
1445
|
-
}
|
|
1470
|
+
await this.recoverExpiredCatalogReviewLeasesInTransaction(manager, scope, task);
|
|
1446
1471
|
const blocks = await repo.createQueryBuilder('block')
|
|
1447
1472
|
.setLock('pessimistic_write')
|
|
1448
1473
|
.setOnLocked('skip_locked')
|
|
@@ -1453,10 +1478,34 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
1453
1478
|
.addOrderBy('block.id', 'ASC')
|
|
1454
1479
|
.limit(limit)
|
|
1455
1480
|
.getMany();
|
|
1456
|
-
if (blocks.length === 0)
|
|
1457
|
-
|
|
1481
|
+
if (blocks.length === 0) {
|
|
1482
|
+
const leased = await repo.createQueryBuilder('block')
|
|
1483
|
+
.setLock('pessimistic_write')
|
|
1484
|
+
.setOnLocked('skip_locked')
|
|
1485
|
+
.where('block."tenantId" = :tenantId AND block."organizationId" = :organizationId AND block."importTaskId" = :taskId AND block.status = :status', {
|
|
1486
|
+
...activeScope(scope), taskId, status: 'LEASED'
|
|
1487
|
+
})
|
|
1488
|
+
.andWhere('block."leaseExpiresAt" IS NOT NULL')
|
|
1489
|
+
.orderBy('block."leaseExpiresAt"', 'ASC')
|
|
1490
|
+
.addOrderBy('block.ordinal', 'ASC')
|
|
1491
|
+
.addOrderBy('block.id', 'ASC')
|
|
1492
|
+
.limit(1)
|
|
1493
|
+
.getOne();
|
|
1494
|
+
if (!leased)
|
|
1495
|
+
return { leaseToken: null, items: [] };
|
|
1496
|
+
const leasedUntil = leased.leaseExpiresAt instanceof Date ? leased.leaseExpiresAt : null;
|
|
1497
|
+
const leaseExpiresAt = leasedUntil?.toISOString() ?? null;
|
|
1498
|
+
const retryAfterMs = leasedUntil ? Math.max(1000, leasedUntil.getTime() - Date.now()) : 5000;
|
|
1499
|
+
return {
|
|
1500
|
+
leaseToken: null,
|
|
1501
|
+
items: [],
|
|
1502
|
+
leaseExpiresAt,
|
|
1503
|
+
retryAfterMs,
|
|
1504
|
+
instruction: '当前待复核块仍被占用,请等待 retryAfterMs 后再调用 nextReviewBatch。'
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1458
1507
|
const leaseToken = randomUUID();
|
|
1459
|
-
const leaseExpiresAt = new Date(Date.now() +
|
|
1508
|
+
const leaseExpiresAt = new Date(Date.now() + CATALOG_REVIEW_LEASE_MS);
|
|
1460
1509
|
for (const block of blocks) {
|
|
1461
1510
|
block.status = 'LEASED';
|
|
1462
1511
|
block.leaseToken = leaseToken;
|