@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.
Files changed (57) hide show
  1. package/dist/lib/analytics-query.d.ts +1 -0
  2. package/dist/lib/analytics-query.d.ts.map +1 -1
  3. package/dist/lib/analytics-query.js +1 -1
  4. package/dist/lib/analytics-query.js.map +1 -1
  5. package/dist/lib/domain/sales-line-order.d.ts +4 -0
  6. package/dist/lib/domain/sales-line-order.d.ts.map +1 -0
  7. package/dist/lib/domain/sales-line-order.js +18 -0
  8. package/dist/lib/domain/sales-line-order.js.map +1 -0
  9. package/dist/lib/entities/customer.entity.d.ts +3 -0
  10. package/dist/lib/entities/customer.entity.d.ts.map +1 -1
  11. package/dist/lib/entities/customer.entity.js +12 -0
  12. package/dist/lib/entities/customer.entity.js.map +1 -1
  13. package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
  14. package/dist/lib/remote-components/trade-compliance-workbench/app.js +5 -5
  15. package/dist/lib/remote-ui/action-execution.d.ts.map +1 -1
  16. package/dist/lib/remote-ui/action-execution.js +2 -0
  17. package/dist/lib/remote-ui/action-execution.js.map +1 -1
  18. package/dist/lib/remote-ui/components/data-grid.d.ts +2 -1
  19. package/dist/lib/remote-ui/components/data-grid.d.ts.map +1 -1
  20. package/dist/lib/remote-ui/components/data-grid.js +2 -2
  21. package/dist/lib/remote-ui/components/data-grid.js.map +1 -1
  22. package/dist/lib/remote-ui/pages/sales-line-order.css +16 -0
  23. package/dist/lib/remote-ui/pages/sales-line-order.d.ts +12 -0
  24. package/dist/lib/remote-ui/pages/sales-line-order.d.ts.map +1 -0
  25. package/dist/lib/remote-ui/pages/sales-line-order.js +66 -0
  26. package/dist/lib/remote-ui/pages/sales-line-order.js.map +1 -0
  27. package/dist/lib/remote-ui/pages/sales.d.ts +11 -1
  28. package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
  29. package/dist/lib/remote-ui/pages/sales.js +43 -4
  30. package/dist/lib/remote-ui/pages/sales.js.map +1 -1
  31. package/dist/lib/remote-ui/sales-contracts.d.ts +14 -1
  32. package/dist/lib/remote-ui/sales-contracts.d.ts.map +1 -1
  33. package/dist/lib/remote-ui/sales-contracts.js.map +1 -1
  34. package/dist/lib/remote-ui/styles.css +6 -0
  35. package/dist/lib/sales-file.service.d.ts.map +1 -1
  36. package/dist/lib/sales-file.service.js +23 -3
  37. package/dist/lib/sales-file.service.js.map +1 -1
  38. package/dist/lib/schema-maintenance.d.ts.map +1 -1
  39. package/dist/lib/schema-maintenance.js +5 -0
  40. package/dist/lib/schema-maintenance.js.map +1 -1
  41. package/dist/lib/types.d.ts +9 -0
  42. package/dist/lib/types.d.ts.map +1 -1
  43. package/dist/lib/types.js +1 -0
  44. package/dist/lib/types.js.map +1 -1
  45. package/dist/lib/workbench-action-contract.d.ts +1 -1
  46. package/dist/lib/workbench-action-contract.d.ts.map +1 -1
  47. package/dist/lib/workbench-action-contract.js +2 -0
  48. package/dist/lib/workbench-action-contract.js.map +1 -1
  49. package/dist/lib/workbench-view.provider.d.ts +22 -0
  50. package/dist/lib/workbench-view.provider.d.ts.map +1 -1
  51. package/dist/lib/workbench-view.provider.js +7 -1
  52. package/dist/lib/workbench-view.provider.js.map +1 -1
  53. package/dist/lib/workbench.service.d.ts +46 -0
  54. package/dist/lib/workbench.service.d.ts.map +1 -1
  55. package/dist/lib/workbench.service.js +122 -7
  56. package/dist/lib/workbench.service.js.map +1 -1
  57. 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
- return { ...customer, ...productRisks.get(customer.id), profitRateStatus: customer.pricingProfitRateStatus, sanctionMatchCount, sanctionStatus: customer.complianceCheckStatus === 'UNCHECKED' ? 'UNCHECKED' : sanctionMatchCount ? 'HIT' : 'CLEAR' };
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) {