@chenchaolong/plugin-trade-compliance-workbench 1.0.118 → 1.0.120

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 (54) hide show
  1. package/dist/lib/adapters/pdf-page-renderer.d.ts.map +1 -1
  2. package/dist/lib/adapters/pdf-page-renderer.js +33 -90
  3. package/dist/lib/adapters/pdf-page-renderer.js.map +1 -1
  4. package/dist/lib/constants.d.ts +2 -0
  5. package/dist/lib/constants.d.ts.map +1 -1
  6. package/dist/lib/constants.js +2 -0
  7. package/dist/lib/constants.js.map +1 -1
  8. package/dist/lib/import-jobs/import-command.builder.js +1 -1
  9. package/dist/lib/import-jobs/import-command.builder.js.map +1 -1
  10. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  11. package/dist/lib/remote-components/trade-compliance-workbench/app.js +8 -8
  12. package/dist/lib/remote-components/trade-compliance-workbench/preview-source.mjs +25 -0
  13. package/dist/lib/remote-components/trade-compliance-workbench/preview.js +341 -0
  14. package/dist/lib/remote-ui/app-source.d.ts.map +1 -1
  15. package/dist/lib/remote-ui/app-source.js +38 -31
  16. package/dist/lib/remote-ui/app-source.js.map +1 -1
  17. package/dist/lib/remote-ui/components/source-file-preview.d.ts +37 -0
  18. package/dist/lib/remote-ui/components/source-file-preview.d.ts.map +1 -0
  19. package/dist/lib/remote-ui/components/source-file-preview.js +115 -0
  20. package/dist/lib/remote-ui/components/source-file-preview.js.map +1 -0
  21. package/dist/lib/remote-ui/contract-import.d.ts +1 -0
  22. package/dist/lib/remote-ui/contract-import.d.ts.map +1 -1
  23. package/dist/lib/remote-ui/contract-import.js +2 -0
  24. package/dist/lib/remote-ui/contract-import.js.map +1 -1
  25. package/dist/lib/remote-ui/contracts.d.ts +1 -0
  26. package/dist/lib/remote-ui/contracts.d.ts.map +1 -1
  27. package/dist/lib/remote-ui/customer-contract-contracts.d.ts +7 -0
  28. package/dist/lib/remote-ui/customer-contract-contracts.d.ts.map +1 -1
  29. package/dist/lib/remote-ui/pages/catalogs.d.ts +1 -1
  30. package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
  31. package/dist/lib/remote-ui/pages/catalogs.js +2 -0
  32. package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
  33. package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -1
  34. package/dist/lib/remote-ui/pages/contracts.js +2 -1
  35. package/dist/lib/remote-ui/pages/contracts.js.map +1 -1
  36. package/dist/lib/remote-ui/pages/procurement.d.ts +2 -1
  37. package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -1
  38. package/dist/lib/remote-ui/pages/procurement.js +32 -9
  39. package/dist/lib/remote-ui/pages/procurement.js.map +1 -1
  40. package/dist/lib/remote-ui/procurement-contracts.d.ts +10 -7
  41. package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -1
  42. package/dist/lib/remote-ui/styles.css +21 -0
  43. package/dist/lib/workbench-view.provider.d.ts +1 -29
  44. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  45. package/dist/lib/workbench-view.provider.js +8 -27
  46. package/dist/lib/workbench-view.provider.js.map +1 -1
  47. package/dist/lib/workbench.middleware.d.ts.map +1 -1
  48. package/dist/lib/workbench.middleware.js +38 -22
  49. package/dist/lib/workbench.middleware.js.map +1 -1
  50. package/dist/lib/workbench.service.d.ts +12 -0
  51. package/dist/lib/workbench.service.d.ts.map +1 -1
  52. package/dist/lib/workbench.service.js +70 -17
  53. package/dist/lib/workbench.service.js.map +1 -1
  54. package/package.json +2 -2
@@ -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';
@@ -694,7 +694,12 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
694
694
  }
