@chenchaolong/plugin-trade-compliance-workbench 1.0.172 → 1.0.174
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/analytics-query.d.ts +1 -0
- package/dist/lib/analytics-query.d.ts.map +1 -1
- package/dist/lib/analytics-query.js +1 -1
- package/dist/lib/analytics-query.js.map +1 -1
- package/dist/lib/domain/sales-line-order.d.ts +4 -0
- package/dist/lib/domain/sales-line-order.d.ts.map +1 -0
- package/dist/lib/domain/sales-line-order.js +18 -0
- package/dist/lib/domain/sales-line-order.js.map +1 -0
- package/dist/lib/entities/customer.entity.d.ts +3 -0
- package/dist/lib/entities/customer.entity.d.ts.map +1 -1
- package/dist/lib/entities/customer.entity.js +12 -0
- package/dist/lib/entities/customer.entity.js.map +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
- package/dist/lib/remote-ui/action-execution.d.ts.map +1 -1
- package/dist/lib/remote-ui/action-execution.js +2 -0
- package/dist/lib/remote-ui/action-execution.js.map +1 -1
- package/dist/lib/remote-ui/components/data-grid.d.ts +2 -1
- package/dist/lib/remote-ui/components/data-grid.d.ts.map +1 -1
- package/dist/lib/remote-ui/components/data-grid.js +2 -2
- package/dist/lib/remote-ui/components/data-grid.js.map +1 -1
- package/dist/lib/remote-ui/pages/sales-line-order.css +16 -0
- package/dist/lib/remote-ui/pages/sales-line-order.d.ts +12 -0
- package/dist/lib/remote-ui/pages/sales-line-order.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/sales-line-order.js +66 -0
- package/dist/lib/remote-ui/pages/sales-line-order.js.map +1 -0
- package/dist/lib/remote-ui/pages/sales.d.ts +11 -1
- package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/sales.js +43 -4
- package/dist/lib/remote-ui/pages/sales.js.map +1 -1
- package/dist/lib/remote-ui/sales-contracts.d.ts +14 -1
- package/dist/lib/remote-ui/sales-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/sales-contracts.js.map +1 -1
- package/dist/lib/remote-ui/styles.css +6 -0
- package/dist/lib/sales-file.service.d.ts.map +1 -1
- package/dist/lib/sales-file.service.js +23 -3
- package/dist/lib/sales-file.service.js.map +1 -1
- package/dist/lib/schema-maintenance.d.ts.map +1 -1
- package/dist/lib/schema-maintenance.js +5 -0
- package/dist/lib/schema-maintenance.js.map +1 -1
- package/dist/lib/types.d.ts +9 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/workbench-action-contract.d.ts +1 -1
- package/dist/lib/workbench-action-contract.d.ts.map +1 -1
- package/dist/lib/workbench-action-contract.js +2 -0
- package/dist/lib/workbench-action-contract.js.map +1 -1
- package/dist/lib/workbench-view.provider.d.ts +22 -0
- package/dist/lib/workbench-view.provider.d.ts.map +1 -1
- package/dist/lib/workbench-view.provider.js +7 -1
- package/dist/lib/workbench-view.provider.js.map +1 -1
- package/dist/lib/workbench.service.d.ts +46 -0
- package/dist/lib/workbench.service.d.ts.map +1 -1
- package/dist/lib/workbench.service.js +122 -7
- package/dist/lib/workbench.service.js.map +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
13
13
|
import { BankAccountSettings, CatalogReviewBlock, CompanySanctionMatch, CompanySettings, ControlledCatalogBatch, ControlledCatalogImportIssue, ControlledGoodsRecord, Customer, CustomerContract, CustomerContractAnalysisRun, CustomerContractClause, CustomerContractClauseAnalysis, DerivedSalesOrderManualData, GeneratedSalesFile, HsCodeReferenceSnapshot, ImportTask, ManagedAsset, MonthlySafeExchangeRate, ProductComplianceMatch, ProfitRuleSettings, PurchaseOrder, PurchaseOrderLine, SanctionCatalogBatch, SanctionCatalogImportIssue, SanctionedCompanyAlias, SanctionedCompanyRecord, SettingsHistory, Supplier } from './entities/index.js';
|
|
14
14
|
import { CATALOG_REVIEW_BATCH_LIMIT, CATALOG_REVIEW_LEASE_MS, CATALOG_TRANSLATION_REVIEW_LEASE_MS } from './constants.js';
|
|
15
15
|
import { CatalogTypes, ClauseTypes, CustomerProfitRateStatuses, DomainError, DomainErrorCodes, RefundValidationMethods, RiskLevels, SalesPaymentStatuses, SanctionHitStatuses, StalledCatalogImportDecisions } from './types.js';
|
|
16
|
+
import { resolveSalesLineOrder } from './domain/sales-line-order.js';
|
|
16
17
|
import { deriveSalesLine, assertReceivedAmount, calculateSalesLineFromRate } from './domain/finance.js';
|
|
17
18
|
import { assertYcNumberContractNo, parseCustomerContractNo, parsePurchaseContractNo } from './domain/contract-number.js';
|
|
18
19
|
import { isProductNameMatch, normalizeExactName, normalizeForProductMatch, normalizeHsCode, normalizeSanctionSource } from './domain/normalization.js';
|
|
@@ -26,7 +27,7 @@ import { parseBankAccountType, parseExchangeRateInput, parseExpectedRevision, va
|
|
|
26
27
|
import { publicCustomerContractReanalysisDto, publicImportTaskDto } from './public-dto.js';
|
|
27
28
|
import { classifyCandidate, normalizeCatalogCandidate } from './catalog-import/confidence.js';
|
|
28
29
|
import { catalogTerminalOutcome } from './catalog-import/outcome.js';
|
|
29
|
-
import { analyticsBusinessDetailFromRaw, analyticsDashboardFromRaw, analyticsDashboardStatement, analyticsDetailStatement, analyticsDetailTotalFromRaw, analyticsExportComplianceFromRaw, analyticsExportCustomersFromRaw, analyticsExportFundRisksFromRaw, analyticsExportSalesFileReviewsFromRaw, analyticsExportStatements, analyticsExportSuppliersFromRaw, resolveAnalyticsPeriod } from './analytics-query.js';
|
|
30
|
+
import { analyticsBusinessDetailFromRaw, analyticsDashboardFromRaw, analyticsDashboardStatement, analyticsDetailStatement, analyticsDetailTotalFromRaw, analyticsExportComplianceFromRaw, analyticsExportCustomersFromRaw, analyticsExportFundRisksFromRaw, analyticsExportSalesFileReviewsFromRaw, analyticsExportStatements, analyticsExportSuppliersFromRaw, analyticsSalesLineAmountSql, resolveAnalyticsPeriod } from './analytics-query.js';
|
|
30
31
|
import { analyticsExportMaxRows, renderAnalyticsExportWorkbook } from './analytics-export.js';
|
|
31
32
|
import { formatBeijingDateTime } from './remote-ui/date-time.js';
|
|
32
33
|
import { TradeComplianceSchemaMaintenance } from './schema-maintenance.js';
|
|
@@ -2931,7 +2932,68 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
2931
2932
|
qb.andWhere("row.complianceCheckStatus = 'UNCHECKED'");
|
|
2932
2933
|
}
|
|
2933
2934
|
});
|
|
2934
|
-
return { ...page, items: await this.salesCustomerDtos(scope, page.items) };
|
|
2935
|
+
return { ...page, items: await this.salesCustomerDtos(scope, page.items, true, true) };
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* 客户级未收款总额(CNY):派生销售订单(客户合同组)各自 clamp 后汇总,超收不抵扣其他订单欠款。
|
|
2939
|
+
* 口径与销售订单列表、分析看板一致:订单总额 = 人工覆盖值或商品行汇总,未收款 = max(总额 - 已收款, 0)。
|
|
2940
|
+
*/
|
|
2941
|
+
async customerUnreceivedAmounts(scope, customerIds) {
|
|
2942
|
+
const map = new Map();
|
|
2943
|
+
const ids = [...new Set(customerIds)].filter(Boolean);
|
|
2944
|
+
if (!ids.length)
|
|
2945
|
+
return map;
|
|
2946
|
+
const parameters = [];
|
|
2947
|
+
const parameter = (value) => { parameters.push(value); return `$${parameters.length}`; };
|
|
2948
|
+
const tenant = parameter(scope.tenantId);
|
|
2949
|
+
const organization = parameter(scope.organizationId);
|
|
2950
|
+
const customerIdsSql = ids.map(id => parameter(id)).join(', ');
|
|
2951
|
+
const lineSalesAmount = analyticsSalesLineAmountSql('line', 'sales_pairs."linePricingOverrides"', 'sales_pairs."effectivePricingProfitRate"');
|
|
2952
|
+
const sql = `WITH sales_pairs AS (
|
|
2953
|
+
SELECT contract."customerId", contract.id AS "customerContractId", purchase.id AS "purchaseOrderId",
|
|
2954
|
+
COALESCE(manual."receivedAmountCny", 0) AS "receivedCny",
|
|
2955
|
+
manual."orderAmountCnyOverride" AS "orderAmountCnyOverride",
|
|
2956
|
+
manual."linePricingOverrides" AS "linePricingOverrides",
|
|
2957
|
+
COALESCE(manual."pricingProfitRateOverride", contract_customer."defaultPricingProfitRate") AS "effectivePricingProfitRate"
|
|
2958
|
+
FROM plugin_trade_compliance_customer_contract contract
|
|
2959
|
+
INNER JOIN plugin_trade_compliance_customer contract_customer ON contract_customer.id = contract."customerId" AND contract_customer."tenantId" = contract."tenantId" AND contract_customer."organizationId" = contract."organizationId" AND contract_customer."deletedAt" IS NULL
|
|
2960
|
+
INNER JOIN plugin_trade_compliance_purchase_order purchase ON purchase."tenantId" = contract."tenantId" AND purchase."organizationId" = contract."organizationId" AND purchase."deletedAt" IS NULL AND ${derivedSalesContractLinkSql('contract', 'purchase')}
|
|
2961
|
+
LEFT JOIN plugin_trade_compliance_derived_sales_manual manual ON manual."customerContractId" = contract.id AND manual."purchaseOrderId" = purchase.id AND manual."tenantId" = contract."tenantId" AND manual."organizationId" = contract."organizationId" AND manual."deletedAt" IS NULL
|
|
2962
|
+
WHERE contract."tenantId" = ${tenant} AND contract."organizationId" = ${organization} AND contract."deletedAt" IS NULL AND contract."customerId" IN (${customerIdsSql})
|
|
2963
|
+
), sales_line_totals AS (
|
|
2964
|
+
SELECT sales_pairs."customerId", sales_pairs."customerContractId", sales_pairs."purchaseOrderId", sales_pairs."receivedCny",
|
|
2965
|
+
COALESCE(sales_pairs."orderAmountCnyOverride", COALESCE(SUM(${lineSalesAmount}), 0)) AS "salesTotalCny"
|
|
2966
|
+
FROM sales_pairs
|
|
2967
|
+
LEFT JOIN plugin_trade_compliance_purchase_order_line line ON line."purchaseOrderId" = sales_pairs."purchaseOrderId" AND line."tenantId" = ${tenant} AND line."organizationId" = ${organization} AND line."deletedAt" IS NULL
|
|
2968
|
+
GROUP BY sales_pairs."customerId", sales_pairs."customerContractId", sales_pairs."purchaseOrderId", sales_pairs."receivedCny", sales_pairs."orderAmountCnyOverride"
|
|
2969
|
+
), sales_contract_totals AS (
|
|
2970
|
+
SELECT sales_line_totals."customerId", sales_line_totals."customerContractId",
|
|
2971
|
+
SUM(sales_line_totals."salesTotalCny") AS "salesTotalCny",
|
|
2972
|
+
SUM(sales_line_totals."receivedCny") AS "receivedCny"
|
|
2973
|
+
FROM sales_line_totals
|
|
2974
|
+
GROUP BY sales_line_totals."customerId", sales_line_totals."customerContractId"
|
|
2975
|
+
)
|
|
2976
|
+
SELECT sales_contract_totals."customerId" AS "customerId",
|
|
2977
|
+
SUM(GREATEST(sales_contract_totals."salesTotalCny" - sales_contract_totals."receivedCny", 0))::text AS "unreceivedCny"
|
|
2978
|
+
FROM sales_contract_totals
|
|
2979
|
+
GROUP BY sales_contract_totals."customerId"`;
|
|
2980
|
+
const rows = await this.dataSource.query(sql, parameters);
|
|
2981
|
+
for (const row of rows)
|
|
2982
|
+
if (row?.customerId)
|
|
2983
|
+
map.set(row.customerId, new Decimal(row.unreceivedCny ?? 0).toFixed(2));
|
|
2984
|
+
return map;
|
|
2985
|
+
}
|
|
2986
|
+
async saveCustomerCreditLimit(scope, customerId, limit) {
|
|
2987
|
+
const normalizedLimit = normalizeOptionalDecimalInput(limit);
|
|
2988
|
+
if (normalizedLimit != null && new Decimal(normalizedLimit).isNegative())
|
|
2989
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '客户信用额度不能小于 0');
|
|
2990
|
+
return this.dataSource.transaction(async (manager) => {
|
|
2991
|
+
const repo = manager.getRepository(Customer);
|
|
2992
|
+
const customer = await required(repo.findOne({ where: { ...activeScope(scope), id: customerId, deletedAt: IsNull() }, lock: { mode: 'pessimistic_write' } }), '客户不存在');
|
|
2993
|
+
customer.creditLimit = normalizedLimit;
|
|
2994
|
+
customer.updatedById = scope.userId ?? null;
|
|
2995
|
+
return repo.save(customer);
|
|
2996
|
+
});
|
|
2935
2997
|
}
|
|
2936
2998
|
async listSalesOrders(scope, customerId, query = {}) {
|
|
2937
2999
|
assertOptionalEnum(query.parameters?.paymentStatus, SalesPaymentStatuses, '收款状态');
|
|
@@ -2954,6 +3016,48 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
2954
3016
|
const filtered = order.lines.filter(line => fuzzyValue(line.productName, parameters.productName) && fuzzyValue(line.productDescription, parameters.productDescription) && fuzzyValue(line.hsCode, parameters.hsCode) && (!hasQueryValue(parameters.complianceCategory) || line.complianceCategories.includes(parameters.complianceCategory)));
|
|
2955
3017
|
return { ...pageRows(filtered, query), order };
|
|
2956
3018
|
}
|
|
3019
|
+
async saveSalesLineOrder(scope, customerContractId, input) {
|
|
3020
|
+
const order = (await this.listDerivedSalesOrders(scope, undefined, [{ customerContractId, purchaseOrderId: '' }])).find(item => item.customerContractId === customerContractId);
|
|
3021
|
+
if (!order)
|
|
3022
|
+
throw new DomainError(DomainErrorCodes.NOT_FOUND, '销售订单不存在');
|
|
3023
|
+
const currentIds = new Set(order.lines.map(line => line.purchaseOrderLineId));
|
|
3024
|
+
const isCompleteList = (value) => Array.isArray(value) && value.length === currentIds.size && new Set(value).size === value.length && value.every(id => typeof id === 'string' && currentIds.has(id));
|
|
3025
|
+
if (!isCompleteList(input.expectedLineIds))
|
|
3026
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '商品明细已变化,请关闭排序窗口并刷新后重试');
|
|
3027
|
+
if (input.lineIds !== null && !isCompleteList(input.lineIds))
|
|
3028
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '排序必须包含当前订单的全部商品且不能重复');
|
|
3029
|
+
if (typeof input.expectedVersion !== 'number' || !Number.isSafeInteger(input.expectedVersion) || input.expectedVersion < 0 || input.expectedVersion !== order.salesLineOrderVersion)
|
|
3030
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '商品顺序已变化,请关闭排序窗口并刷新后重试');
|
|
3031
|
+
const version = input.expectedVersion + 1;
|
|
3032
|
+
// Recheck membership in the same statement as the version CAS: a source edit
|
|
3033
|
+
// committed after derivation must not silently save an obsolete list.
|
|
3034
|
+
const result = await this.dataSource.getRepository(CustomerContract).createQueryBuilder()
|
|
3035
|
+
.update(CustomerContract)
|
|
3036
|
+
.set({ salesLineOrder: input.lineIds, salesLineOrderVersion: version, updatedById: scope.userId ?? null })
|
|
3037
|
+
.where({ ...activeScope(scope), id: customerContractId, deletedAt: IsNull(), salesLineOrderVersion: input.expectedVersion })
|
|
3038
|
+
.andWhere(`ARRAY(
|
|
3039
|
+
SELECT membership_line.id::text
|
|
3040
|
+
FROM plugin_trade_compliance_purchase_order_line membership_line
|
|
3041
|
+
INNER JOIN plugin_trade_compliance_purchase_order membership_purchase
|
|
3042
|
+
ON membership_purchase.id = membership_line."purchaseOrderId"
|
|
3043
|
+
AND membership_purchase."tenantId" = membership_line."tenantId"
|
|
3044
|
+
AND membership_purchase."organizationId" = membership_line."organizationId"
|
|
3045
|
+
AND membership_purchase."deletedAt" IS NULL
|
|
3046
|
+
WHERE membership_line."tenantId" = :membershipTenantId
|
|
3047
|
+
AND membership_line."organizationId" = :membershipOrganizationId
|
|
3048
|
+
AND membership_line."deletedAt" IS NULL
|
|
3049
|
+
AND ${derivedSalesContractLinkSql('plugin_trade_compliance_customer_contract', 'membership_purchase')}
|
|
3050
|
+
ORDER BY membership_line.id::text
|
|
3051
|
+
) = CAST(:expectedSalesLineIds AS text[])`, {
|
|
3052
|
+
membershipTenantId: scope.tenantId,
|
|
3053
|
+
membershipOrganizationId: scope.organizationId,
|
|
3054
|
+
expectedSalesLineIds: [...input.expectedLineIds].sort()
|
|
3055
|
+
})
|
|
3056
|
+
.execute();
|
|
3057
|
+
if (result.affected !== 1)
|
|
3058
|
+
throw new DomainError(DomainErrorCodes.INVALID_INPUT, '商品明细或顺序已变化,请关闭排序窗口并刷新后重试');
|
|
3059
|
+
return { customerContractId, salesLineOrderVersion: version };
|
|
3060
|
+
}
|
|
2957
3061
|
async listSalesFileOrders(scope, query = {}) {
|
|
2958
3062
|
const page = await this.selectDerivedSalesOrderIdentities(scope, undefined, query, true);
|
|
2959
3063
|
return { ...page, items: await this.salesOrderDtos(scope, await this.listDerivedSalesOrders(scope, undefined, page.items)) };
|
|
@@ -3496,6 +3600,7 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
3496
3600
|
const derivedLines = (linesByPurchase.get(purchase.id) ?? []).map(line => {
|
|
3497
3601
|
const pricing = deriveSalesPricingLine(line, lineOverrides[line.id], effectiveRate);
|
|
3498
3602
|
return {
|
|
3603
|
+
supplierId: purchase.supplierId, purchaseContractNo: purchase.contractNo, sourceLineNumber: line.lineNumber,
|
|
3499
3604
|
purchaseOrderLineId: line.id, productName: line.productName, englishName: line.englishName, productDescription: line.productDescription,
|
|
3500
3605
|
modelSpecification: line.modelSpecification, unit: line.unit, quantity: line.quantity, hsCode: line.hsCode,
|
|
3501
3606
|
vatRate: line.vatRate, refundRate: line.refundRate, originCountry: line.originCountry, domesticSourceLocation: line.domesticSourceLocation,
|
|
@@ -3543,6 +3648,9 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
3543
3648
|
const contract = contractById.get(order.customerContractId);
|
|
3544
3649
|
if (!contract)
|
|
3545
3650
|
continue;
|
|
3651
|
+
order.lines = resolveSalesLineOrder(order.lines, contract.salesLineOrder);
|
|
3652
|
+
order.salesLineOrderVersion = contract.salesLineOrderVersion ?? 0;
|
|
3653
|
+
order.salesLineOrderCustomized = Array.isArray(contract.salesLineOrder);
|
|
3546
3654
|
const baseCode = parseCustomerContractNo(contract.contractNo).baseCode;
|
|
3547
3655
|
const groupPurchases = (purchasesByBaseCode.get(baseCode) ?? []).filter(purchase => order.purchaseOrderIds?.includes(purchase.id));
|
|
3548
3656
|
// 销售单日期 = 客户合同与采购合同关联上、派生销售订单得以生成的日期
|
|
@@ -4384,12 +4492,13 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
4384
4492
|
return { ...supplier, ...productRisks.get(supplier.id), sanctionMatchCount, sanctionStatus: supplier.complianceCheckedAt == null ? 'UNCHECKED' : sanctionMatchCount ? 'HIT' : 'CLEAR' };
|
|
4385
4493
|
});
|
|
4386
4494
|
}
|
|
4387
|
-
async salesCustomerDtos(scope, customers, includeRelatedProductRisks = true) {
|
|
4495
|
+
async salesCustomerDtos(scope, customers, includeRelatedProductRisks = true, includeCredit = false) {
|
|
4388
4496
|
const ids = customers.map(item => item.id);
|
|
4389
|
-
const [matches, contracts, purchases] = await Promise.all([
|
|
4497
|
+
const [matches, contracts, purchases, unreceivedByCustomer] = await Promise.all([
|
|
4390
4498
|
ids.length ? this.dataSource.getRepository(CompanySanctionMatch).find({ where: { ...activeScope(scope), subjectType: 'CUSTOMER', customerId: In(ids), deletedAt: IsNull() } }) : [],
|
|
4391
4499
|
includeRelatedProductRisks && ids.length ? this.dataSource.getRepository(CustomerContract).find({ where: { ...activeScope(scope), customerId: In(ids), deletedAt: IsNull() } }) : [],
|
|
4392
|
-
includeRelatedProductRisks && ids.length ? this.dataSource.getRepository(PurchaseOrder).find({ where: { ...activeScope(scope), deletedAt: IsNull() } }) : []
|
|
4500
|
+
includeRelatedProductRisks && ids.length ? this.dataSource.getRepository(PurchaseOrder).find({ where: { ...activeScope(scope), deletedAt: IsNull() } }) : [],
|
|
4501
|
+
includeCredit && ids.length ? this.customerUnreceivedAmounts(scope, ids) : Promise.resolve(new Map())
|
|
4393
4502
|
]);
|
|
4394
4503
|
const purchaseIdsByCustomer = new Map(customers.map(customer => {
|
|
4395
4504
|
const contractBaseCodes = new Set(contracts.filter(contract => contract.customerId === customer.id).map(contract => parseCustomerContractNo(contract.contractNo).baseCode));
|
|
@@ -4398,7 +4507,8 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
4398
4507
|
const productRisks = includeRelatedProductRisks ? await this.relatedProductRisks(scope, purchaseIdsByCustomer) : new Map();
|
|
4399
4508
|
return customers.map(customer => {
|
|
4400
4509
|
const sanctionMatchCount = matches.filter(match => match.customerId === customer.id).length;
|
|
4401
|
-
|
|
4510
|
+
const creditFields = includeCredit ? customerCreditFields(customer, unreceivedByCustomer.get(customer.id) ?? '0.00') : {};
|
|
4511
|
+
return { ...customer, ...productRisks.get(customer.id), ...creditFields, profitRateStatus: customer.pricingProfitRateStatus, sanctionMatchCount, sanctionStatus: customer.complianceCheckStatus === 'UNCHECKED' ? 'UNCHECKED' : sanctionMatchCount ? 'HIT' : 'CLEAR' };
|
|
4402
4512
|
});
|
|
4403
4513
|
}
|
|
4404
4514
|
async relatedProductRisks(scope, purchaseIdsByParty) {
|
|
@@ -4435,7 +4545,7 @@ let TradeComplianceWorkbenchService = class TradeComplianceWorkbenchService {
|
|
|
4435
4545
|
const paymentStatus = received.lte(0) ? 'UNPAID' : received.gte(total) ? 'PAID' : 'PARTIAL';
|
|
4436
4546
|
const orderPurchaseIds = order.purchaseOrderIds?.length ? order.purchaseOrderIds : [order.purchaseOrderId];
|
|
4437
4547
|
const orderSupplierNames = [...new Set(orderPurchaseIds.map(id => supplierNames.get(purchaseSuppliers.get(id) ?? '') ?? '').filter(Boolean))];
|
|
4438
|
-
return { ...order, customerName: customerNames.get(order.customerId) ?? '', supplierName: orderSupplierNames.join('、'), pricingProfitRateStatus: order.pricingStatus === 'UNPRICED' ? 'MISSING' : 'CONFIGURED', paymentStatus, lines: order.lines.map(line => ({ ...line, complianceCategories: [...new Set(line.complianceMatches.map(match => match.catalogType))] })) };
|
|
4548
|
+
return { ...order, customerName: customerNames.get(order.customerId) ?? '', supplierName: orderSupplierNames.join('、'), pricingProfitRateStatus: order.pricingStatus === 'UNPRICED' ? 'MISSING' : 'CONFIGURED', paymentStatus, lines: order.lines.map(line => ({ ...line, supplierName: supplierNames.get(line.supplierId ?? '') ?? '', complianceCategories: [...new Set(line.complianceMatches.map(match => match.catalogType))] })) };
|
|
4439
4549
|
});
|
|
4440
4550
|
}
|
|
4441
4551
|
async purchaseOrderDtos(scope, orders) {
|
|
@@ -5081,6 +5191,11 @@ function salesMetrics(rows) {
|
|
|
5081
5191
|
};
|
|
5082
5192
|
}
|
|
5083
5193
|
function sum(rows, key) { return rows.reduce((value, row) => value.plus(row[key] ?? 0), new Decimal(0)).toFixed(2); }
|
|
5194
|
+
// 信用额度(人工维护)为静态值;未收款为运行时派生值,剩余额度 = 额度 - 未收款,可为负(已超额),绝不落库。
|
|
5195
|
+
function customerCreditFields(customer, unreceivedAmountCny) {
|
|
5196
|
+
const remainingCreditCny = customer.creditLimit == null ? null : new Decimal(customer.creditLimit).minus(unreceivedAmountCny).toFixed(2);
|
|
5197
|
+
return { creditLimit: customer.creditLimit, unreceivedAmountCny, remainingCreditCny };
|
|
5198
|
+
}
|
|
5084
5199
|
function booleanFilter(value) { return value === true || value === 'true' ? true : value === false || value === 'false' ? false : null; }
|
|
5085
5200
|
function purchaseEstimatedRefundSql(alias) { return `(SELECT COALESCE(SUM(refund_line."lineAmountTaxIncluded" / (1 + COALESCE(refund_line."vatRate", 0)) * COALESCE(refund_line."refundRate", 0)), 0) FROM plugin_trade_compliance_purchase_order_line refund_line WHERE refund_line."purchaseOrderId" = ${alias}.id AND refund_line."tenantId" = ${alias}."tenantId" AND refund_line."organizationId" = ${alias}."organizationId" AND refund_line."deletedAt" IS NULL)`; }
|
|
5086
5201
|
function derivedSalesContractLinkSql(contractAlias, purchaseAlias) {
|