@chenchaolong/plugin-trade-compliance-workbench 1.0.23 → 1.0.24

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 (59) hide show
  1. package/dist/lib/entities/shared.entity.d.ts +6 -1
  2. package/dist/lib/entities/shared.entity.d.ts.map +1 -1
  3. package/dist/lib/entities/shared.entity.js +20 -0
  4. package/dist/lib/entities/shared.entity.js.map +1 -1
  5. package/dist/lib/import-jobs/import-job-callback.processor.d.ts.map +1 -1
  6. package/dist/lib/import-jobs/import-job-callback.processor.js +4 -1
  7. package/dist/lib/import-jobs/import-job-callback.processor.js.map +1 -1
  8. package/dist/lib/import-jobs/import-job.coordinator.d.ts +2 -1
  9. package/dist/lib/import-jobs/import-job.coordinator.d.ts.map +1 -1
  10. package/dist/lib/import-jobs/import-job.coordinator.js +12 -0
  11. package/dist/lib/import-jobs/import-job.coordinator.js.map +1 -1
  12. package/dist/lib/import-jobs/import-job.processor.d.ts.map +1 -1
  13. package/dist/lib/import-jobs/import-job.processor.js +3 -0
  14. package/dist/lib/import-jobs/import-job.processor.js.map +1 -1
  15. package/dist/lib/import-jobs/import-timeout.processor.d.ts.map +1 -1
  16. package/dist/lib/import-jobs/import-timeout.processor.js +3 -1
  17. package/dist/lib/import-jobs/import-timeout.processor.js.map +1 -1
  18. package/dist/lib/public-dto.d.ts +1 -1
  19. package/dist/lib/public-dto.d.ts.map +1 -1
  20. package/dist/lib/public-dto.js +2 -0
  21. package/dist/lib/public-dto.js.map +1 -1
  22. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  23. package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
  24. package/dist/lib/remote-ui/action-execution.d.ts.map +1 -1
  25. package/dist/lib/remote-ui/action-execution.js +1 -0
  26. package/dist/lib/remote-ui/action-execution.js.map +1 -1
  27. package/dist/lib/remote-ui/app-source.js +67 -16
  28. package/dist/lib/remote-ui/app-source.js.map +1 -1
  29. package/dist/lib/remote-ui/components/task-status.d.ts +3 -0
  30. package/dist/lib/remote-ui/components/task-status.d.ts.map +1 -1
  31. package/dist/lib/remote-ui/components/task-status.js +59 -4
  32. package/dist/lib/remote-ui/components/task-status.js.map +1 -1
  33. package/dist/lib/remote-ui/contract-import.d.ts +3 -1
  34. package/dist/lib/remote-ui/contract-import.d.ts.map +1 -1
  35. package/dist/lib/remote-ui/contract-import.js +2 -0
  36. package/dist/lib/remote-ui/contract-import.js.map +1 -1
  37. package/dist/lib/remote-ui/contracts.d.ts +3 -1
  38. package/dist/lib/remote-ui/contracts.d.ts.map +1 -1
  39. package/dist/lib/remote-ui/import-task-status.d.ts +35 -0
  40. package/dist/lib/remote-ui/import-task-status.d.ts.map +1 -1
  41. package/dist/lib/remote-ui/import-task-status.js +105 -0
  42. package/dist/lib/remote-ui/import-task-status.js.map +1 -1
  43. package/dist/lib/remote-ui/styles.css +16 -5
  44. package/dist/lib/types.d.ts +1 -1
  45. package/dist/lib/types.d.ts.map +1 -1
  46. package/dist/lib/types.js +1 -1
  47. package/dist/lib/types.js.map +1 -1
  48. package/dist/lib/workbench-action-contract.d.ts +1 -1
  49. package/dist/lib/workbench-action-contract.d.ts.map +1 -1
  50. package/dist/lib/workbench-action-contract.js +1 -0
  51. package/dist/lib/workbench-action-contract.js.map +1 -1
  52. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  53. package/dist/lib/workbench-view.provider.js +19 -2
  54. package/dist/lib/workbench-view.provider.js.map +1 -1
  55. package/dist/lib/workbench.service.d.ts +19 -1
  56. package/dist/lib/workbench.service.d.ts.map +1 -1
  57. package/dist/lib/workbench.service.js +105 -1
  58. package/dist/lib/workbench.service.js.map +1 -1
  59. package/package.json +1 -1
