import assert from 'node:assert/strict' import test from 'node:test' import { buildAnalyticsWindow, buildCommercialMutation, buildCommercialValueLedgers, buildPricingScenarioPayload, buildPricingScenarioView, buildQuotaRows, classifyCommercialAccountState, createDefaultPricingScenarioForm, formatCurrencyAmount, formatMetricValues, formatRatio } from '../src/components/commercial/commercialWorkspaceModel.js' test('客户价值账和平台经营账保持严格分离', () => { const ledgers = buildCommercialValueLedgers({ verifiedCashSavings: metric('verified_cash_savings', '客户财务确认现金节省', [{ currency: 'CNY', amount: '100' }]), customerCharges: metric('customer_charges', '客户合同收费', [{ currency: 'CNY', amount: '40' }], 'partial'), customerRoi: { ...metric('customer_roi', '客户现金 ROI', [], 'partial'), ratios: [{ currency: 'CNY', ratio: '1.5', numerator: '60', denominator: '40' }] }, customerLaborValue: unavailable('customer_labor_value', '客户认可工时价值'), internalCosts: metric('internal_costs', '平台内部成本', [{ currency: 'CNY', amount: '12' }]), contributionMargin: metric('contribution_margin', '贡献毛利', [{ currency: 'CNY', amount: '28' }], 'partial') }) assert.deepEqual(ledgers.customer.map((item) => item.category), [ 'verifiedCashSavings', 'customerCharges', 'customerRoi', 'customerLaborValue' ]) assert.deepEqual(ledgers.platform.map((item) => item.category), ['internalCosts', 'contributionMargin']) assert.equal(ledgers.customer[2].displayValue, '150.0%') assert.equal(ledgers.platform[0].displayValue, formatCurrencyAmount('12', 'CNY')) }) test('不同币种逐行保留,绝不在前端求和或换算', () => { const values = formatMetricValues(metric('verified', '已核验', [ { currency: 'CNY', amount: '100' }, { currency: 'USD', amount: '20' } ])) assert.deepEqual(values.map((item) => item.currency), ['CNY', 'USD']) assert.deepEqual(values.map((item) => item.amount), [100, 20]) assert.match(values[0].displayValue, /100/u) assert.match(values[1].displayValue, /20/u) assert.equal(formatRatio('0.25'), '25.0%') }) test('没有真实财务证据时明确不可用,不把未知伪装成零', () => { const [cash, labor] = buildCommercialValueLedgers({ verifiedCashSavings: unavailable('verified_cash_savings', '客户财务确认现金节省'), customerCharges: unavailable('customer_charges', '客户合同收费'), customerRoi: unavailable('customer_roi', '客户现金 ROI'), customerLaborValue: unavailable('customer_labor_value', '客户认可工时价值'), internalCosts: unavailable('internal_costs', '平台内部成本'), contributionMargin: unavailable('contribution_margin', '贡献毛利') }).customer assert.equal(cash.status, 'unavailable') assert.equal(cash.displayValue, '不可用') assert.deepEqual(cash.values, []) assert.equal(labor.displayValue, '不可用') assert.deepEqual(labor.requiredInputs, ['真实财务证据']) }) test('账户状态完整区分加载、权限、错误、未配置、部分与就绪', () => { assert.equal(classifyCommercialAccountState({ loading: true }), 'loading') assert.equal(classifyCommercialAccountState({ error: { status: 403 } }), 'permission') assert.equal(classifyCommercialAccountState({ error: new Error('offline') }), 'error') assert.equal(classifyCommercialAccountState({ account: { dataStatus: 'unavailable' } }), 'unavailable') assert.equal(classifyCommercialAccountState({ account: { dataStatus: 'partial' } }), 'partial') assert.equal(classifyCommercialAccountState({ account: { dataStatus: 'available' } }), 'ready') }) test('权益列表保持失败关闭语义并区分不限量和未知硬上限', () => { const rows = buildQuotaRows({ quotas: [ { status: 'unlimited', usedQuantity: '12', hardLimitRemaining: null, commerciallyAllowed: true, entitlement: { id: 'e1', entitlementKey: 'ocr', metricKey: 'ocr_calls', unit: '次', entitlementType: 'unlimited', version: 1 } }, { status: 'inactive', usedQuantity: '0', hardLimitRemaining: null, commerciallyAllowed: false, reason: '订阅暂停', entitlement: { id: 'e2', entitlementKey: 'ai', metricKey: 'ai_calls', unit: '次', entitlementType: 'metered', version: 2 } } ] }) assert.equal(rows[0].remainingLabel, '不限量') assert.equal(rows[1].remainingLabel, '未配置硬上限') assert.equal(rows[1].commerciallyAllowed, false) assert.equal(rows[1].reason, '订阅暂停') }) test('分析窗口生成显式时区并拒绝倒序和超过两年', () => { const window = buildAnalyticsWindow({ start: '2026-04-01', end: '2026-07-01' }) assert.match(window.start, /(Z|[+-]\d{2}:\d{2})$/u) assert.match(window.end, /(Z|[+-]\d{2}:\d{2})$/u) assert.throws(() => buildAnalyticsWindow({ start: '2026-07-01', end: '2026-04-01' }), /早于/u) assert.throws(() => buildAnalyticsWindow({ start: '2023-01-01', end: '2026-07-01' }), /731/u) }) test('定价场景将百分比和时间窗转换为后端精确契约并执行前置校验', () => { const defaults = createDefaultPricingScenarioForm( { start: '2026-04-01', end: '2026-07-01' }, new Date('2026-07-02T08:30:00Z') ) assert.equal(defaults.targetContributionMarginPercent, '65') assert.equal(defaults.maxVerifiedSavingsSharePercent, '25') const payload = buildPricingScenarioPayload({ ...defaults, asOf: '2026-07-02T08:30:00Z', targetContributionMarginPercent: '65', maxVerifiedSavingsSharePercent: '25' }) assert.match(payload.start, /(Z|[+-]\d{2}:\d{2})$/u) assert.match(payload.end, /(Z|[+-]\d{2}:\d{2})$/u) assert.equal(payload.asOf, '2026-07-02T08:30:00.000Z') assert.equal(payload.targetContributionMarginRate, '0.65') assert.equal(payload.maxVerifiedSavingsShare, '0.25') assert.throws(() => buildPricingScenarioPayload({ ...defaults, targetContributionMarginPercent: '95' }), /小于 95/u) assert.throws(() => buildPricingScenarioPayload({ ...defaults, targetContributionMarginPercent: '94.999999' }), /小于 95/u) assert.equal(buildPricingScenarioPayload({ ...defaults, targetContributionMarginPercent: '94.9999' }).targetContributionMarginRate, '0.949999') assert.throws(() => buildPricingScenarioPayload({ ...defaults, maxVerifiedSavingsSharePercent: '0' }), /大于 0/u) assert.throws(() => buildPricingScenarioPayload({ ...defaults, asOf: '2026-03-01T00:00:00Z' }), /不能早于/u) }) test('定价结果逐币种展示价格走廊且绝不把缺失事实显示为零', () => { const view = buildPricingScenarioView({ tenantId: 'tenant-a', start: '2026-04-01T00:00:00Z', end: '2026-07-01T23:59:59Z', asOf: '2026-07-02T00:00:00Z', targetContributionMarginRate: '0.65', maxVerifiedSavingsShare: '0.25', recommendedModel: 'hybrid', evidenceStatus: 'partial', scenarios: [ { currency: 'CNY', status: 'feasible', internalCost: '20', verifiedCashSavings: '200', minimumSustainableCharge: '57.1429', maximumValueAlignedCharge: '50', maximumSuccessFee: '0', customerRoiAtMinimumCharge: '2.5', contributionMarginAtValueCeiling: '0.6', reason: '独立 CNY 走廊' }, { currency: 'USD', status: 'cost_only', internalCost: '5', verifiedCashSavings: null, minimumSustainableCharge: '14.2857', maximumValueAlignedCharge: null, maximumSuccessFee: null, customerRoiAtMinimumCharge: null, contributionMarginAtValueCeiling: null, reason: '缺少 USD 价值证据' } ], notes: ['结果不会自动写套餐'] }) assert.equal(view.recommendedModelLabel, '混合定价(基础订阅 + 封顶成功费)') assert.equal(view.multiCurrency, true) assert.deepEqual(view.rows.map((row) => row.currency), ['CNY', 'USD']) assert.match(view.rows[0].minimumSustainableChargeLabel, /57/u) assert.equal(view.rows[1].maximumValueAlignedChargeLabel, '不可用') assert.equal(view.rows[1].maximumSuccessFeeLabel, '不可用') assert.equal(view.rows[1].customerRoiLabel, '—') assert.deepEqual(view.rows[1].missingInputs, ['同币种财务确认现金节省']) assert.doesNotMatch(view.rows[1].maximumValueAlignedChargeLabel, /0/u) }) test('完全无证据的定价响应保留 unavailable 与缺失说明', () => { const view = buildPricingScenarioView({ recommendedModel: 'pilot_collecting', evidenceStatus: 'unavailable', scenarios: [], notes: [] }) assert.equal(view.evidenceStatus, 'unavailable') assert.equal(view.evidenceStatusLabel, '不可用') assert.equal(view.recommendedModelLabel, '试点采集证据') assert.deepEqual(view.rows, []) }) test('套餐、订阅与权益表单构造服务端事实时间和版本化载荷', () => { const plan = buildCommercialMutation('createPlan', { planCode: 'enterprise', name: '企业版', pricingModel: 'hybrid', billingInterval: 'annual', currency: 'cny', baseFee: '1000', includedSeats: 20, overageEnabled: true, effectiveFrom: '2026-07-16T09:30', effectiveTo: '', contractTermsJson: '{"approved_by":"cfo"}' }) assert.equal(plan.currency, 'CNY') assert.match(plan.effectiveFrom, /Z$/u) assert.equal(plan.contractTermsJson.approved_by, 'cfo') const subscription = buildCommercialMutation('createSubscription', { subscriptionKey: 'sub-enterprise', status: 'active', startsAt: '2026-07-16T09:30', currentPeriodStart: '2026-07-16T09:30', currentPeriodEnd: '2026-08-16T09:30', seats: 20, metadataJson: '{}' }, { plan: { id: 'plan-1' } }) assert.equal(subscription.planId, 'plan-1') assert.match(subscription.currentPeriodEnd, /Z$/u) const entitlement = buildCommercialMutation('upsertEntitlement', { entitlementKey: 'ocr', metricKey: 'ocr_calls', entitlementType: 'metered', unit: '次', includedQuantity: '100', hardLimitQuantity: '120', resetInterval: 'monthly', overagePolicy: 'block', effectiveFrom: '2026-07-16T09:30', configJson: '{}' }, { subscription: { id: 'sub-1' } }) assert.equal(entitlement.subscriptionId, 'sub-1') assert.equal(entitlement.hardLimitQuantity, '120') }) test('客户端在写入前拒绝不完整窗口、降配和无效 JSON', () => { assert.throws(() => buildCommercialMutation('createPlan', { planCode: 'p', name: 'P', pricingModel: 'subscription', billingInterval: 'annual', currency: 'CNY', baseFee: '0', includedSeats: 1, effectiveFrom: '2026-08-01T00:00', effectiveTo: '2026-07-01T00:00', contractTermsJson: '{}' }), /晚于/u) assert.throws(() => buildCommercialMutation('upsertEntitlement', { entitlementKey: 'ocr', metricKey: 'ocr', entitlementType: 'metered', unit: '次', includedQuantity: '100', hardLimitQuantity: '10', resetInterval: 'monthly', overagePolicy: 'block', effectiveFrom: '2026-07-01T00:00', configJson: '{}' }, { subscription: { id: 'sub-1' } }), /不能小于/u) assert.throws(() => buildCommercialMutation('createPlan', { planCode: 'p', name: 'P', pricingModel: 'subscription', billingInterval: 'annual', currency: 'CNY', baseFee: '0', includedSeats: 1, effectiveFrom: '2026-07-01T00:00', contractTermsJson: '{bad}' }), /有效 JSON/u) }) test('用量和成本事件在客户端执行幂等、符号、冲回与币种边界校验', () => { const account = { subscription: { id: 'sub-1' } } const usage = buildCommercialMutation('recordUsage', { entitlementId: 'ent-1', eventType: 'usage', quantity: '1', occurredAt: '2026-07-16T09:30', sourceSystem: 'web', idempotencyKey: 'usage-1', metadataJson: '{}' }, account) assert.equal(usage.subscriptionId, 'sub-1') assert.match(usage.occurredAt, /Z$/u) assert.throws(() => buildCommercialMutation('recordUsage', { entitlementId: 'ent-1', eventType: 'credit', quantity: '1', occurredAt: '2026-07-16T09:30', sourceSystem: 'web', idempotencyKey: 'usage-2', metadataJson: '{}' }, account), /必须小于 0/u) assert.throws(() => buildCommercialMutation('recordUsage', { entitlementId: 'ent-1', eventType: 'reversal', quantity: '-1', occurredAt: '2026-07-16T09:30', sourceSystem: 'web', idempotencyKey: 'usage-3', metadataJson: '{}' }, account), /被冲回/u) const cost = buildCommercialMutation('recordCost', { eventType: 'incurred', costCategory: 'ai_inference', quantity: '3', unit: '次', unitCost: '0.02', originalCurrency: 'usd', reportingCurrency: 'cny', fxRate: '7.2', allocationKey: 'ai-july', occurredAt: '2026-07-16T09:30', sourceSystem: 'billing', idempotencyKey: 'cost-1', metadataJson: '{}' }, account) assert.equal(cost.originalCurrency, 'USD') assert.equal(cost.reportingCurrency, 'CNY') assert.equal(cost.fxRate, '7.2') }) test('订阅状态变更必须使用版本、受限目标状态和可审计原因', () => { const transition = buildCommercialMutation('transitionSubscription', { resourceId: 'sub-1', expectedVersion: 3, targetStatus: 'suspended', reason: '客户合同暂停' }) assert.deepEqual(transition, { resourceId: 'sub-1', expectedVersion: 3, targetStatus: 'suspended', reason: '客户合同暂停' }) assert.throws(() => buildCommercialMutation('transitionSubscription', { resourceId: 'sub-1', expectedVersion: 3, targetStatus: 'active', reason: '恢复' }), /目标订阅状态无效/u) assert.throws(() => buildCommercialMutation('transitionSubscription', { resourceId: 'sub-1', expectedVersion: 3, targetStatus: 'canceled', reason: 'x' }), /至少填写 2/u) }) function metric(key, label, values, status = 'available') { return { key, label, status, values, ratios: [], reason: '真实事实口径', requiredInputs: [], notes: [] } } function unavailable(key, label) { return { key, label, status: 'unavailable', values: [], ratios: [], reason: '缺少真实财务证据', requiredInputs: ['真实财务证据'], notes: [] } }