695
695
  async getImportTaskResult(scope, taskId) {
696
696
  await this.expireStaleImportTasks(scope);
697
- const task = await required(this.dataSource.getRepository(ImportTask).findOne({ where: { ...activeScope(scope), id: taskId, deletedAt: IsNull() } }), '解析任务不存在');
697
+ const taskRepo = this.dataSource.getRepository(ImportTask);
698
+ let task = await taskRepo.findOne({ where: { ...activeScope(scope), id: taskId, deletedAt: IsNull() } });
699
+ task ??= await taskRepo.findOne({ where: { ...activeScope(scope), id: taskId } });
700
+ if (!task)
701
+ throw new DomainError(DomainErrorCodes.NOT_FOUND, '解析任务不存在');
702
+ task = await this.recoverExpiredCatalogReviewLeases(scope, task) ?? task;
698
703
  const publicTask = (await this.publicImportTaskDtos([task]))[0];
699
704
  if (!task.resultEntityId)
700
705
  return { task: publicTask };
@@ -1375,7 +1380,7 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
1375
1380
  if (blocks.length === 0)
1376
1381
  return null;
1377
1382
  const leaseToken = randomUUID();
1378
- const leaseExpiresAt = new Date(Date.now() + 10 * 60_000);
1383
+ const leaseExpiresAt = new Date(Date.now() + CATALOG_TRANSLATION_REVIEW_LEASE_MS);
1379
1384
  for (const block of blocks) {
1380
1385
  block.status = 'LEASED';
1381
1386
  block.leaseToken = leaseToken;
@@ -1403,12 +1408,45 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
1403
1408
  async countCatalogReviewBlocks(scope, taskId) {
1404
1409
  return this.dataSource.getRepository(CatalogReviewBlock).count({ where: { ...activeScope(scope), importTaskId: taskId, status: In(['PENDING', 'LEASED']) } });
1405
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
+ }
1406
1443
  async waitImportProgress(scope, taskId, afterProgressVersion = 0, waitMs = 10_000) {
1407
1444
  const deadline = Date.now() + Math.max(0, Math.min(waitMs, 10_000));
1408
1445
  while (true) {
1409
- const task = await required(this.dataSource.getRepository(ImportTask).findOne({
1446
+ let task = await required(this.dataSource.getRepository(ImportTask).findOne({
1410
1447
  where: { ...activeScope(scope), id: taskId }
1411
1448
  }), '解析任务不存在');
1449
+ task = await this.recoverExpiredCatalogReviewLeases(scope, task) ?? task;
1412
1450
  const version = task.progress?.progressVersion ?? 0;
1413
1451
  if (version > afterProgressVersion || TERMINAL_IMPORT_TASK_STATUSES.includes(task.status)) {
1414
1452
  return { task: publicImportTaskDto(task) };
@@ -1429,16 +1467,7 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
1429
1467
  throw new DomainError(DomainErrorCodes.INVALID_INPUT, '仅目录任务包含复核块');
1430
1468
  }
1431
1469
  const repo = manager.getRepository(CatalogReviewBlock);
1432
- await repo.update({
1433
- ...activeScope(scope), importTaskId: taskId, status: 'LEASED', leaseExpiresAt: LessThanOrEqual(new Date())
1434
- }, { status: 'PENDING', leaseToken: null, leaseExpiresAt: null });
1435
- const previousPendingCount = task.progress?.reviewPendingCount;
1436
- const pendingCount = await this.reconcileCatalogReviewPendingCount(repo, scope, task);
1437
- if (task.progress && previousPendingCount !== pendingCount) {
1438
- task.progress.progressVersion += 1;
1439
- task.progress.lastProgressAt = new Date().toISOString();
1440
- await manager.getRepository(ImportTask).save(task);
1441
- }
1470
+ await this.recoverExpiredCatalogReviewLeasesInTransaction(manager, scope, task);
1442
1471
  const blocks = await repo.createQueryBuilder('block')
1443
1472
  .setLock('pessimistic_write')
1444
1473
  .setOnLocked('skip_locked')
@@ -1449,10 +1478,34 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
1449
1478
  .addOrderBy('block.id', 'ASC')
1450
1479
  .limit(limit)
1451
1480
  .getMany();
1452
- if (blocks.length === 0)
1453
- return { leaseToken: null, items: [] };
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
+ }
1454
1507
  const leaseToken = randomUUID();
1455
- const leaseExpiresAt = new Date(Date.now() + 5 * 60_000);
1508
+ const leaseExpiresAt = new Date(Date.now() + CATALOG_REVIEW_LEASE_MS);
1456
1509
  for (const block of blocks) {
1457
1510
  block.status = 'LEASED';
1458
1511
  block.leaseToken = leaseToken;