@@ -44,7 +44,11 @@ const SKIPPED_REASON_MESSAGES = {
44
44
  const RUNNING_IMPORT_TASK_STATUSES = ['EXTRACTING', 'AI_PARSING', 'VALIDATING', 'PERSISTING'];
45
45
  const RECOVERABLE_IMPORT_TASK_STATUSES = ['QUEUED', 'EXTRACTING', 'AI_PARSING', 'VALIDATING', 'PERSISTING'];
46
46
  const RECOVERABLE_REANALYSIS_STATUSES = ['PENDING', 'RUNNING'];
47
- const TERMINAL_IMPORT_TASK_STATUSES = ['AWAITING_REVIEW', 'SUCCEEDED', 'FAILED', 'TIMED_OUT'];
47
+ const TERMINAL_IMPORT_TASK_STATUSES = ['AWAITING_REVIEW', 'SUCCEEDED', 'FAILED', 'TIMED_OUT', 'CANCELED'];
48
+ const DELETABLE_IMPORT_TASK_STATUSES = [
49
+ 'QUEUED', 'EXTRACTING', 'AI_PARSING', 'VALIDATING',
50
+ 'AWAITING_REVIEW', 'PERSISTING', 'FAILED', 'TIMED_OUT'
51
+ ];
48
52
  const CATALOG_RISK_RECOMMENDATIONS = {
49
53
  DUAL_USE: '建议核验军民两用物项许可要求,并在出口前完成合规审批',
50
54
  OVERSEAS_SANCTION: '建议核验海外制裁限制及交易对手风险,并在交易前完成合规审批'
@@ -141,6 +145,89 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
141
145
  const page = await this.pagedQuery(ImportTask, scope, query, ['sourceFileName', 'documentType', 'status', 'errorMessage'], { documentType: query.parameters?.documentType, status: query.parameters?.status });
142
146
  return { ...page, items: page.items.map(publicImportTaskDto) };
143
147
  }
148
+ async deleteImportTask(scope, taskId) {
149
+ const transactionResult = await this.dataSource.transaction(async (manager) => {
150
+ const taskRepo = manager.getRepository(ImportTask);
151
+ const task = await required(taskRepo.findOne({
152
+ where: { ...activeScope(scope), id: requiredId(taskId, '解析任务标识') },
153
+ lock: { mode: 'pessimistic_write' }
154
+ }), '解析任务不存在');
155
+ if (task.deletedAt || task.status === 'CANCELED') {
156
+ return {
157
+ taskId: task.id, status: 'CANCELED', deleted: true,
158
+ idempotent: true, sourceFileCleanupRequired: false,
159
+ cancellationTarget: null
160
+ };
161
+ }
162
+ if (!DELETABLE_IMPORT_TASK_STATUSES.includes(task.status)) {
163
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '已完成任务不允许删除');
164
+ }
165
+ const cancellationTarget = {
166
+ importTaskId: task.id,
167
+ claimAttemptToken: task.claimAttemptToken
168
+ };
169
+ const now = new Date();
170
+ task.status = 'CANCELED';
171
+ task.canceledAt = now;
172
+ task.canceledById = scope.userId ?? null;
173
+ task.deletedAt = now;
174
+ task.deletedById = scope.userId ?? null;
175
+ task.deleteOperation = 'USER_DELETE';
176
+ task.claimAttemptToken = null;
177
+ task.resultProcessingClaimId = null;
178
+ task.resultProcessingClaimExpiresAt = null;
179
+ task.completedAt = now;
180
+ await this.deleteImportTaskTransientRows(manager, scope, task);
181
+ await taskRepo.save(task);
182
+ const sourceFileCleanupRequired = !await this.importSourceFileStillReferenced(manager, scope, task);
183
+ return {
184
+ taskId: task.id, status: 'CANCELED', deleted: true,
185
+ idempotent: false, sourceFileCleanupRequired, cancellationTarget,
186
+ sourceFileId: task.sourceFileId
187
+ };
188
+ });
189
+ let sourceFileRemoved = false;
190
+ const sourceFileId = 'sourceFileId' in transactionResult ? transactionResult.sourceFileId : undefined;
191
+ if (transactionResult.sourceFileCleanupRequired && typeof sourceFileId === 'string' && this.storage) {
192
+ try {
193
+ await this.storage.remove(sourceFileId);
194
+ sourceFileRemoved = true;
195
+ }
196
+ catch {
197
+ sourceFileRemoved = false;
198
+ }
199
+ }
200
+ return { ...transactionResult, sourceFileRemoved };
201
+ }
202
+ async deleteImportTaskTransientRows(manager, scope, task) {
203
+ await manager.getRepository(CatalogReviewBlock).delete({ ...activeScope(scope), importTaskId: task.id });
204
+ await this.deleteEmptyImportCatalogBatch(manager, scope, task.id, ControlledCatalogBatch, ControlledGoodsRecord, ControlledCatalogImportIssue);
205
+ await this.deleteEmptyImportCatalogBatch(manager, scope, task.id, SanctionCatalogBatch, SanctionedCompanyRecord, SanctionCatalogImportIssue);
206
+ }
207
+ async deleteEmptyImportCatalogBatch(manager, scope, importTaskId, batchEntity, recordEntity, issueEntity) {
208
+ const batchRepo = manager.getRepository(batchEntity);
209
+ const batch = await batchRepo.findOne({ where: { ...activeScope(scope), importTaskId } });
210
+ if (!batch)
211
+ return;
212
+ const committedRows = await manager.getRepository(recordEntity).count({
213
+ where: { ...activeScope(scope), batchId: batch.id }
214
+ });
215
+ if (committedRows > 0)
216
+ return;
217
+ await manager.getRepository(issueEntity).delete({ ...activeScope(scope), batchId: batch.id });
218
+ await batchRepo.delete({ ...activeScope(scope), id: batch.id });
219
+ }
220
+ async importSourceFileStillReferenced(manager, scope, task) {
221
+ const where = { ...activeScope(scope), sourceFileId: task.sourceFileId };
222
+ const [otherTasks, controlledBatches, sanctionBatches, contracts, orders] = await Promise.all([
223
+ manager.getRepository(ImportTask).count({ where: { ...where, id: Not(task.id) } }),
224
+ manager.getRepository(ControlledCatalogBatch).count({ where }),
225
+ manager.getRepository(SanctionCatalogBatch).count({ where }),
226
+ manager.getRepository(CustomerContract).count({ where }),
227
+ manager.getRepository(PurchaseOrder).count({ where })
228
+ ]);
229
+ return otherTasks + controlledBatches + sanctionBatches + contracts + orders > 0;
230
+ }
144
231
  async listActiveCustomerContractReanalyses(scope) {
145
232
  const [contracts, tasks] = await Promise.all([
146
233
  this.dataSource.getRepository(CustomerContract).find({ where: { ...activeScope(scope), currentReanalysisRunId: Not(IsNull()), deletedAt: IsNull() } }),
@@ -1045,6 +1132,8 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
1045
1132
  const task = await required(repo.findOne({
1046
1133
  where: { ...activeScope(scope), id: taskId }, lock: { mode: 'pessimistic_write' }
1047
1134
  }), '解析任务不存在');
1135
+ if (isCanceledImportTask(task))
1136
+ return task;
1048
1137
  if (task.observerConversationId && task.observerConversationId !== normalizedConversationId) {
1049
1138
  throw new DomainError(DomainErrorCodes.INVALID_INPUT, '观察会话标识不一致');
1050
1139
  }
@@ -1489,6 +1578,12 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
1489
1578
  return repo.save(task);
1490
1579
  });
1491
1580
  }
