@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.
- package/README.md +46 -1
- package/dist/assets/branding/yuneec-logo.png +0 -0
- package/dist/assets/templates/CI.xlsx +0 -0
- package/dist/assets/templates/CUSTOMS_DECLARATION.xlsx +0 -0
- package/dist/assets/templates/PI.xlsx +0 -0
- package/dist/assets/templates/PL.xlsx +0 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/adapters/file-storage.d.ts +4 -2
- package/dist/lib/adapters/file-storage.d.ts.map +1 -1
- package/dist/lib/adapters/file-storage.js +25 -10
- package/dist/lib/adapters/file-storage.js.map +1 -1
- package/dist/lib/adapters/hsbianma.provider.d.ts +1 -0
- package/dist/lib/adapters/hsbianma.provider.d.ts.map +1 -1
- package/dist/lib/adapters/hsbianma.provider.js +32 -3
- package/dist/lib/adapters/hsbianma.provider.js.map +1 -1
- package/dist/lib/adapters/translation.provider.d.ts +3 -0
- package/dist/lib/adapters/translation.provider.d.ts.map +1 -1
- package/dist/lib/adapters/translation.provider.js +15 -6
- package/dist/lib/adapters/translation.provider.js.map +1 -1
- package/dist/lib/constants.d.ts +3 -2
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +3 -2
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/demo-data.definitions.d.ts +28 -0
- package/dist/lib/demo-data.definitions.d.ts.map +1 -0
- package/dist/lib/demo-data.definitions.js +236 -0
- package/dist/lib/demo-data.definitions.js.map +1 -0
- package/dist/lib/demo-data.entity.d.ts +9 -0
- package/dist/lib/demo-data.entity.d.ts.map +1 -0
- package/dist/lib/demo-data.entity.js +24 -0
- package/dist/lib/demo-data.entity.js.map +1 -0
- package/dist/lib/demo-data.ids.d.ts +4 -0
- package/dist/lib/demo-data.ids.d.ts.map +1 -0
- package/dist/lib/demo-data.ids.js +19 -0
- package/dist/lib/demo-data.ids.js.map +1 -0
- package/dist/lib/demo-data.service.d.ts +26 -0
- package/dist/lib/demo-data.service.d.ts.map +1 -0
- package/dist/lib/demo-data.service.js +124 -0
- package/dist/lib/demo-data.service.js.map +1 -0
- package/dist/lib/domain/default-settings.d.ts +1 -0
- package/dist/lib/domain/default-settings.d.ts.map +1 -1
- package/dist/lib/domain/default-settings.js +1 -0
- package/dist/lib/domain/default-settings.js.map +1 -1
- package/dist/lib/domain/risk-notice.d.ts +59 -0
- package/dist/lib/domain/risk-notice.d.ts.map +1 -0
- package/dist/lib/domain/risk-notice.js +30 -0
- package/dist/lib/domain/risk-notice.js.map +1 -0
- package/dist/lib/entities/catalog.entity.d.ts.map +1 -1
- package/dist/lib/entities/catalog.entity.js +4 -2
- package/dist/lib/entities/catalog.entity.js.map +1 -1
- package/dist/lib/entities/customer.entity.d.ts +9 -2
- package/dist/lib/entities/customer.entity.d.ts.map +1 -1
- package/dist/lib/entities/customer.entity.js +29 -1
- package/dist/lib/entities/customer.entity.js.map +1 -1
- package/dist/lib/entities/index.d.ts +2 -2
- package/dist/lib/entities/index.d.ts.map +1 -1
- package/dist/lib/entities/index.js +2 -2
- package/dist/lib/entities/index.js.map +1 -1
- package/dist/lib/entities/procurement.entity.d.ts +6 -4
- package/dist/lib/entities/procurement.entity.d.ts.map +1 -1
- package/dist/lib/entities/procurement.entity.js +9 -1
- package/dist/lib/entities/procurement.entity.js.map +1 -1
- package/dist/lib/entities/settings.entity.d.ts +12 -2
- package/dist/lib/entities/settings.entity.d.ts.map +1 -1
- package/dist/lib/entities/settings.entity.js +40 -0
- package/dist/lib/entities/settings.entity.js.map +1 -1
- package/dist/lib/entities/shared.entity.d.ts +10 -5
- package/dist/lib/entities/shared.entity.d.ts.map +1 -1
- package/dist/lib/entities/shared.entity.js +22 -1
- package/dist/lib/entities/shared.entity.js.map +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -0
- package/dist/lib/remote-components/trade-compliance-workbench/app.js +63 -277
- package/dist/lib/remote-ui/analytics-contracts.d.ts +106 -0
- package/dist/lib/remote-ui/analytics-contracts.d.ts.map +1 -0
- package/dist/lib/remote-ui/analytics-contracts.js +4 -0
- package/dist/lib/remote-ui/analytics-contracts.js.map +1 -0
- package/dist/lib/remote-ui/app-source.d.ts +2 -0
- package/dist/lib/remote-ui/app-source.d.ts.map +1 -0
- package/dist/lib/remote-ui/app-source.js +753 -0
- package/dist/lib/remote-ui/app-source.js.map +1 -0
- package/dist/lib/remote-ui/bridge.d.ts +36 -0
- package/dist/lib/remote-ui/bridge.d.ts.map +1 -0
- package/dist/lib/remote-ui/bridge.js +159 -0
- package/dist/lib/remote-ui/bridge.js.map +1 -0
- package/dist/lib/remote-ui/components/data-grid.d.ts +34 -0
- package/dist/lib/remote-ui/components/data-grid.d.ts.map +1 -0
- package/dist/lib/remote-ui/components/data-grid.js +59 -0
- package/dist/lib/remote-ui/components/data-grid.js.map +1 -0
- package/dist/lib/remote-ui/components/forms.d.ts +63 -0
- package/dist/lib/remote-ui/components/forms.d.ts.map +1 -0
- package/dist/lib/remote-ui/components/forms.js +117 -0
- package/dist/lib/remote-ui/components/forms.js.map +1 -0
- package/dist/lib/remote-ui/components/layout.d.ts +34 -0
- package/dist/lib/remote-ui/components/layout.d.ts.map +1 -0
- package/dist/lib/remote-ui/components/layout.js +89 -0
- package/dist/lib/remote-ui/components/layout.js.map +1 -0
- package/dist/lib/remote-ui/contract-import.d.ts +82 -0
- package/dist/lib/remote-ui/contract-import.d.ts.map +1 -0
- package/dist/lib/remote-ui/contract-import.js +102 -0
- package/dist/lib/remote-ui/contract-import.js.map +1 -0
- package/dist/lib/remote-ui/contracts.d.ts +257 -0
- package/dist/lib/remote-ui/contracts.d.ts.map +1 -0
- package/dist/lib/remote-ui/contracts.js +12 -0
- package/dist/lib/remote-ui/contracts.js.map +1 -0
- package/dist/lib/remote-ui/customer-contract-contracts.d.ts +227 -0
- package/dist/lib/remote-ui/customer-contract-contracts.d.ts.map +1 -0
- package/dist/lib/remote-ui/customer-contract-contracts.js +10 -0
- package/dist/lib/remote-ui/customer-contract-contracts.js.map +1 -0
- package/dist/lib/remote-ui/model.d.ts +31 -0
- package/dist/lib/remote-ui/model.d.ts.map +1 -0
- package/dist/lib/remote-ui/model.js +248 -0
- package/dist/lib/remote-ui/model.js.map +1 -0
- package/dist/lib/remote-ui/pages/analytics.d.ts +12 -0
- package/dist/lib/remote-ui/pages/analytics.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/analytics.js +123 -0
- package/dist/lib/remote-ui/pages/analytics.js.map +1 -0
- package/dist/lib/remote-ui/pages/catalogs.d.ts +225 -0
- package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/catalogs.js +572 -0
- package/dist/lib/remote-ui/pages/catalogs.js.map +1 -0
- package/dist/lib/remote-ui/pages/contracts.d.ts +80 -0
- package/dist/lib/remote-ui/pages/contracts.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/contracts.js +396 -0
- package/dist/lib/remote-ui/pages/contracts.js.map +1 -0
- package/dist/lib/remote-ui/pages/files.d.ts +31 -0
- package/dist/lib/remote-ui/pages/files.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/files.js +64 -0
- package/dist/lib/remote-ui/pages/files.js.map +1 -0
- package/dist/lib/remote-ui/pages/home.d.ts +71 -0
- package/dist/lib/remote-ui/pages/home.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/home.js +136 -0
- package/dist/lib/remote-ui/pages/home.js.map +1 -0
- package/dist/lib/remote-ui/pages/procurement.d.ts +36 -0
- package/dist/lib/remote-ui/pages/procurement.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/procurement.js +370 -0
- package/dist/lib/remote-ui/pages/procurement.js.map +1 -0
- package/dist/lib/remote-ui/pages/sales.d.ts +12 -0
- package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/sales.js +106 -0
- package/dist/lib/remote-ui/pages/sales.js.map +1 -0
- package/dist/lib/remote-ui/pages/settings.d.ts +34 -0
- package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/settings.js +259 -0
- package/dist/lib/remote-ui/pages/settings.js.map +1 -0
- package/dist/lib/remote-ui/procurement-contracts.d.ts +177 -0
- package/dist/lib/remote-ui/procurement-contracts.d.ts.map +1 -0
- package/dist/lib/remote-ui/procurement-contracts.js +2 -0
- package/dist/lib/remote-ui/procurement-contracts.js.map +1 -0
- package/dist/lib/remote-ui/sales-contracts.d.ts +133 -0
- package/dist/lib/remote-ui/sales-contracts.d.ts.map +1 -0
- package/dist/lib/remote-ui/sales-contracts.js +2 -0
- package/dist/lib/remote-ui/sales-contracts.js.map +1 -0
- package/dist/lib/remote-ui/settings-contracts.d.ts +114 -0
- package/dist/lib/remote-ui/settings-contracts.d.ts.map +1 -0
- package/dist/lib/remote-ui/settings-contracts.js +6 -0
- package/dist/lib/remote-ui/settings-contracts.js.map +1 -0
- package/dist/lib/remote-ui/upload-policy.d.ts +18 -0
- package/dist/lib/remote-ui/upload-policy.d.ts.map +1 -0
- package/dist/lib/remote-ui/upload-policy.js +56 -0
- package/dist/lib/remote-ui/upload-policy.js.map +1 -0
- package/dist/lib/runtime-validation.d.ts +61 -0
- package/dist/lib/runtime-validation.d.ts.map +1 -0
- package/dist/lib/runtime-validation.js +243 -0
- package/dist/lib/runtime-validation.js.map +1 -0
- package/dist/lib/sales-file.service.d.ts +54 -6
- package/dist/lib/sales-file.service.d.ts.map +1 -1
- package/dist/lib/sales-file.service.js +278 -33
- package/dist/lib/sales-file.service.js.map +1 -1
- package/dist/lib/trade-compliance.config.d.ts +32 -0
- package/dist/lib/trade-compliance.config.d.ts.map +1 -1
- package/dist/lib/trade-compliance.config.js +16 -4
- package/dist/lib/trade-compliance.config.js.map +1 -1
- package/dist/lib/types.d.ts +253 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +24 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/workbench-view.provider.d.ts +675 -38
- package/dist/lib/workbench-view.provider.d.ts.map +1 -1
- package/dist/lib/workbench-view.provider.js +411 -40
- package/dist/lib/workbench-view.provider.js.map +1 -1
- package/dist/lib/workbench.middleware.d.ts +70 -19
- package/dist/lib/workbench.middleware.d.ts.map +1 -1
- package/dist/lib/workbench.middleware.js +5 -1
- package/dist/lib/workbench.middleware.js.map +1 -1
- package/dist/lib/workbench.plugin.d.ts.map +1 -1
- package/dist/lib/workbench.plugin.js +6 -4
- package/dist/lib/workbench.plugin.js.map +1 -1
- package/dist/lib/workbench.service.d.ts +629 -48
- package/dist/lib/workbench.service.d.ts.map +1 -1
- package/dist/lib/workbench.service.js +1818 -300
- package/dist/lib/workbench.service.js.map +1 -1
- package/package.json +15 -4
- 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":"
|
|
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 =
|
|
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
|
|
53
|
-
const
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
131
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
|
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
|
|
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
|