@chenchaolong/plugin-trade-compliance-workbench 1.0.0 → 1.0.2

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 (194) hide show
  1. package/README.md +46 -1
  2. package/dist/assets/branding/yuneec-logo.png +0 -0
  3. package/dist/assets/templates/CI.xlsx +0 -0
  4. package/dist/assets/templates/CUSTOMS_DECLARATION.xlsx +0 -0
  5. package/dist/assets/templates/PI.xlsx +0 -0
  6. package/dist/assets/templates/PL.xlsx +0 -0
  7. package/dist/index.d.ts +14 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/lib/adapters/file-storage.d.ts +4 -2
  10. package/dist/lib/adapters/file-storage.d.ts.map +1 -1
  11. package/dist/lib/adapters/file-storage.js +25 -10
  12. package/dist/lib/adapters/file-storage.js.map +1 -1
  13. package/dist/lib/adapters/hsbianma.provider.d.ts +1 -0
  14. package/dist/lib/adapters/hsbianma.provider.d.ts.map +1 -1
  15. package/dist/lib/adapters/hsbianma.provider.js +32 -3
  16. package/dist/lib/adapters/hsbianma.provider.js.map +1 -1
  17. package/dist/lib/adapters/translation.provider.d.ts +3 -0
  18. package/dist/lib/adapters/translation.provider.d.ts.map +1 -1
  19. package/dist/lib/adapters/translation.provider.js +15 -6
  20. package/dist/lib/adapters/translation.provider.js.map +1 -1
  21. package/dist/lib/constants.d.ts +3 -2
  22. package/dist/lib/constants.d.ts.map +1 -1
  23. package/dist/lib/constants.js +3 -2
  24. package/dist/lib/constants.js.map +1 -1
  25. package/dist/lib/demo-data.definitions.d.ts +28 -0
  26. package/dist/lib/demo-data.definitions.d.ts.map +1 -0
  27. package/dist/lib/demo-data.definitions.js +236 -0
  28. package/dist/lib/demo-data.definitions.js.map +1 -0
  29. package/dist/lib/demo-data.entity.d.ts +9 -0
  30. package/dist/lib/demo-data.entity.d.ts.map +1 -0
  31. package/dist/lib/demo-data.entity.js +24 -0
  32. package/dist/lib/demo-data.entity.js.map +1 -0
  33. package/dist/lib/demo-data.ids.d.ts +4 -0
  34. package/dist/lib/demo-data.ids.d.ts.map +1 -0
  35. package/dist/lib/demo-data.ids.js +19 -0
  36. package/dist/lib/demo-data.ids.js.map +1 -0
  37. package/dist/lib/demo-data.service.d.ts +26 -0
  38. package/dist/lib/demo-data.service.d.ts.map +1 -0
  39. package/dist/lib/demo-data.service.js +124 -0
  40. package/dist/lib/demo-data.service.js.map +1 -0
  41. package/dist/lib/domain/default-settings.d.ts +1 -0
  42. package/dist/lib/domain/default-settings.d.ts.map +1 -1
  43. package/dist/lib/domain/default-settings.js +1 -0
  44. package/dist/lib/domain/default-settings.js.map +1 -1
  45. package/dist/lib/domain/risk-notice.d.ts +59 -0
  46. package/dist/lib/domain/risk-notice.d.ts.map +1 -0
  47. package/dist/lib/domain/risk-notice.js +30 -0
  48. package/dist/lib/domain/risk-notice.js.map +1 -0
  49. package/dist/lib/entities/catalog.entity.d.ts.map +1 -1
  50. package/dist/lib/entities/catalog.entity.js +4 -2
  51. package/dist/lib/entities/catalog.entity.js.map +1 -1
  52. package/dist/lib/entities/customer.entity.d.ts +9 -2
  53. package/dist/lib/entities/customer.entity.d.ts.map +1 -1
  54. package/dist/lib/entities/customer.entity.js +29 -1
  55. package/dist/lib/entities/customer.entity.js.map +1 -1
  56. package/dist/lib/entities/index.d.ts +2 -2
  57. package/dist/lib/entities/index.d.ts.map +1 -1
  58. package/dist/lib/entities/index.js +2 -2
  59. package/dist/lib/entities/index.js.map +1 -1
  60. package/dist/lib/entities/procurement.entity.d.ts +6 -4
  61. package/dist/lib/entities/procurement.entity.d.ts.map +1 -1
  62. package/dist/lib/entities/procurement.entity.js +9 -1
  63. package/dist/lib/entities/procurement.entity.js.map +1 -1
  64. package/dist/lib/entities/settings.entity.d.ts +12 -2
  65. package/dist/lib/entities/settings.entity.d.ts.map +1 -1
  66. package/dist/lib/entities/settings.entity.js +40 -0
  67. package/dist/lib/entities/settings.entity.js.map +1 -1
  68. package/dist/lib/entities/shared.entity.d.ts +10 -5
  69. package/dist/lib/entities/shared.entity.d.ts.map +1 -1
  70. package/dist/lib/entities/shared.entity.js +22 -1
  71. package/dist/lib/entities/shared.entity.js.map +1 -1
  72. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -0
  73. package/dist/lib/remote-components/trade-compliance-workbench/app.js +63 -277
  74. package/dist/lib/remote-ui/analytics-contracts.d.ts +106 -0
  75. package/dist/lib/remote-ui/analytics-contracts.d.ts.map +1 -0
  76. package/dist/lib/remote-ui/analytics-contracts.js +4 -0
  77. package/dist/lib/remote-ui/analytics-contracts.js.map +1 -0
  78. package/dist/lib/remote-ui/app-source.d.ts +2 -0
  79. package/dist/lib/remote-ui/app-source.d.ts.map +1 -0
  80. package/dist/lib/remote-ui/app-source.js +753 -0
  81. package/dist/lib/remote-ui/app-source.js.map +1 -0
  82. package/dist/lib/remote-ui/bridge.d.ts +36 -0
  83. package/dist/lib/remote-ui/bridge.d.ts.map +1 -0
  84. package/dist/lib/remote-ui/bridge.js +159 -0
  85. package/dist/lib/remote-ui/bridge.js.map +1 -0
  86. package/dist/lib/remote-ui/components/data-grid.d.ts +34 -0
  87. package/dist/lib/remote-ui/components/data-grid.d.ts.map +1 -0
  88. package/dist/lib/remote-ui/components/data-grid.js +59 -0
  89. package/dist/lib/remote-ui/components/data-grid.js.map +1 -0
  90. package/dist/lib/remote-ui/components/forms.d.ts +63 -0
  91. package/dist/lib/remote-ui/components/forms.d.ts.map +1 -0
  92. package/dist/lib/remote-ui/components/forms.js +117 -0
  93. package/dist/lib/remote-ui/components/forms.js.map +1 -0
  94. package/dist/lib/remote-ui/components/layout.d.ts +34 -0
  95. package/dist/lib/remote-ui/components/layout.d.ts.map +1 -0
  96. package/dist/lib/remote-ui/components/layout.js +89 -0
  97. package/dist/lib/remote-ui/components/layout.js.map +1 -0
  98. package/dist/lib/remote-ui/contract-import.d.ts +82 -0
  99. package/dist/lib/remote-ui/contract-import.d.ts.map +1 -0
  100. package/dist/lib/remote-ui/contract-import.js +102 -0
  101. package/dist/lib/remote-ui/contract-import.js.map +1 -0
  102. package/dist/lib/remote-ui/contracts.d.ts +257 -0
  103. package/dist/lib/remote-ui/contracts.d.ts.map +1 -0
  104. package/dist/lib/remote-ui/contracts.js +12 -0
  105. package/dist/lib/remote-ui/contracts.js.map +1 -0
  106. package/dist/lib/remote-ui/customer-contract-contracts.d.ts +227 -0
  107. package/dist/lib/remote-ui/customer-contract-contracts.d.ts.map +1 -0
  108. package/dist/lib/remote-ui/customer-contract-contracts.js +10 -0
  109. package/dist/lib/remote-ui/customer-contract-contracts.js.map +1 -0
  110. package/dist/lib/remote-ui/model.d.ts +31 -0
  111. package/dist/lib/remote-ui/model.d.ts.map +1 -0
  112. package/dist/lib/remote-ui/model.js +248 -0
  113. package/dist/lib/remote-ui/model.js.map +1 -0
  114. package/dist/lib/remote-ui/pages/analytics.d.ts +12 -0
  115. package/dist/lib/remote-ui/pages/analytics.d.ts.map +1 -0
  116. package/dist/lib/remote-ui/pages/analytics.js +123 -0
  117. package/dist/lib/remote-ui/pages/analytics.js.map +1 -0
  118. package/dist/lib/remote-ui/pages/catalogs.d.ts +225 -0
  119. package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -0
  120. package/dist/lib/remote-ui/pages/catalogs.js +572 -0
  121. package/dist/lib/remote-ui/pages/catalogs.js.map +1 -0
  122. package/dist/lib/remote-ui/pages/contracts.d.ts +80 -0
  123. package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -0
  124. package/dist/lib/remote-ui/pages/contracts.js +396 -0
  125. package/dist/lib/remote-ui/pages/contracts.js.map +1 -0
  126. package/dist/lib/remote-ui/pages/files.d.ts +31 -0
  127. package/dist/lib/remote-ui/pages/files.d.ts.map +1 -0
  128. package/dist/lib/remote-ui/pages/files.js +64 -0
  129. package/dist/lib/remote-ui/pages/files.js.map +1 -0
  130. package/dist/lib/remote-ui/pages/home.d.ts +71 -0
  131. package/dist/lib/remote-ui/pages/home.d.ts.map +1 -0
  132. package/dist/lib/remote-ui/pages/home.js +136 -0
  133. package/dist/lib/remote-ui/pages/home.js.map +1 -0
  134. package/dist/lib/remote-ui/pages/procurement.d.ts +36 -0
  135. package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -0
  136. package/dist/lib/remote-ui/pages/procurement.js +370 -0
  137. package/dist/lib/remote-ui/pages/procurement.js.map +1 -0
  138. package/dist/lib/remote-ui/pages/sales.d.ts +12 -0
  139. package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -0
  140. package/dist/lib/remote-ui/pages/sales.js +106 -0
  141. package/dist/lib/remote-ui/pages/sales.js.map +1 -0
  142. package/dist/lib/remote-ui/pages/settings.d.ts +34 -0
  143. package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -0
  144. package/dist/lib/remote-ui/pages/settings.js +259 -0
  145. package/dist/lib/remote-ui/pages/settings.js.map +1 -0
  146. package/dist/lib/remote-ui/procurement-contracts.d.ts +177 -0
  147. package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -0
  148. package/dist/lib/remote-ui/procurement-contracts.js +2 -0
  149. package/dist/lib/remote-ui/procurement-contracts.js.map +1 -0
  150. package/dist/lib/remote-ui/sales-contracts.d.ts +133 -0
  151. package/dist/lib/remote-ui/sales-contracts.d.ts.map +1 -0
  152. package/dist/lib/remote-ui/sales-contracts.js +2 -0
  153. package/dist/lib/remote-ui/sales-contracts.js.map +1 -0
  154. package/dist/lib/remote-ui/settings-contracts.d.ts +114 -0
  155. package/dist/lib/remote-ui/settings-contracts.d.ts.map +1 -0
  156. package/dist/lib/remote-ui/settings-contracts.js +6 -0
  157. package/dist/lib/remote-ui/settings-contracts.js.map +1 -0
  158. package/dist/lib/remote-ui/upload-policy.d.ts +18 -0
  159. package/dist/lib/remote-ui/upload-policy.d.ts.map +1 -0
  160. package/dist/lib/remote-ui/upload-policy.js +56 -0
  161. package/dist/lib/remote-ui/upload-policy.js.map +1 -0
  162. package/dist/lib/runtime-validation.d.ts +61 -0
  163. package/dist/lib/runtime-validation.d.ts.map +1 -0
  164. package/dist/lib/runtime-validation.js +243 -0
  165. package/dist/lib/runtime-validation.js.map +1 -0
  166. package/dist/lib/sales-file.service.d.ts +54 -6
  167. package/dist/lib/sales-file.service.d.ts.map +1 -1
  168. package/dist/lib/sales-file.service.js +278 -33
  169. package/dist/lib/sales-file.service.js.map +1 -1
  170. package/dist/lib/trade-compliance.config.d.ts +32 -0
  171. package/dist/lib/trade-compliance.config.d.ts.map +1 -1
  172. package/dist/lib/trade-compliance.config.js +16 -4
  173. package/dist/lib/trade-compliance.config.js.map +1 -1
  174. package/dist/lib/types.d.ts +253 -1
  175. package/dist/lib/types.d.ts.map +1 -1
  176. package/dist/lib/types.js +24 -0
  177. package/dist/lib/types.js.map +1 -1
  178. package/dist/lib/workbench-view.provider.d.ts +675 -38
  179. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  180. package/dist/lib/workbench-view.provider.js +411 -40
  181. package/dist/lib/workbench-view.provider.js.map +1 -1
  182. package/dist/lib/workbench.middleware.d.ts +70 -19
  183. package/dist/lib/workbench.middleware.d.ts.map +1 -1
  184. package/dist/lib/workbench.middleware.js +5 -1
  185. package/dist/lib/workbench.middleware.js.map +1 -1
  186. package/dist/lib/workbench.plugin.d.ts.map +1 -1
  187. package/dist/lib/workbench.plugin.js +6 -4
  188. package/dist/lib/workbench.plugin.js.map +1 -1
  189. package/dist/lib/workbench.service.d.ts +629 -48
  190. package/dist/lib/workbench.service.d.ts.map +1 -1
  191. package/dist/lib/workbench.service.js +1818 -300
  192. package/dist/lib/workbench.service.js.map +1 -1
  193. package/package.json +15 -4
  194. package/scripts/seed-demo-data.mjs +138 -0