1581
+ async isImportTaskCanceled(scope, id) {
1582
+ const task = await this.dataSource.getRepository(ImportTask).findOne({
1583
+ where: { ...activeScope(scope), id }
1584
+ });
1585
+ return !task || isCanceledImportTask(task);
1586
+ }
1492
1587
  async markImportCommandDispatched(scope, id) {
1493
1588
  return this.dataSource.transaction(async (manager) => {
1494
1589
  const repo = manager.getRepository(ImportTask);
@@ -1530,6 +1625,12 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
1530
1625
  });
1531
1626
  if (!task)
1532
1627
  return { accepted: false, reason: 'scope_mismatch', sequence: 0 };
1628
+ if (isCanceledImportTask(task)) {
1629
+ return {
1630
+ accepted: false, reason: 'task_canceled',
1631
+ sequence: task.handoffCallbackSequence ?? 0
1632
+ };
1633
+ }
1533
1634
  if (!handoffCallbackIdentityMatches(input, `trade-compliance-import-task-${task.id}`, task.targetAssistantId))
1534
1635
  return { accepted: false, reason: 'identity_mismatch', sequence: task.handoffCallbackSequence ?? 0 };
1535
1636
  const currentSequence = task.handoffCallbackSequence ?? 0;
@@ -3351,6 +3452,9 @@ function assertImportTaskResultWritable(task) {
3351
3452
  throw new DomainError(DomainErrorCodes.INVALID_INPUT, '解析任务状态不允许保存结果');
3352
3453
  }
3353
3454
  }
3455
+ function isCanceledImportTask(task) {
3456
+ return task.status === 'CANCELED' || Boolean(task.deletedAt);
3457
+ }
3354
3458
  function throwImportTaskTerminalState(task) {
3355
3459
  const message = task.status === 'FAILED' || task.status === 'TIMED_OUT'
3356
3460
  ? task.errorMessage || '解析任务未成功完成'