diff --git a/apps/backend/src/services/cost-center/cost-center-constants.ts b/apps/backend/src/services/cost-center/cost-center-constants.ts index e687ae92..9a947ad7 100644 --- a/apps/backend/src/services/cost-center/cost-center-constants.ts +++ b/apps/backend/src/services/cost-center/cost-center-constants.ts @@ -28,6 +28,7 @@ export type TemplateMappingMatchType = export type CostModel = | 'affiliate_purchase' | 'work_order_reward' + | 'third_party_voucher_cost' | 'manual_actual' | 'platform_fee' | 'payment_fee' @@ -37,6 +38,7 @@ export type CostModel = export const COST_MODELS = new Set([ 'affiliate_purchase', 'work_order_reward', + 'third_party_voucher_cost', 'manual_actual', 'platform_fee', 'payment_fee', @@ -48,6 +50,7 @@ export const COST_MODELS = new Set([ export const COST_MODEL_CODE_BASE: Record = { affiliate_purchase: 'affiliate_purchase', work_order_reward: 'work_order_reward', + third_party_voucher_cost: 'third_party_voucher_cost', manual_actual: 'manual_actual', platform_fee: 'platform_fee', payment_fee: 'payment_fee', diff --git a/apps/backend/src/services/cost-center/cost-center-mappers.ts b/apps/backend/src/services/cost-center/cost-center-mappers.ts index 9e37d6c3..24d0bffe 100644 --- a/apps/backend/src/services/cost-center/cost-center-mappers.ts +++ b/apps/backend/src/services/cost-center/cost-center-mappers.ts @@ -38,6 +38,7 @@ export function productSourceFilterSql(paramIndex: number, column = 'p.canonical return `( ($${paramIndex} = 'affiliate_dash' AND ${column} LIKE 'aff:%') OR ($${paramIndex} = 'work_order' AND (${column} LIKE 'tpl:%' OR ${column} LIKE 'wo:%')) + OR ($${paramIndex} = 'third_party_voucher' AND ${column} LIKE 'tpv:%') OR ($${paramIndex} = 'manual' AND ${column} LIKE 'man:%') )` } @@ -52,6 +53,7 @@ export function costModelAppliesTo(costModel: string, fulfillmentMode: string) { if ((GENERIC_COST_MODELS as readonly string[]).includes(costModel)) return true if (costModel === 'affiliate_purchase') return fulfillmentMode === 'affiliate_dash' if (costModel === 'work_order_reward') return fulfillmentMode === 'work_order' + if (costModel === 'third_party_voucher_cost') return fulfillmentMode === 'third_party_voucher' if (costModel === 'manual_actual') return fulfillmentMode === 'manual' return false } @@ -67,6 +69,8 @@ export function costModelScopeSql(fulfillmentModeExpression: string): string { AND ${fulfillmentModeExpression} = 'affiliate_dash') OR (ci.cost_model = 'work_order_reward' AND ${fulfillmentModeExpression} = 'work_order') + OR (ci.cost_model = 'third_party_voucher_cost' + AND ${fulfillmentModeExpression} = 'third_party_voucher') OR (ci.cost_model = 'manual_actual' AND ${fulfillmentModeExpression} = 'manual') )` diff --git a/apps/backend/src/services/cost-center/cost-center-overview-service.ts b/apps/backend/src/services/cost-center/cost-center-overview-service.ts index bcc78e49..3b56cd0c 100644 --- a/apps/backend/src/services/cost-center/cost-center-overview-service.ts +++ b/apps/backend/src/services/cost-center/cost-center-overview-service.ts @@ -41,6 +41,7 @@ export async function getAdminCostCenterOverview(params: { scope?: string } = {} $1::text IS NULL OR ($1 = 'affiliate_dash' AND p.canonical_sku LIKE 'aff:%') OR ($1 = 'work_order' AND (p.canonical_sku LIKE 'tpl:%' OR p.canonical_sku LIKE 'wo:%')) + OR ($1 = 'third_party_voucher' AND p.canonical_sku LIKE 'tpv:%') OR ($1 = 'manual' AND p.canonical_sku LIKE 'man:%') ) ), @@ -48,6 +49,7 @@ export async function getAdminCostCenterOverview(params: { scope?: string } = {} SELECT CASE $1 WHEN 'affiliate_dash' THEN 'affiliate_purchase' WHEN 'work_order' THEN 'work_order_reward' + WHEN 'third_party_voucher' THEN 'third_party_voucher_cost' WHEN 'manual' THEN 'manual_actual' END AS cost_model ) diff --git a/apps/backend/src/services/cost-center/cost-center.test.ts b/apps/backend/src/services/cost-center/cost-center.test.ts index 832575d7..030ec22f 100644 --- a/apps/backend/src/services/cost-center/cost-center.test.ts +++ b/apps/backend/src/services/cost-center/cost-center.test.ts @@ -1,6 +1,7 @@ import assert from 'node:assert/strict' import test from 'node:test' +import { costModelAppliesTo } from './cost-center-mappers.js' import { normalizeMappingMatchType, normalizeProviderKey, @@ -8,6 +9,7 @@ import { } from './cost-center-shared.js' import { calculateCostTotal, validateCostVersionScope } from './cost-resolution-service.js' import { resolveMappingProductId } from './product-catalog-service.js' +import { productSourceFilterSql } from './cost-center-mappers.js' import { buildTemplateMappingPlan, buildTemplateProductSku, @@ -289,3 +291,10 @@ test('接单模板改价 SQL 的参数必须显式指定 bigint', async () => { ), ) }) + +test('成本模型与商品来源必须覆盖第三方卡券渠道', () => { + assert.equal(costModelAppliesTo('third_party_voucher_cost', 'third_party_voucher'), true) + assert.equal(costModelAppliesTo('third_party_voucher_cost', 'manual'), false) + assert.ok(productSourceFilterSql(1).includes("'third_party_voucher'")) + assert.ok(productSourceFilterSql(1).includes("'tpv:%'")) +}) diff --git a/apps/frontend/src/pages/admin/cost-center/shared.ts b/apps/frontend/src/pages/admin/cost-center/shared.ts index 2d873990..402a0a36 100644 --- a/apps/frontend/src/pages/admin/cost-center/shared.ts +++ b/apps/frontend/src/pages/admin/cost-center/shared.ts @@ -78,6 +78,7 @@ export type CostVersionFormValues = { export const COST_MODEL_OPTIONS = [ { value: 'affiliate_purchase', label: '上游采购成本' }, { value: 'work_order_reward', label: '接单平台打手报酬' }, + { value: 'third_party_voucher_cost', label: '第三方卡券采购成本' }, { value: 'manual_actual', label: '人工客服实际支出' }, { value: 'platform_fee', label: '平台服务费' }, { value: 'payment_fee', label: '支付通道费' }, diff --git a/apps/frontend/src/types/admin/cost-center.ts b/apps/frontend/src/types/admin/cost-center.ts index f44ddda8..b817abd4 100644 --- a/apps/frontend/src/types/admin/cost-center.ts +++ b/apps/frontend/src/types/admin/cost-center.ts @@ -18,6 +18,7 @@ export type ProductSource = 'affiliate_dash' | 'work_order' | 'third_party_vouch export type CostModel = | 'affiliate_purchase' | 'work_order_reward' + | 'third_party_voucher_cost' | 'manual_actual' | 'platform_fee' | 'payment_fee'