@@ -1 +1 @@
1
- {"version":3,"file":"workbench-view.provider.d.ts","sourceRoot":"","sources":["../../src/lib/workbench-view.provider.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,+BAA+B,EAAE,MAAM,wBAAwB,CAAA;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AASvE,qBAEa,oCAAoC;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAAmC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAAoB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAjH,OAAO,EAAE,+BAA+B,EAAmB,UAAU,EAAE,gBAAgB,EAAmB,OAAO,EAAE,0BAA0B;IAE1K,QAAQ,CAAC,OAAO,EAAE,GAAG;IAErB,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAatC,uBAAuB,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG;;;;IAYtE,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAQtD,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoCjF,qBAAqB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkBtG"}
1
+ {"version":3,"file":"workbench-view.provider.d.ts","sourceRoot":"","sources":["../../src/lib/workbench-view.provider.ts"],"names":[],"mappings":"AAKA,OAAO,EAAoG,KAAK,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AACnK,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAGrD,OAAO,EAAE,+BAA+B,EAAqC,MAAM,wBAAwB,CAAA;AAC3G,OAAO,EAA6B,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AAGvE,OAAO,EAAwO,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAExR,OAAO,EAAE,8BAA8B,EAA0B,MAAM,wBAAwB,CAAA;AAO/F,qBAEa,oCAAoC;IAE7C,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACZ,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACY,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;gBAJjE,OAAO,EAAE,+BAA+B,EACxC,UAAU,EAAE,gBAAgB,EAC5B,OAAO,EAAE,0BAA0B,EACvB,QAAQ,CAAC,EAAE,8BAA8B,YAAA,EACH,cAAc,CAAC,EAAE,qBAAqB,YAAA;IAG3G,QAAQ,CAAC,OAAO,EAAE,GAAG;IAErB,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAatC,uBAAuB,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG;;;;IAatE,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsDtD,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6FjF,qBAAqB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAoCvF,iBAAiB;IAsB/B,OAAO,CAAC,QAAQ;CAGjB;AAoDD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CASvG;AAsHD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,UAA0E"}
@@ -1,25 +1,32 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import { Injectable } from '@nestjs/common';
1
+ import { __decorate, __metadata, __param } from "tslib";
2
+ import { Inject, Injectable, Optional } from '@nestjs/common';
3
3
  import { createRequire } from 'node:module';
4
4
  import { dirname, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { readFile } from 'node:fs/promises';
7
- import { renderRemoteReactIframeHtml, ViewExtensionProvider } from '@xpert-ai/plugin-sdk';
7
+ import { PLUGIN_CONFIG_RESOLVER_TOKEN, renderRemoteReactIframeHtml, RequestContext, ViewExtensionProvider } from '@xpert-ai/plugin-sdk';
8
+ import { PermissionsEnum } from '@xpert-ai/contracts';
9
+ import XLSX from 'xlsx';
8
10
  import { FIXED_SLOT, FEATURE, ICON, MAIN_SLOT, PLUGIN_NAME, REMOTE_ENTRY_KEY, TOOL_NAMES, VIEW_KEY, VIEW_PROVIDER_KEY } from './constants.js';
9
11
  import { TradeComplianceWorkbenchService } from './workbench.service.js';
10
- import { SalesFileService } from './sales-file.service.js';
12
+ import { MAX_EDITED_WORKBOOK_BYTES, SalesFileService } from './sales-file.service.js';
11
13
  import { TradeComplianceFileStorage } from './adapters/file-storage.js';
12
14
  import { extractDocumentText } from './adapters/document-text-extractor.js';
13
15
  import { getHsBianmaCodeDetail, searchHsBianmaCodes } from './adapters/hsbianma.provider.js';
14
- import { DomainError, DomainErrorCodes, SourceFileFormats } from './types.js';
16
+ import { DomainError, DomainErrorCodes, SalesFileTypes, SourceFileFormats } from './types.js';
17
+ import { parseAnalyticsQuery, parseBankAccountType, parseExpectedRevision, parseRestoreSettingsInput, parseSettingsType } from './runtime-validation.js';
18
+ import { TradeComplianceDemoDataService } from './demo-data.service.js';
19
+ import { resolveTradeComplianceConfig } from './trade-compliance.config.js';
15
20
  const moduleDir = dirname(fileURLToPath(import.meta.url));
16
21
  const requireFromHere = createRequire(import.meta.url);
17
22
  const text = (en_US, zh_Hans) => ({ en_US, zh_Hans });
18
23
  let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewProvider {
19
- constructor(service, salesFiles, storage) {
24
+ constructor(service, salesFiles, storage, demoData, configResolver) {
20
25
  this.service = service;
21
26
  this.salesFiles = salesFiles;
22
27
  this.storage = storage;
28
+ this.demoData = demoData;
29
+ this.configResolver = configResolver;
23
30
  }
24
31
  supports(context) { return context.hostType === 'agent'; }
25
32
  getViewManifests(_context, slot) {
@@ -38,22 +45,81 @@ let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewPro
38
45
  async getRemoteComponentEntry(_context, viewKey, component) {
39
46
  if (viewKey !== VIEW_KEY || component.entry !== REMOTE_ENTRY_KEY)
40
47
  return { html: '<!doctype html><html><body>Unsupported component.</body></html>', contentType: 'text/html; charset=utf-8' };
41
- const [appScript, editorScript, editorCss, reactUmd, reactDomUmd] = await Promise.all([
48
+ const [appScript, appCss, editorScript, editorCss, reactUmd, reactDomUmd] = await Promise.all([
42
49
  readFile(join(moduleDir, 'remote-components', REMOTE_ENTRY_KEY, 'app.js'), 'utf8'),
50
+ readFile(join(moduleDir, 'remote-components', REMOTE_ENTRY_KEY, 'app.css'), 'utf8'),
43
51
  readFile(join(moduleDir, 'remote-components', REMOTE_ENTRY_KEY, 'editor.js'), 'utf8'),
44
52
  readFile(join(moduleDir, 'remote-components', REMOTE_ENTRY_KEY, 'editor.css'), 'utf8'),
45
53
  readPackageFile('react', 'umd/react.production.min.js'), readPackageFile('react-dom', 'umd/react-dom.production.min.js')
46
54
  ]);
47
- const spreadsheetRuntime = `(()=>{const style=document.createElement('style');style.textContent=${JSON.stringify(editorCss)};document.head.appendChild(style)})();\n${editorScript}\n${appScript}`;
48
- return { html: renderRemoteReactIframeHtml({ title: '外贸合规工作台', lang: 'zh-Hans', reactUmd, reactDomUmd, appScript: spreadsheetRuntime }), contentType: 'text/html; charset=utf-8' };
55
+ const spreadsheetRuntime = `${editorScript}\n${appScript}`;
56
+ return { html: renderRemoteReactIframeHtml({ title: '外贸合规工作台', lang: 'zh-Hans', reactUmd, reactDomUmd, appScript: spreadsheetRuntime, appCss: sanitizeIframeCss(`${appCss}\n${editorCss}`) }), contentType: 'text/html; charset=utf-8' };
49
57
  }
50
58
  async getViewData(context, _viewKey, query) {
51
59
  const scope = scopeFromContext(context);
52
- const all = { page: 1, pageSize: 200, search: query?.search, parameters: query?.parameters ?? {} };
53
- const [dashboard, controlledGoods, controlledBatches, sanctionedCompanies, sanctionBatches, suppliers, purchaseOrders, purchaseLines, customers, customerContracts, derivedSalesOrders, generatedFiles, settings, analytics, importTasks] = await Promise.all([
54
- this.service.dashboard(scope), this.service.listControlledGoods(scope, all), this.service.listControlledBatches(scope, all), this.service.listSanctionedCompanies(scope, all), this.service.listSanctionBatches(scope, all), this.service.listSuppliers(scope, all), this.service.listPurchaseOrders(scope, undefined, all), this.service.listPurchaseLines(scope, undefined, all), this.service.listCustomers(scope, all), this.service.listCustomerContracts(scope, all), this.service.listDerivedSalesOrders(scope), this.service.listGeneratedFiles(scope, all), this.service.settings(scope), this.service.analytics(scope, { ...(query?.parameters ?? {}), search: query?.search }), this.service.listImportTasks(scope, all)
60
+ const parameters = record(query?.parameters);
61
+ const collection = stringValue(parameters.collection);
62
+ if (collection) {
63
+ const pageQuery = { page: numberValue(query?.page, 1), pageSize: numberValue(query?.pageSize, 20), parameters: record(parameters.filters) };
64
+ if (collection === 'controlledGoods')
65
+ return { collection, ...await this.service.listControlledGoods(scope, pageQuery) };
66
+ if (collection === 'sanctionedCompanies')
67
+ return { collection, ...await this.service.listSanctionedCompanies(scope, pageQuery) };
68
+ if (collection === 'suppliers')
69
+ return { collection, ...await this.service.listSuppliers(scope, pageQuery) };
70
+ if (collection === 'purchaseOrders') {
71
+ const supplierId = requiredString(parameters.supplierId, 'supplierId');
72
+ const [page, supplier, metrics] = await Promise.all([
73
+ this.service.listPurchaseOrders(scope, supplierId, pageQuery),
74
+ this.service.getSupplier(scope, supplierId),
75
+ this.service.getSupplierProcurementMetrics(scope, supplierId)
76
+ ]);
77
+ return { collection, ...page, supplier, metrics };
78
+ }
79
+ if (collection === 'purchaseLines') {
80
+ const purchaseOrderId = requiredString(parameters.purchaseOrderId, 'purchaseOrderId');
81
+ const [page, order] = await Promise.all([
82
+ this.service.listPurchaseLines(scope, purchaseOrderId, pageQuery),
83
+ this.service.getPurchaseOrder(scope, purchaseOrderId)
84
+ ]);
85
+ return { collection, ...page, order, supplier: await this.service.getSupplier(scope, order.supplierId) };
86
+ }
87
+ if (collection === 'customerContracts')
88
+ return { collection, ...await this.service.listCustomerContracts(scope, pageQuery) };
89
+ if (collection === 'salesCustomers')
90
+ return { collection, ...await this.service.listSalesCustomers(scope, pageQuery) };
91
+ if (collection === 'salesOrders')
92
+ return { collection, ...await this.service.listSalesOrders(scope, requiredString(parameters.customerId, 'customerId'), pageQuery) };
93
+ if (collection === 'salesLines') {
94
+ const [customerContractId, purchaseOrderId] = salesOrderKey(requiredString(parameters.orderKey, 'orderKey'));
95
+ return { collection, ...await this.service.listSalesLines(scope, { customerId: requiredString(parameters.customerId, 'customerId'), customerContractId, purchaseOrderId, ...pageQuery }) };
96
+ }
97
+ if (collection === 'salesFileOrders')
98
+ return { collection, ...await this.service.listSalesFileOrders(scope, pageQuery) };
99
+ if (collection === 'generatedFiles')
100
+ return { collection, ...await this.service.listGeneratedFiles(scope, { ...pageQuery, parameters: { ...pageQuery.parameters, customerContractId: parameters.customerContractId, purchaseOrderId: parameters.purchaseOrderId } }) };
101
+ if (collection === 'analytics')
102
+ return { collection, ...await this.service.analytics(scope, parseAnalyticsQuery({ mode: parameters.mode, dimension: parameters.dimension, filters: record(parameters.filters), page: query?.page, pageSize: query?.pageSize })) };
103
+ if (collection === 'settings')
104
+ return { collection, ...await this.service.settings(scope) };
105
+ if (collection === 'customerContractAnalysis')
106
+ return { collection, ...await this.service.getCustomerContractReanalysisResult(scope, requiredString(parameters.runId, 'runId')) };
107
+ if (collection === 'importTask')
108
+ return { collection, ...await this.service.getImportTaskResult(scope, requiredString(parameters.taskId, 'taskId')) };
109
+ if (collection === 'importIssues')
110
+ return { collection, ...await this.service.listImportIssues(scope, {
111
+ documentType: catalogDocumentType(parameters.documentType), batchId: requiredString(parameters.batchId, 'batchId'), issueType: importIssueType(parameters.issueType), page: pageQuery.page, pageSize: pageQuery.pageSize
112
+ }) };
113
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '不支持的数据集合', { collection });
114
+ }
115
+ const all = { page: 1, pageSize: 200, search: query?.search, parameters };
116
+ const catalogPage = { page: 1, pageSize: 20, parameters: {} };
117
+ const [dashboard, controlledGoods, controlledBatches, sanctionedCompanies, sanctionBatches, customers, derivedSalesOrders, generatedFiles, settings, analytics, importTasks] = await Promise.all([
118
+ this.service.dashboard(scope), this.service.listControlledGoods(scope, catalogPage), this.service.listControlledBatches(scope, all), this.service.listSanctionedCompanies(scope, catalogPage), this.service.listSanctionBatches(scope, all), this.service.listCustomers(scope, all), this.service.listDerivedSalesOrders(scope), this.service.listGeneratedFiles(scope, all), this.service.settings(scope), this.service.analytics(scope, { mode: 'PURCHASE', dimension: 'SUPPLIER', filters: {}, page: 1, pageSize: 20 }), this.service.listImportTasks(scope, all)
55
119
  ]);
56
- return { items: [], total: 0, summary: { dashboard, controlledGoods, controlledBatches, sanctionedCompanies, sanctionBatches, suppliers, purchaseOrders, purchaseLines, customers, customerContracts, derivedSalesOrders, generatedFiles, settings, analytics, importTasks } };
120
+ const emptyCollection = { items: [], total: 0, page: 1, pageSize: 20 };
121
+ const uploadPolicy = { maxUploadMb: resolveTradeComplianceConfig(this.configResolver, scope.organizationId).storage.maxUploadMb };
122
+ return { items: [], total: 0, summary: { uploadPolicy, dashboard, controlledGoods, controlledBatches, sanctionedCompanies, sanctionBatches, suppliers: emptyCollection, purchaseOrders: emptyCollection, purchaseLines: emptyCollection, customers, customerContracts: emptyCollection, derivedSalesOrders, generatedFiles, settings, analytics, importTasks } };
57
123
  }
58
124
  async executeViewAction(context, _viewKey, actionKey, request) {
59
125
  const scope = scopeFromContext(context);
@@ -62,6 +128,14 @@ let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewPro
62
128
  let data;
63
129
  switch (actionKey) {
64
130
  case 'refresh': return { success: true, refresh: true, message: text('Refreshed', '已刷新') };
131
+ case 'seed_demo_data': {
132
+ RequestContext.hasPermission(PermissionsEnum.ORG_DEMO_EDIT, true);
133
+ parseDemoSeedInput(input);
134
+ if (!this.demoData)
135
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '演示数据服务不可用');
136
+ data = await this.demoData.seed(scope);
137
+ break;
138
+ }
65
139
  case 'save_controlled_goods':
66
140
  data = await this.service.saveControlledGoods(scope, input);
67
141
  break;
@@ -104,21 +178,64 @@ let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewPro
104
178
  case 'get_customer_contract_review':
105
179
  data = await this.service.getCustomerContractReview(scope, requiredString(input.id, 'id'));
106
180
  break;
107
- case 'update_customer_contract_review':
108
- data = await this.service.updateCustomerContractReview(scope, requiredString(input.id, 'id'), input);
181
+ case 'update_customer_contract_review': {
182
+ const parsed = customerContractReviewUpdate(request?.input);
183
+ data = await this.service.updateCustomerContractReview(scope, parsed.id, parsed.update);
184
+ break;
185
+ }
186
+ case 'reanalyze_customer_contract': {
187
+ const started = await this.service.startCustomerContractReanalysis(scope, { reviewSource: customerContractReviewSource(input.reviewSource), id: stringValue(input.id), importTaskId: stringValue(input.importTaskId) });
188
+ const proposedCommand = started.run.executionCommand ?? buildCustomerContractReanalysisCommand(started);
189
+ const run = await this.service.saveCustomerContractReanalysisExecution(scope, started.run.id, proposedCommand);
190
+ if (!run.executionCommand)
191
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '重新分析任务缺少已持久化的执行命令');
192
+ data = { run, commands: [run.executionCommand], polling: { intervalMs: 2000, timeoutMs: 1200000 } };
193
+ break;
194
+ }
195
+ case 'mark_customer_contract_reanalysis_dispatched':
196
+ data = await this.service.markCustomerContractReanalysisDispatched(scope, requiredString(input.runId, 'runId'));
197
+ break;
198
+ case 'mark_customer_contract_reanalysis_failed':
199
+ data = await this.service.markCustomerContractReanalysisFailed(scope, requiredString(input.runId, 'runId'), { errorCode: requiredString(input.errorCode, 'errorCode'), errorMessage: requiredString(input.errorMessage, 'errorMessage') });
109
200
  break;
110
201
  case 'delete_entity':
111
202
  data = await this.service.softDelete(scope, requiredString(input.entityType, 'entityType'), requiredString(input.id, 'id'));
112
203
  break;
113
- case 'save_settings':
114
- data = await this.service.saveSettings(scope, input.settingsType, record(input.values));
204
+ case 'save_settings': {
205
+ const settingsType = parseSettingsType(input.settingsType);
206
+ data = await this.service.saveSettings(scope, settingsType, record(input.values), parseExpectedRevision(input.expectedRevision, settingsType === 'rate'));
207
+ break;
208
+ }
209
+ case 'restore_settings_default':
210
+ data = await this.service.restoreSettingsDefault(scope, parseRestoreSettingsInput(input));
211
+ break;
212
+ case 'delete_settings_rate': {
213
+ const settingsType = parseSettingsType(input.settingsType);
214
+ if (settingsType !== 'rate')
215
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '删除月度汇率必须使用 rate 设置类型', { settingsType });
216
+ data = await this.service.deleteMonthlyRate(scope, { id: requiredString(input.id, 'id'), expectedRevision: parseExpectedRevision(input.expectedRevision) });
217
+ break;
218
+ }
219
+ case 'discard_company_logo': {
220
+ const logoFileId = requiredString(input.logoFileId, 'logoFileId');
221
+ await this.service.discardCompanyLogoDraft(scope, logoFileId);
222
+ data = { logoFileId };
223
+ break;
224
+ }
225
+ case 'export_analytics': {
226
+ const result = await this.service.exportAnalytics(scope, parseAnalyticsQuery({ mode: input.mode, dimension: input.dimension, filters: input.filters, page: 1, pageSize: 20 }));
227
+ data = { fileName: result.fileName, mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', base64: result.buffer.toString('base64') };
228
+ break;
229
+ }
230
+ case 'preflight_sales_file':
231
+ data = await this.salesFiles.preflight(scope, salesFileGenerationInput(input));
115
232
  break;
116
233
  case 'generate_sales_file':
117
- data = await this.salesFiles.generate(scope, input);
234
+ data = await this.salesFiles.generate(scope, salesFileGenerationInput(input));
118
235
  break;
119
236
  case 'download_sales_file': {
120
237
  const result = await this.salesFiles.download(scope, requiredString(input.id, 'id'));
121
- data = { fileName: result.item.fileName, mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', base64: result.buffer.toString('base64') };
238
+ data = { fileName: result.item.fileName, storageFileId: result.item.storageFileId, editVersion: result.item.editVersion, mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', base64: result.buffer.toString('base64') };
122
239
  break;
123
240
  }
124
241
  case 'download_import_issues': {
@@ -126,19 +243,39 @@ let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewPro
126
243
  data = { fileName: result.fileName, mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', base64: result.buffer.toString('base64') };
127
244
  break;
128
245
  }
246
+ case 'retry_import_task':
247
+ data = await this.startCatalogRetry(scope, requiredString(input.taskId, 'taskId'));
248
+ break;
249
+ case 'mark_import_command_dispatched':
250
+ data = await this.service.markImportCommandDispatched(scope, requiredString(input.taskId, 'taskId'));
251
+ break;
129
252
  case 'download_source_file': {
130
- const buffer = await this.storage.read(requiredString(input.sourceFileId, 'sourceFileId'));
131
- data = { fileName: requiredString(input.fileName, 'fileName'), mimeType: 'application/octet-stream', base64: buffer.toString('base64') };
253
+ const descriptor = await this.service.getSourceFileDescriptor(scope, requiredString(input.sourceFileId, 'sourceFileId'));
254
+ const buffer = await this.storage.read(descriptor.sourceFileId);
255
+ data = { fileName: descriptor.sourceFileName, mimeType: mimeTypeForFile(descriptor.sourceFileName), base64: buffer.toString('base64') };
256
+ break;
257
+ }
258
+ case 'preview_source_file': {
259
+ const descriptor = await this.service.getSourceFileDescriptor(scope, requiredString(input.sourceFileId, 'sourceFileId'));
260
+ const buffer = await this.storage.read(descriptor.sourceFileId);
261
+ data = await sourcePreview(descriptor.sourceFileName, buffer);
132
262
  break;
133
263
  }
134
- case 'save_edited_sales_file':
135
- data = await this.salesFiles.saveEditedWorkbook(scope, requiredString(input.id, 'id'), Buffer.from(requiredString(input.base64, 'base64'), 'base64'));
264
+ case 'save_edited_sales_file': {
265
+ const base64 = requiredString(input.base64, 'base64');
266
+ if (base64.length > Math.ceil(MAX_EDITED_WORKBOOK_BYTES / 3) * 4)
267
+ throw new DomainError(DomainErrorCodes.FILE_TOO_LARGE, '编辑后的工作簿不能超过 20 MB');
268
+ const buffer = Buffer.from(base64, 'base64');
269
+ if (buffer.length > MAX_EDITED_WORKBOOK_BYTES)
270
+ throw new DomainError(DomainErrorCodes.FILE_TOO_LARGE, '编辑后的工作簿不能超过 20 MB');
271
+ data = await this.salesFiles.saveEditedWorkbook(scope, requiredString(input.id, 'id'), buffer, { editVersion: requiredNonNegativeInteger(input.editVersion, 'editVersion'), expectedStorageFileId: requiredString(input.expectedStorageFileId, 'expectedStorageFileId') });
136
272
  break;
273
+ }
137
274
  case 'search_hs_code':
138
- data = await searchHsBianmaCodes({ keywords: requiredString(input.keywords, 'keywords'), page: numberValue(input.page, 1), filterFailureCode: true, displayChapter: false, displayEnName: true }, hsConfig());
275
+ data = await searchHsBianmaCodes({ keywords: requiredString(input.keywords, 'keywords'), page: numberValue(input.page, 1), filterFailureCode: true, displayChapter: false, displayEnName: true }, this.hsConfig(scope));
139
276
  break;
140
277
  case 'get_hs_code_detail':
141
- data = await getHsBianmaCodeDetail({ code: stringValue(input.code), detailUrl: stringValue(input.detailUrl) }, hsConfig());
278
+ data = { ...await getHsBianmaCodeDetail({ code: stringValue(input.code), detailUrl: stringValue(input.detailUrl) }, this.hsConfig(scope)), queriedAt: new Date().toISOString() };
142
279
  break;
143
280
  default: throw new DomainError(DomainErrorCodes.INVALID_INPUT, '不支持的操作', { actionKey });
144
281
  }
@@ -153,43 +290,96 @@ let TradeComplianceWorkbenchViewProvider = class TradeComplianceWorkbenchViewPro
153
290
  return failure(new DomainError(DomainErrorCodes.INVALID_INPUT, '不支持的视图'));
154
291
  const scope = scopeFromContext(context);
155
292
  const input = record(request?.input);
293
+ let task;
156
294
  try {
157
- const fileName = fileNameOf(file, input);
158
- const format = formatOf(fileName);
159
- const size = Number(file?.size ?? 0);
160
- if (!format)
161
- throw new DomainError(DomainErrorCodes.UNSUPPORTED_FILE_FORMAT, '仅支持 PDF、Word、Excel 和常见图片格式');
162
- if (size > 20 * 1024 * 1024)
163
- throw new DomainError(DomainErrorCodes.FILE_TOO_LARGE, '文件不能超过 20 MB');
164
- const buffer = bufferOf(file) ?? await bufferFromPath(input);
295
+ const buffer = bufferOf(file);
165
296
  if (!buffer?.length)
166
297
  throw new DomainError(DomainErrorCodes.INVALID_INPUT, '无法读取上传文件内容');
298
+ if (actionKey === 'upload_company_logo') {
299
+ if (buffer.length > 2 * 1024 * 1024)
300
+ throw new DomainError(DomainErrorCodes.FILE_TOO_LARGE, '公司 Logo 不能超过 2 MB');
301
+ const fileName = fileNameOf(file, input);
302
+ const format = validateSourceFile(fileName, String(file?.mimetype ?? file?.type ?? ''), buffer);
303
+ if (format !== 'PNG' && format !== 'JPEG')
304
+ throw new DomainError(DomainErrorCodes.UNSUPPORTED_FILE_FORMAT, '公司 Logo 仅支持 PNG、JPG/JPEG');
305
+ const asset = await this.service.createCompanyLogoDraft(scope, buffer, fileName);
306
+ return { success: true, refresh: false, message: text('Logo uploaded', '公司 Logo 上传成功'), data: { logoFileId: asset.id } };
307
+ }
308
+ const maxUploadMb = resolveTradeComplianceConfig(this.configResolver, scope.organizationId).storage.maxUploadMb;
309
+ if (buffer.length > maxUploadMb * 1024 * 1024)
310
+ throw new DomainError(DomainErrorCodes.FILE_TOO_LARGE, `文件不能超过 ${maxUploadMb} MB`);
311
+ const fileName = fileNameOf(file, input);
312
+ const format = validateSourceFile(fileName, String(file?.mimetype ?? file?.type ?? ''), buffer);
313
+ const sourcePlatformFileId = platformFileId(file, input);
314
+ if (!sourcePlatformFileId)
315
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '上传文件缺少平台文件标识,请重新上传');
167
316
  const documentType = documentTypeForAction(actionKey);
168
317
  const storageFileId = await this.storage.save(buffer, fileName, 'uploads');
169
- const task = await this.service.createImportTask(scope, { documentType, sourceFileId: storageFileId, sourceFileName: fileName, sourceFileFormat: format, status: 'EXTRACTING' });
318
+ const executionInput = executionInputForAction(actionKey, input);
319
+ task = await this.service.createImportTask(scope, { documentType, sourceFileId: storageFileId, sourceFileName: fileName, sourceFileFormat: format, sourcePlatformFileId, executionInput, status: 'EXTRACTING' });
170
320
  let extracted = { text: '', chunks: [], kind: 'image', originalLength: 0 };
171
321
  if (!['PNG', 'JPEG', 'WEBP'].includes(format))
172
322
  extracted = await extractDocumentText({ buffer, fileName, mimeType: file?.mimetype ?? file?.type, maxChars: 100000 });
173
323
  const parsingTask = await this.service.markImportTaskParsing(scope, task.id);
174
324
  const command = buildAssistantCommand({ actionKey, file, input, task, extracted, catalogType: input.catalogType, replaceBatchId: input.replaceBatchId });
325
+ await this.service.saveImportTaskExecution(scope, task.id, command);
175
326
  return { success: true, refresh: false, message: text('Parsing started', '文件已进入解析中,请勿重复操作'), data: { task: parsingTask, commands: [command], polling: { intervalMs: 2000, timeoutMs: 1200000 } } };
176
327
  }
177
328
  catch (error) {
329
+ if (task?.id)
330
+ await this.service.markImportTaskFailed(scope, task.id, { errorCode: error instanceof DomainError ? error.code : DomainErrorCodes.INVALID_INPUT, errorMessage: error instanceof Error ? error.message : String(error) });
178
331
  return failure(error);
179
332
  }
180
333
  }
334
+ async startCatalogRetry(scope, sourceTaskId) {
335
+ let task;
336
+ try {
337
+ task = await this.service.createRetryImportTask(scope, sourceTaskId);
338
+ const buffer = await this.storage.read(task.sourceFileId);
339
+ let extracted = { text: '', chunks: [], kind: 'image', originalLength: 0 };
340
+ if (!['PNG', 'JPEG', 'WEBP'].includes(task.sourceFileFormat))
341
+ extracted = await extractDocumentText({ buffer, fileName: task.sourceFileName, maxChars: 100000 });
342
+ if (!task.sourcePlatformFileId)
343
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '源文件缺少平台文件标识,无法重新解析');
344
+ const executionInput = task.executionInput;
345
+ if (!executionInput)
346
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '重试任务缺少执行参数');
347
+ const file = { name: task.sourceFileName, size: buffer.length, fileAssetId: task.sourcePlatformFileId, fileId: task.sourcePlatformFileId };
348
+ const input = { ...executionInput, fileAssetId: task.sourcePlatformFileId };
349
+ const command = buildAssistantCommand({ actionKey: executionInput.actionKey, file, input, task, extracted, catalogType: executionInput.catalogType, replaceBatchId: executionInput.replaceBatchId });
350
+ const parsingTask = await this.service.markImportTaskParsing(scope, task.id);
351
+ await this.service.saveImportTaskExecution(scope, task.id, command);
352
+ return { task: parsingTask, commands: [command], polling: { intervalMs: 2000, timeoutMs: 1200000 } };
353
+ }
354
+ catch (error) {
355
+ if (task?.id)
356
+ await this.service.markImportTaskFailed(scope, task.id, { errorCode: error instanceof DomainError ? error.code : DomainErrorCodes.INVALID_INPUT, errorMessage: error instanceof Error ? error.message : String(error) });
357
+ throw error;
358
+ }
359
+ }
360
+ hsConfig(scope) {
361
+ return resolveTradeComplianceConfig(this.configResolver, scope.organizationId).hsBianma;
362
+ }
181
363
  };
182
364
  TradeComplianceWorkbenchViewProvider = __decorate([
183
365
  Injectable(),
184
366
  ViewExtensionProvider(VIEW_PROVIDER_KEY),
185
- __metadata("design:paramtypes", [TradeComplianceWorkbenchService, SalesFileService, TradeComplianceFileStorage])
367
+ __param(3, Optional()),
368
+ __param(4, Optional()),
369
+ __param(4, Inject(PLUGIN_CONFIG_RESOLVER_TOKEN)),
370
+ __metadata("design:paramtypes", [TradeComplianceWorkbenchService,
371
+ SalesFileService,
372
+ TradeComplianceFileStorage,
373
+ TradeComplianceDemoDataService, Object])
186
374
  ], TradeComplianceWorkbenchViewProvider);
187
375
  export { TradeComplianceWorkbenchViewProvider };
188
376
  function actionManifests() {
189
377
  const invoke = (key, label, icon = 'ri-edit-line') => ({ key, label: text(key, label), icon, actionType: 'invoke' });
190
378
  const upload = (key, label) => ({ ...invoke(key, label, 'ri-upload-cloud-line'), transport: 'file', placement: 'toolbar' });
191
- return [invoke('refresh', '刷新', 'ri-refresh-line'), upload('upload_controlled_catalog', '上传管控目录'), upload('upload_sanction_catalog', '上传制裁目录'), upload('upload_purchase_contract', '上传采购合同'), upload('upload_customer_contract', '上传客户合同'),
192
- ...['save_controlled_goods', 'update_controlled_goods', 'save_sanctioned_company', 'update_sanctioned_company', 'save_purchase_review', 'save_customer_contract_review', 'update_supplier', 'update_purchase_order', 'add_purchase_line', 'update_purchase_line', 'update_customer', 'save_customer_profit_rate', 'save_derived_manual_data', 'get_customer_contract_review', 'update_customer_contract_review', 'delete_entity', 'save_settings', 'generate_sales_file', 'download_sales_file', 'download_import_issues', 'download_source_file', 'save_edited_sales_file', 'search_hs_code', 'get_hs_code_detail'].map(key => invoke(key, key))];
379
+ const fileInvoke = (key, label) => ({ ...invoke(key, label, 'ri-upload-cloud-line'), transport: 'file' });
380
+ return [invoke('refresh', '刷新', 'ri-refresh-line'), upload('upload_controlled_catalog', '上传管控目录'), upload('upload_sanction_catalog', '上传制裁目录'), upload('upload_purchase_contract', '上传采购合同'), upload('upload_customer_contract', '上传客户合同'), fileInvoke('upload_company_logo', '上传公司 Logo'),
381
+ { ...invoke('seed_demo_data', 'seed_demo_data'), permissions: [PermissionsEnum.ORG_DEMO_EDIT] },
382
+ ...['save_controlled_goods', 'update_controlled_goods', 'save_sanctioned_company', 'update_sanctioned_company', 'retry_import_task', 'mark_import_command_dispatched', 'save_purchase_review', 'save_customer_contract_review', 'update_supplier', 'update_purchase_order', 'add_purchase_line', 'update_purchase_line', 'update_customer', 'save_customer_profit_rate', 'save_derived_manual_data', 'get_customer_contract_review', 'update_customer_contract_review', 'reanalyze_customer_contract', 'mark_customer_contract_reanalysis_dispatched', 'mark_customer_contract_reanalysis_failed', 'delete_entity', 'save_settings', 'restore_settings_default', 'delete_settings_rate', 'discard_company_logo', 'export_analytics', 'preflight_sales_file', 'generate_sales_file', 'download_sales_file', 'download_import_issues', 'download_source_file', 'preview_source_file', 'save_edited_sales_file', 'search_hs_code', 'get_hs_code_detail'].map(key => invoke(key, key))];
193
383
  }
194
384
  function buildAssistantCommand({ actionKey, file, input, task, extracted, catalogType, replaceBatchId }) {
195
385
  const toolName = actionKey === 'upload_controlled_catalog' ? TOOL_NAMES.saveControlledCatalog : actionKey === 'upload_sanction_catalog' ? TOOL_NAMES.saveSanctionCatalog : actionKey === 'upload_purchase_contract' ? TOOL_NAMES.savePurchaseDraft : TOOL_NAMES.saveCustomerContractDraft;
@@ -198,17 +388,179 @@ function buildAssistantCommand({ actionKey, file, input, task, extracted, catalo
198
388
  const fixed = JSON.stringify({ importTaskId: task.id, sourceFileId: task.sourceFileId, sourceFileName: task.sourceFileName, catalogType, replaceBatchId });
199
389
  return { commandKey: 'assistant.chat.send_message', payload: { text: `请解析上传的外贸合规文件。${requirements}\n解析完成后必须调用工具 ${toolName} 保存结果,以下固定参数必须原样传入:${fixed}\n统一使用中文说明,不要遗漏文件中的记录。${source}`, clientMessageId: `trade-compliance:${task.id}`, files: [assistantFile(file, input, task.sourceFileName)], followUpMode: 'queue', state: { tradeComplianceWorkbench: { actionKey, importTaskId: task.id } } } };
200
390
  }
201
- function assistantFile(file, input, fileName) { const workspace = record(input.workspaceFile); return { id: input.fileAssetId ?? input.fileId ?? file?.fileAssetId ?? file?.fileId, fileId: input.fileAssetId ?? input.fileId ?? file?.fileAssetId ?? file?.fileId, fileAssetId: input.fileAssetId ?? input.fileId ?? file?.fileAssetId ?? file?.fileId, workspacePath: workspace.workspacePath ?? input.workspacePath, filePath: workspace.filePath ?? input.filePath, name: fileName, originalName: fileName, mimeType: file?.mimetype ?? file?.type, size: file?.size, role: 'source_file' }; }
391
+ function buildCustomerContractReanalysisCommand(started) {
392
+ if (started.run.promptVersion !== 'CUSTOMER_CLAUSE_V1')
393
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '不支持的客户合同重新分析提示版本');
394
+ const fixed = JSON.stringify({ analysisRunId: started.run.id });
395
+ const clauses = JSON.stringify(started.clauses.map(clause => ({ clauseType: clause.clauseType, extractionStatus: clause.extractionStatus, reviewedText: clause.reviewedText, sourceText: clause.sourceText, sourceLocation: clause.sourceLocation, sourceContent: clause.sourceContent })));
396
+ return { commandKey: 'assistant.chat.send_message', payload: { text: `请重新分析客户合同七类关键条款。必须按 clauseType 返回风险等级、中文风险提示和修改建议,并调用工具 ${TOOL_NAMES.saveCustomerContractReanalysis} 保存结果。固定参数必须原样传入:${fixed}\n待分析条款:${clauses}`, clientMessageId: `trade-compliance:customer-reanalysis:${started.run.id}`, followUpMode: 'queue', state: { tradeComplianceWorkbench: { actionKey: 'reanalyze_customer_contract', analysisRunId: started.run.id } } } };
397
+ }
398
+ function assistantFile(file, input, fileName) {
399
+ const fileAssetId = input.fileAssetId ?? input.fileId ?? file?.fileAssetId ?? file?.fileId;
400
+ const storageFileId = input.storageFileId ?? file?.storageFileId;
401
+ return { id: fileAssetId, fileId: fileAssetId, fileAssetId, ...(storageFileId ? { storageFileId } : {}), name: fileName, originalName: fileName, mimeType: file?.mimetype ?? file?.type, size: file?.size, role: 'source_file' };
402
+ }
403
+ function platformFileId(file, input) { const value = input.fileAssetId ?? input.fileId ?? file?.fileAssetId ?? file?.fileId; return value ? String(value) : null; }
404
+ function executionInputForAction(actionKey, input) {
405
+ const typedActionKey = documentActionKey(actionKey);
406
+ const catalogType = input.catalogType === 'DUAL_USE' || input.catalogType === 'OVERSEAS_SANCTION' ? input.catalogType : undefined;
407
+ if (typedActionKey === 'upload_controlled_catalog' && !catalogType)
408
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '管控目录任务缺少目录类型');
409
+ return { actionKey: typedActionKey, ...(catalogType ? { catalogType } : {}), ...(input.replaceBatchId ? { replaceBatchId: String(input.replaceBatchId) } : {}) };
410
+ }
411
+ function documentActionKey(actionKey) { documentTypeForAction(actionKey); return actionKey; }
412
+ function catalogDocumentType(value) { if (value === 'CONTROLLED_CATALOG' || value === 'SANCTION_CATALOG')
413
+ return value; throw new DomainError(DomainErrorCodes.INVALID_INPUT, '不支持的目录文档类型'); }
414
+ function importIssueType(value) { if (value === 'DUPLICATE' || value === 'INVALID')
415
+ return value; throw new DomainError(DomainErrorCodes.INVALID_INPUT, '不支持的导入问题类型'); }
416
+ function customerContractReviewSource(value) { if (value === 'IMPORT_DRAFT' || value === 'EXISTING_CONTRACT')
417
+ return value; throw new DomainError(DomainErrorCodes.INVALID_INPUT, '不支持的客户合同审核来源'); }
418
+ function salesOrderKey(value) { const parts = value.split(':'); if (parts.length !== 2 || parts.some(part => !part))
419
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '销售订单标识无效'); return parts; }
420
+ function salesFileGenerationInput(input) {
421
+ if (!SalesFileTypes.includes(input.fileType))
422
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '销售文件类型无效');
423
+ const fileType = input.fileType;
424
+ const bankType = input.bankType == null ? undefined : parseBankAccountType(input.bankType);
425
+ return { customerContractId: requiredString(input.customerContractId, 'customerContractId'), purchaseOrderId: requiredString(input.purchaseOrderId, 'purchaseOrderId'), fileType, ...(bankType ? { bankType } : {}) };
426
+ }
427
+ function requiredNonNegativeInteger(value, field) { const parsed = Number(value); if (!Number.isInteger(parsed) || parsed < 0)
428
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${field} 必须是非负整数`); return parsed; }
202
429
  function documentTypeForAction(action) { const map = { upload_controlled_catalog: 'CONTROLLED_CATALOG', upload_sanction_catalog: 'SANCTION_CATALOG', upload_purchase_contract: 'PURCHASE_CONTRACT', upload_customer_contract: 'CUSTOMER_CONTRACT' }; const value = map[action]; if (!value)
203
430
  throw new DomainError(DomainErrorCodes.INVALID_INPUT, '不支持的上传操作', { action }); return value; }
204
431
  function formatOf(fileName) { const extension = fileName.split('.').pop()?.toUpperCase(); const normalized = extension === 'JPG' ? 'JPEG' : extension; return SourceFileFormats.includes(normalized) ? normalized : null; }
432
+ export function validateSourceFile(fileName, mimeType, buffer) {
433
+ const extensionFormat = formatOf(fileName);
434
+ const mimeFormat = formatForMimeType(mimeType);
435
+ const signatureFormat = formatForSignature(buffer);
436
+ if (!extensionFormat)
437
+ throw new DomainError(DomainErrorCodes.UNSUPPORTED_FILE_FORMAT, '仅支持 PDF、Word、Excel 和常见图片格式');
438
+ if (extensionFormat !== mimeFormat || extensionFormat !== signatureFormat) {
439
+ throw new DomainError(DomainErrorCodes.UNSUPPORTED_FILE_FORMAT, '文件扩展名、MIME 类型与文件签名不一致');
440
+ }
441
+ return extensionFormat;
442
+ }
443
+ function formatForMimeType(mimeType) {
444
+ const formats = {
445
+ 'application/pdf': 'PDF',
446
+ 'application/msword': 'DOC',
447
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'DOCX',
448
+ 'application/vnd.ms-excel': 'XLS',
449
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'XLSX',
450
+ 'image/png': 'PNG',
451
+ 'image/jpeg': 'JPEG',
452
+ 'image/webp': 'WEBP'
453
+ };
454
+ return formats[mimeType.trim().toLowerCase()] ?? null;
455
+ }
456
+ function formatForSignature(buffer) {
457
+ if (buffer.subarray(0, 5).equals(Buffer.from('%PDF-')))
458
+ return 'PDF';
459
+ if (buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])))
460
+ return 'PNG';
461
+ if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff)
462
+ return 'JPEG';
463
+ if (buffer.length >= 12 && buffer.subarray(0, 4).toString('ascii') === 'RIFF' && buffer.subarray(8, 12).toString('ascii') === 'WEBP')
464
+ return 'WEBP';
465
+ if (buffer.subarray(0, 8).equals(Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]))) {
466
+ let names;
467
+ try {
468
+ const container = XLSX.CFB.read(buffer, { type: 'buffer' });
469
+ names = container.FileIndex.filter((entry) => entry.type === 2).map((entry) => String(entry.name));
470
+ }
471
+ catch {
472
+ return null;
473
+ }
474
+ const isWord = names.includes('WordDocument');
475
+ const isExcel = names.includes('Workbook') || names.includes('Book');
476
+ if (isWord !== isExcel)
477
+ return isWord ? 'DOC' : 'XLS';
478
+ return null;
479
+ }
480
+ if (buffer.length >= 4 && [[0x03, 0x04], [0x05, 0x06], [0x07, 0x08]].some(([third, fourth]) => buffer[0] === 0x50 && buffer[1] === 0x4b && buffer[2] === third && buffer[3] === fourth)) {
481
+ let container;
482
+ try {
483
+ container = XLSX.CFB.read(buffer, { type: 'buffer' });
484
+ }
485
+ catch {
486
+ return null;
487
+ }
488
+ const entries = container.FileIndex
489
+ .map((entry, index) => entry.type === 2 ? packageEntryName(container.FullPaths[index]) : null)
490
+ .filter((name) => Boolean(name));
491
+ if (!entries.includes('[Content_Types].xml'))
492
+ return null;
493
+ const isWord = entries.some(name => name.startsWith('word/'));
494
+ const isExcel = entries.some(name => name.startsWith('xl/'));
495
+ if (isWord !== isExcel)
496
+ return isWord ? 'DOCX' : 'XLSX';
497
+ }
498
+ return null;
499
+ }
500
+ function packageEntryName(fullPath) { const value = String(fullPath ?? '').replace(/\\/g, '/'); const rootSeparator = value.indexOf('/'); return rootSeparator < 0 ? value : value.slice(rootSeparator + 1); }
501
+ function mimeTypeForFile(fileName) { const format = formatOf(fileName); return format === 'PDF' ? 'application/pdf' : format === 'PNG' ? 'image/png' : format === 'JPEG' ? 'image/jpeg' : format === 'WEBP' ? 'image/webp' : format === 'DOC' ? 'application/msword' : format === 'DOCX' ? 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' : format === 'XLS' ? 'application/vnd.ms-excel' : format === 'XLSX' ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' : 'application/octet-stream'; }
205
502
  function scopeFromContext(context) { if (!context.tenantId || !context.organizationId)
206
503
  throw new DomainError(DomainErrorCodes.INVALID_INPUT, '请求上下文缺少租户或组织标识'); return { tenantId: context.tenantId, organizationId: context.organizationId, userId: context.userId }; }
504
+ function parseDemoSeedInput(input) {
505
+ if (Object.keys(input).length !== 1 || input.confirmation !== 'INIT_DEMO_DATA') {
506
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '初始化演示数据必须提供精确确认值 INIT_DEMO_DATA');
507
+ }
508
+ return { confirmation: input.confirmation };
509
+ }
207
510
  function success(data) { return { success: true, message: text('Saved', '已保存'), data, refresh: true }; }
208
511
  function failure(error) { const domain = error instanceof DomainError ? error : new DomainError(DomainErrorCodes.INVALID_INPUT, error instanceof Error ? error.message : String(error)); return { success: false, message: text(domain.message, domain.message), error: { code: domain.code, message: domain.message, details: domain.details }, refresh: false }; }
209
512
  function record(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }
210
513
  function requiredString(value, field) { const result = String(value ?? '').trim(); if (!result)
211
514
  throw new DomainError(DomainErrorCodes.INVALID_INPUT, `缺少 ${field}`); return result; }
515
+ function customerContractReviewUpdate(value) {
516
+ const payload = objectInput(value, '客户合同审核载荷');
517
+ const contractInput = objectInput(payload.contract, '合同信息');
518
+ const contract = {
519
+ contractNo: typedRequiredString(contractInput.contractNo, '客户合同编码')
520
+ };
521
+ if ('signedAt' in contractInput)
522
+ contract.signedAt = nullableString(contractInput.signedAt, '签订日期');
523
+ if ('contractAmountReviewCny' in contractInput)
524
+ contract.contractAmountReviewCny = nullableDecimalString(contractInput.contractAmountReviewCny, '人民币预审合同金额');
525
+ const update = { contract };
526
+ if ('customer' in payload)
527
+ update.customer = objectInput(payload.customer, '客户信息');
528
+ if ('clauses' in payload) {
529
+ if (!Array.isArray(payload.clauses))
530
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, '合同条款必须是数组');
531
+ update.clauses = payload.clauses;
532
+ }
533
+ if ('customerTransferConfirmation' in payload)
534
+ update.customerTransferConfirmation = objectInput(payload.customerTransferConfirmation, '客户转移确认');
535
+ return { id: typedRequiredString(payload.id, '客户合同标识'), update };
536
+ }
537
+ function objectInput(value, label) {
538
+ if (!value || typeof value !== 'object' || Array.isArray(value))
539
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${label}必须是对象`);
540
+ return value;
541
+ }
542
+ function typedRequiredString(value, label) {
543
+ if (typeof value !== 'string')
544
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${label}必须是字符串`);
545
+ const result = value.trim();
546
+ if (!result)
547
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${label}不能为空`);
548
+ return result;
549
+ }
550
+ function nullableString(value, label) {
551
+ if (value == null)
552
+ return null;
553
+ if (typeof value !== 'string')
554
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${label}必须是字符串`);
555
+ return value.trim() || null;
556
+ }
557
+ function nullableDecimalString(value, label) {
558
+ if (value == null)
559
+ return null;
560
+ if (typeof value !== 'string' || !/^(?:0|[1-9]\d{0,15})(?:\.\d{1,4})?$/.test(value.trim()))
561
+ throw new DomainError(DomainErrorCodes.INVALID_INPUT, `${label}必须是最多四位小数的非负数字符串`);
562
+ return value.trim();
563
+ }
212
564
  function stringValue(value) { const result = String(value ?? '').trim(); return result || undefined; }
213
565
  function numberValue(value, fallback) { const result = Number(value); return Number.isFinite(result) ? result : fallback; }
214
566
  function fileNameOf(file, input) { return String(file?.originalname ?? file?.name ?? input.fileName ?? 'uploaded-file'); }
@@ -216,7 +568,26 @@ function bufferOf(file) { const value = file?.buffer; if (Buffer.isBuffer(value)
216
568
  return value; if (value instanceof ArrayBuffer)
217
569
  return Buffer.from(value); if (ArrayBuffer.isView(value))
218
570
  return Buffer.from(value.buffer, value.byteOffset, value.byteLength); return null; }
219
- async function bufferFromPath(input) { const workspace = record(input.workspaceFile); const path = stringValue(workspace.workspacePath ?? workspace.filePath ?? input.workspacePath ?? input.filePath); return path ? readFile(path) : null; }
220
571
  async function readPackageFile(packageName, filePath) { const root = dirname(requireFromHere.resolve(`${packageName}/package.json`)); return readFile(join(root, filePath), 'utf8'); }
221
- function hsConfig() { return { baseUrl: process.env.TRADE_COMPLIANCE_HS_BASE_URL, timeoutMs: Number(process.env.TRADE_COMPLIANCE_HS_TIMEOUT_MS || 15000) }; }
572
+ async function sourcePreview(fileName, buffer) {
573
+ const format = formatForSignature(buffer);
574
+ if (format === 'PDF' || format === 'PNG' || format === 'JPEG' || format === 'WEBP') {
575
+ return { previewKind: 'URL', fileName, mimeType: mimeTypeForFormat(format), base64: buffer.toString('base64') };
576
+ }
577
+ if (format === 'DOC' || format === 'DOCX' || format === 'XLS' || format === 'XLSX') {
578
+ const extracted = await extractDocumentText({ buffer, fileName, mimeType: mimeTypeForFormat(format), maxChars: 100000 });
579
+ const content = (extracted.chunks?.join('\n\n') ?? extracted.text ?? '').trim();
580
+ if (!content)
581
+ return { previewKind: 'UNAVAILABLE', fileName, message: '该 Office 文件未提取到可预览文本' };
582
+ return { previewKind: 'TEXT', fileName, content };
583
+ }
584
+ return { previewKind: 'UNAVAILABLE', fileName, message: '该源文件暂无法在线预览' };
585
+ }
586
+ function mimeTypeForFormat(format) {
587
+ const values = {
588
+ PDF: 'application/pdf', DOC: 'application/msword', DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', XLS: 'application/vnd.ms-excel', XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', PNG: 'image/png', JPEG: 'image/jpeg', WEBP: 'image/webp'
589
+ };
590
+ return values[format];
591
+ }
592
+ export function sanitizeIframeCss(css) { return css.replace(/<\/style/gi, (match) => `<\\/${match.slice(2)}`); }
222
593
  //# sourceMappingURL=workbench-view.provider.js.map