fix(admin): 恢复接单模板计价配置并统一成本价格链路
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
-- 接单模板显式保存计价方式,金额字段只由商品与成本中心写投影。
|
||||
-- 历史数据按旧运行口径回填:拼单自动按件优先识别,其次识别“单价>0 且固定金额=0”。
|
||||
|
||||
ALTER TABLE work_product_rules
|
||||
ADD COLUMN IF NOT EXISTS pricing_mode TEXT;
|
||||
|
||||
UPDATE work_product_rules
|
||||
SET pricing_mode = CASE
|
||||
WHEN sharing_enabled = TRUE
|
||||
AND sharing_auto_from_order = TRUE
|
||||
AND unit_price_fen > 0 THEN 'unit'
|
||||
WHEN unit_price_fen > 0 AND reward_amount = 0 THEN 'unit'
|
||||
ELSE 'fixed'
|
||||
END
|
||||
WHERE pricing_mode IS NULL
|
||||
OR pricing_mode NOT IN ('fixed', 'unit');
|
||||
|
||||
ALTER TABLE work_product_rules
|
||||
ALTER COLUMN pricing_mode SET DEFAULT 'fixed',
|
||||
ALTER COLUMN pricing_mode SET NOT NULL;
|
||||
|
||||
-- 自动拼单只能依赖每件金额;固定金额模板不能保留“随订单自动算拼单”的旧标志。
|
||||
UPDATE work_product_rules
|
||||
SET sharing_auto_from_order = FALSE
|
||||
WHERE pricing_mode = 'fixed'
|
||||
AND sharing_auto_from_order = TRUE;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'chk_work_product_rules_pricing_mode'
|
||||
AND conrelid = 'work_product_rules'::regclass
|
||||
) THEN
|
||||
ALTER TABLE work_product_rules
|
||||
ADD CONSTRAINT chk_work_product_rules_pricing_mode
|
||||
CHECK (pricing_mode IN ('fixed', 'unit'));
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
COMMENT ON COLUMN work_product_rules.pricing_mode IS
|
||||
'接单计价方式:fixed 固定整单金额 / unit 按 SKU 数量乘每件金额';
|
||||
|
||||
-- 同步模板商品属性中的计价方式,避免继续从两个金额字段反推。
|
||||
UPDATE product_catalog p
|
||||
SET attributes_json = COALESCE(p.attributes_json, '{}'::jsonb) || jsonb_build_object(
|
||||
'pricingMode', wpr.pricing_mode,
|
||||
'rewardAmountFen', wpr.reward_amount,
|
||||
'unitPriceFen', wpr.unit_price_fen
|
||||
),
|
||||
updated_at = NOW()
|
||||
FROM work_product_rules wpr
|
||||
WHERE p.canonical_sku = LEFT('tpl:' || BTRIM(wpr.rule_key), 64);
|
||||
@@ -288,6 +288,7 @@ export type WorkProductRuleRow = {
|
||||
auto_create: boolean
|
||||
reward_amount: number
|
||||
unit_price_fen: number
|
||||
pricing_mode: 'fixed' | 'unit'
|
||||
required_deposit_amount: number
|
||||
deposit_threshold_amount: number
|
||||
sharing_enabled: boolean
|
||||
|
||||
@@ -356,6 +356,7 @@ export async function upsertWorkProductRule(input: {
|
||||
autoCreate: boolean
|
||||
rewardAmount: number
|
||||
unitPriceFen?: number
|
||||
pricingMode: 'fixed' | 'unit'
|
||||
requiredDepositAmount: number
|
||||
depositThresholdAmount: number
|
||||
sharingEnabled?: boolean
|
||||
@@ -385,7 +386,7 @@ export async function upsertWorkProductRule(input: {
|
||||
timeout_minutes, timeout_policy,
|
||||
acceptance_mode, gift_cooldown_hours, gift_cooldown_minutes,
|
||||
requirement_json, match_json,
|
||||
sort_order, created_at, updated_at
|
||||
sort_order, pricing_mode, created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$7, $8, $9, $10, $11,
|
||||
@@ -395,7 +396,7 @@ export async function upsertWorkProductRule(input: {
|
||||
$19, $20,
|
||||
$21, $22, $23,
|
||||
$24::jsonb, $25::jsonb,
|
||||
$26, $27, $28
|
||||
$26, $27, $28, $29
|
||||
)
|
||||
ON CONFLICT (rule_key) DO UPDATE
|
||||
SET
|
||||
@@ -424,6 +425,7 @@ export async function upsertWorkProductRule(input: {
|
||||
requirement_json = EXCLUDED.requirement_json,
|
||||
match_json = EXCLUDED.match_json,
|
||||
sort_order = EXCLUDED.sort_order,
|
||||
pricing_mode = EXCLUDED.pricing_mode,
|
||||
updated_at = EXCLUDED.updated_at
|
||||
RETURNING *
|
||||
`,
|
||||
@@ -454,6 +456,7 @@ export async function upsertWorkProductRule(input: {
|
||||
input.requirementJson,
|
||||
input.matchJson || '{}',
|
||||
input.sortOrder,
|
||||
input.pricingMode,
|
||||
input.now,
|
||||
input.now,
|
||||
],
|
||||
|
||||
@@ -188,13 +188,11 @@ export function mapCostVersion(row: CostVersionRow) {
|
||||
const revenueFen = Number(row.revenue_fen || 0)
|
||||
const rewardAvgFen = Number(row.reward_avg_fen || 0)
|
||||
const rewardFen = Number(row.reward_fen || 0)
|
||||
const ruleUnitPriceFen = Number(row.rule_unit_price_fen || 0)
|
||||
const ruleRewardAmountFen = Number(row.rule_reward_amount || 0)
|
||||
const amountKind =
|
||||
row.cost_item_code === 'sys_work_order_reward' &&
|
||||
row.scope_type === 'rule' &&
|
||||
ruleUnitPriceFen > 0 &&
|
||||
ruleRewardAmountFen === 0
|
||||
row.work_product_rule_pricing_mode === 'unit'
|
||||
? 'unit_price'
|
||||
: 'fixed'
|
||||
return {
|
||||
@@ -214,7 +212,7 @@ export function mapCostVersion(row: CostVersionRow) {
|
||||
workProductRuleName: row.work_product_rule_name || '',
|
||||
workProductRuleKey: row.work_product_rule_key || '',
|
||||
ruleRewardAmountFen,
|
||||
ruleUnitPriceFen,
|
||||
ruleUnitPriceFen: Number(row.rule_unit_price_fen || 0),
|
||||
revenueItemCount,
|
||||
revenueFen,
|
||||
revenueAvgFen: revenueItemCount > 0 ? Math.round(revenueFen / revenueItemCount) : 0,
|
||||
|
||||
@@ -113,6 +113,7 @@ export type CostVersionRow = {
|
||||
work_product_rule_id: number | null
|
||||
work_product_rule_name: string | null
|
||||
work_product_rule_key: string | null
|
||||
work_product_rule_pricing_mode?: 'fixed' | 'unit' | null
|
||||
rule_reward_amount: number | null
|
||||
rule_unit_price_fen: number | null
|
||||
revenue_item_count: number | null
|
||||
|
||||
@@ -324,6 +324,7 @@ export async function listAdminCostVersions(payload: JsonObject = {}) {
|
||||
p.canonical_name,
|
||||
wpr.product_name AS work_product_rule_name,
|
||||
wpr.rule_key AS work_product_rule_key,
|
||||
wpr.pricing_mode AS work_product_rule_pricing_mode,
|
||||
wpr.reward_amount AS rule_reward_amount,
|
||||
wpr.unit_price_fen AS rule_unit_price_fen,
|
||||
rev.revenue_item_count,
|
||||
|
||||
@@ -103,6 +103,25 @@ test('成本解析:卡券渠道按件计,取业务日期前的最新版本',
|
||||
assert.equal(resolution.unitAmountFen, 5000)
|
||||
})
|
||||
|
||||
test('成本解析:接单模板按 SKU 计件时,预估成本按数量乘每件金额', () => {
|
||||
const resolution = resolveCost(
|
||||
item({
|
||||
fulfillment_channel: 'work_order',
|
||||
canonical_product_id: null,
|
||||
quantity: 2,
|
||||
work_product_rule_id: 9,
|
||||
work_product_rule_pricing_mode: 'unit',
|
||||
}),
|
||||
costItems,
|
||||
versions,
|
||||
pricingModes,
|
||||
)
|
||||
assert.ok(resolution)
|
||||
assert.equal(resolution.costVersionId, 101)
|
||||
assert.equal(resolution.unitAmountFen, 840)
|
||||
assert.equal(resolution.totalAmountFen, 1680)
|
||||
})
|
||||
|
||||
test('成本解析:生效日期晚于业务日期的版本不参与', () => {
|
||||
const version = selectCostVersion(versions, {
|
||||
costItemId: 4,
|
||||
|
||||
@@ -62,6 +62,7 @@ export type CostCandidateItem = {
|
||||
basis_at: string
|
||||
work_order_id: number | null
|
||||
work_product_rule_id: number | null
|
||||
work_product_rule_pricing_mode?: 'fixed' | 'unit' | null
|
||||
work_order_status: string | null
|
||||
reward_amount: number | null
|
||||
revenue_fen: number
|
||||
@@ -157,7 +158,14 @@ export function resolveCost(
|
||||
})
|
||||
if (!version) return null
|
||||
|
||||
const pricingMode = pricingModeByItemId.get(costItem.id) || costItem.pricing_mode
|
||||
const pricingMode =
|
||||
item.fulfillment_channel === 'work_order' &&
|
||||
costItem.code === 'sys_work_order_reward' &&
|
||||
item.work_product_rule_pricing_mode
|
||||
? item.work_product_rule_pricing_mode === 'unit'
|
||||
? 'per_item'
|
||||
: 'per_order'
|
||||
: pricingModeByItemId.get(costItem.id) || costItem.pricing_mode
|
||||
const amount = Number(version.amount_fen) || 0
|
||||
const quantity = Math.max(1, Number(item.quantity) || 1)
|
||||
|
||||
@@ -306,11 +314,13 @@ const CANDIDATE_SQL = `
|
||||
), 0)::bigint AS refund_fen,
|
||||
wo.id AS work_order_id,
|
||||
wo.product_rule_id AS work_product_rule_id,
|
||||
wpr.pricing_mode AS work_product_rule_pricing_mode,
|
||||
wo.status AS work_order_status,
|
||||
wo.reward_amount
|
||||
FROM order_items oi
|
||||
JOIN orders o ON o.id = oi.order_id
|
||||
LEFT JOIN work_orders wo ON wo.order_item_id = oi.id
|
||||
LEFT JOIN work_product_rules wpr ON wpr.id = wo.product_rule_id
|
||||
WHERE oi.fulfillment_status = 'assigned'
|
||||
AND oi.fulfillment_channel IN (
|
||||
'affiliate_dash', 'third_party_voucher', 'work_order', 'manual'
|
||||
|
||||
@@ -153,11 +153,7 @@ export async function listAdminProducts(payload: JsonObject = {}) {
|
||||
`
|
||||
SELECT
|
||||
p.*,
|
||||
CASE
|
||||
WHEN tpl_rule.unit_price_fen > 0 AND tpl_rule.reward_amount = 0 THEN 'unit'
|
||||
WHEN tpl_rule.id IS NOT NULL THEN 'fixed'
|
||||
ELSE NULL
|
||||
END AS template_pricing_mode,
|
||||
tpl_rule.pricing_mode AS template_pricing_mode,
|
||||
tpl_rule.enabled AS template_enabled,
|
||||
tpl_rule.sharing_enabled AS template_sharing_enabled,
|
||||
(
|
||||
@@ -1551,10 +1547,11 @@ export async function setAdminProductPrice(
|
||||
enabled: boolean
|
||||
reward_amount: number
|
||||
unit_price_fen: number
|
||||
pricing_mode: 'fixed' | 'unit'
|
||||
sharing_enabled: boolean
|
||||
sharing_auto_from_order: boolean
|
||||
}>(
|
||||
`SELECT id, rule_key, product_name, enabled, reward_amount, unit_price_fen,
|
||||
`SELECT id, rule_key, product_name, enabled, reward_amount, unit_price_fen, pricing_mode,
|
||||
sharing_enabled, sharing_auto_from_order
|
||||
FROM work_product_rules
|
||||
WHERE rule_key = $1
|
||||
@@ -1569,17 +1566,13 @@ export async function setAdminProductPrice(
|
||||
})
|
||||
}
|
||||
const rawPricingMode = text(payload.pricingMode ?? payload.pricing_mode)
|
||||
if (rawPricingMode && rawPricingMode !== 'fixed' && rawPricingMode !== 'unit') {
|
||||
throw createHttpError('接单模板计价方式无效', {
|
||||
if (rawPricingMode) {
|
||||
throw createHttpError('接单模板计价方式只能在【接单模板】中设置', {
|
||||
statusCode: 400,
|
||||
errorCode: 'cost_work_order_pricing_mode_invalid',
|
||||
errorCode: 'cost_work_order_pricing_mode_managed_by_template',
|
||||
})
|
||||
}
|
||||
const pricingMode =
|
||||
rawPricingMode ||
|
||||
(Number(rule.unit_price_fen || 0) > 0 && Number(rule.reward_amount || 0) === 0
|
||||
? 'unit'
|
||||
: 'fixed')
|
||||
const pricingMode: 'fixed' | 'unit' = rule.pricing_mode === 'unit' ? 'unit' : 'fixed'
|
||||
if (pricingMode === 'fixed' && rule.sharing_enabled && rule.sharing_auto_from_order) {
|
||||
throw createHttpError(
|
||||
'拼单自动按件计价与固定金额冲突,请先在接单模板关闭拼单或改为手工拼单价',
|
||||
|
||||
@@ -128,6 +128,7 @@ export async function applyAdminTemplateProductBootstrap(
|
||||
skuNick: template.sku_nick,
|
||||
relSkuId: template.rel_sku_id,
|
||||
sellerIds: template.seller_ids_json,
|
||||
pricingMode: template.pricing_mode === 'unit' ? 'unit' : 'fixed',
|
||||
unitPriceFen: Number(template.unit_price_fen || 0),
|
||||
rewardAmountFen: Number(template.reward_amount || 0),
|
||||
templateEnabled: template.enabled,
|
||||
@@ -282,6 +283,7 @@ type TemplateBootstrapTemplateRow = {
|
||||
category: string
|
||||
unit_price_fen: number
|
||||
reward_amount: number
|
||||
pricing_mode: 'fixed' | 'unit'
|
||||
mapping_id: number | null
|
||||
mapping_type: string
|
||||
sku_nick: string
|
||||
@@ -696,6 +698,7 @@ async function loadTemplateProductBootstrapData(range: { fromDate: string; toDat
|
||||
COALESCE(c.name, '') AS category,
|
||||
r.unit_price_fen,
|
||||
r.reward_amount,
|
||||
r.pricing_mode,
|
||||
m.id AS mapping_id,
|
||||
COALESCE(m.mapping_type, '') AS mapping_type,
|
||||
COALESCE(m.sku_nick, '') AS sku_nick,
|
||||
|
||||
@@ -327,7 +327,7 @@ export async function syncAdminWorkOrderTemplateCostVersions(ruleId?: number) {
|
||||
SELECT
|
||||
wpr.id AS rule_id,
|
||||
CASE
|
||||
WHEN wpr.unit_price_fen > 0 AND wpr.reward_amount = 0 THEN wpr.unit_price_fen
|
||||
WHEN wpr.pricing_mode = 'unit' THEN wpr.unit_price_fen
|
||||
ELSE wpr.reward_amount
|
||||
END AS amount_fen
|
||||
FROM work_product_rules wpr
|
||||
@@ -364,7 +364,7 @@ export async function syncAdminWorkOrderTemplateCostVersions(ruleId?: number) {
|
||||
SELECT
|
||||
wpr.id AS rule_id,
|
||||
CASE
|
||||
WHEN wpr.unit_price_fen > 0 AND wpr.reward_amount = 0 THEN wpr.unit_price_fen
|
||||
WHEN wpr.pricing_mode = 'unit' THEN wpr.unit_price_fen
|
||||
ELSE wpr.reward_amount
|
||||
END AS amount_fen
|
||||
FROM work_product_rules wpr
|
||||
@@ -416,7 +416,7 @@ export async function syncAdminWorkOrderTemplateCostVersions(ruleId?: number) {
|
||||
'import',
|
||||
'active',
|
||||
CASE
|
||||
WHEN wpr.unit_price_fen > 0 AND wpr.reward_amount = 0
|
||||
WHEN wpr.pricing_mode = 'unit'
|
||||
THEN '模板按件发单单价(数量取订单 SKU)'
|
||||
ELSE '模板固定发单金额'
|
||||
END,
|
||||
@@ -469,7 +469,7 @@ export async function syncAdminWorkOrderTemplateCostVersions(ruleId?: number) {
|
||||
SELECT
|
||||
COUNT(*)::int AS rule_count,
|
||||
COUNT(*) FILTER (
|
||||
WHERE wpr.unit_price_fen > 0 AND wpr.reward_amount = 0
|
||||
WHERE wpr.pricing_mode = 'unit'
|
||||
)::int AS unit_price_rule_count,
|
||||
COUNT(v.id) FILTER (
|
||||
WHERE v.scope_type = 'rule' AND v.status = 'active'
|
||||
|
||||
@@ -45,6 +45,7 @@ test(
|
||||
assert.ok(ruleId > 0)
|
||||
assert.ok(productId > 0)
|
||||
assert.equal(created.rule.enabled, false)
|
||||
assert.equal(created.rule.pricingMode, 'fixed')
|
||||
assert.equal(created.costConfigurationRequired, true)
|
||||
|
||||
const product = await query<{ canonical_sku: string; status: string }>(
|
||||
@@ -77,7 +78,6 @@ test(
|
||||
productId,
|
||||
{
|
||||
amountFen: 1234,
|
||||
pricingMode: 'fixed',
|
||||
changeReason: '接单模板成本集成测试',
|
||||
syncOpenOrders: false,
|
||||
},
|
||||
@@ -100,10 +100,11 @@ test(
|
||||
enabled: boolean
|
||||
reward_amount: number
|
||||
unit_price_fen: number
|
||||
pricing_mode: string
|
||||
amount_fen: number
|
||||
}>(
|
||||
`
|
||||
SELECT wpr.enabled, wpr.reward_amount, wpr.unit_price_fen, v.amount_fen
|
||||
SELECT wpr.enabled, wpr.reward_amount, wpr.unit_price_fen, wpr.pricing_mode, v.amount_fen
|
||||
FROM work_product_rules wpr
|
||||
JOIN cost_versions v ON v.work_product_rule_id = wpr.id AND v.status = 'active'
|
||||
WHERE wpr.id = $1
|
||||
@@ -115,6 +116,7 @@ test(
|
||||
assert.equal(projection.rows[0]?.enabled, false)
|
||||
assert.equal(Number(projection.rows[0]?.reward_amount), 1234)
|
||||
assert.equal(Number(projection.rows[0]?.unit_price_fen), 0)
|
||||
assert.equal(projection.rows[0]?.pricing_mode, 'fixed')
|
||||
assert.equal(Number(projection.rows[0]?.amount_fen), 1234)
|
||||
|
||||
await assert.rejects(
|
||||
@@ -124,6 +126,30 @@ test(
|
||||
{
|
||||
amountFen: 2000,
|
||||
pricingMode: 'fixed',
|
||||
syncOpenOrders: false,
|
||||
},
|
||||
{
|
||||
sessionId: `it-${suffix}`,
|
||||
userId: Number(adminUser.id),
|
||||
username: adminUser.username,
|
||||
displayName: adminUser.display_name || adminUser.username,
|
||||
role: 'admin',
|
||||
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
||||
absoluteExpiresAt: new Date(Date.now() + 60_000).toISOString(),
|
||||
sessionVersion: 1,
|
||||
deviceId: 'integration-test',
|
||||
permissions: [],
|
||||
},
|
||||
),
|
||||
/计价方式只能在【接单模板】中设置/,
|
||||
)
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
setAdminProductPrice(
|
||||
productId,
|
||||
{
|
||||
amountFen: 2000,
|
||||
effectiveFrom: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
||||
syncOpenOrders: false,
|
||||
},
|
||||
@@ -143,11 +169,19 @@ test(
|
||||
/暂不支持未来生效/,
|
||||
)
|
||||
|
||||
const unitModeRule = await saveAdminWorkProductRule({
|
||||
ruleId,
|
||||
productName,
|
||||
pricingMode: 'unit',
|
||||
})
|
||||
assert.equal(unitModeRule.rule.pricingMode, 'unit')
|
||||
assert.equal(unitModeRule.rule.unitPriceFen, 1234)
|
||||
assert.equal(unitModeRule.rule.rewardAmount, 0)
|
||||
|
||||
await setAdminProductPrice(
|
||||
productId,
|
||||
{
|
||||
amountFen: 1300,
|
||||
pricingMode: 'unit',
|
||||
changeReason: '接单模板按件成本集成测试',
|
||||
syncOpenOrders: false,
|
||||
},
|
||||
@@ -167,10 +201,11 @@ test(
|
||||
const unitProjection = await query<{
|
||||
reward_amount: number
|
||||
unit_price_fen: number
|
||||
pricing_mode: string
|
||||
amount_fen: number
|
||||
}>(
|
||||
`
|
||||
SELECT wpr.reward_amount, wpr.unit_price_fen, v.amount_fen
|
||||
SELECT wpr.reward_amount, wpr.unit_price_fen, wpr.pricing_mode, v.amount_fen
|
||||
FROM work_product_rules wpr
|
||||
JOIN cost_versions v ON v.work_product_rule_id = wpr.id AND v.status = 'active'
|
||||
WHERE wpr.id = $1
|
||||
@@ -181,8 +216,18 @@ test(
|
||||
)
|
||||
assert.equal(Number(unitProjection.rows[0]?.reward_amount), 0)
|
||||
assert.equal(Number(unitProjection.rows[0]?.unit_price_fen), 1300)
|
||||
assert.equal(unitProjection.rows[0]?.pricing_mode, 'unit')
|
||||
assert.equal(Number(unitProjection.rows[0]?.amount_fen), 1300)
|
||||
|
||||
const fixedModeRule = await saveAdminWorkProductRule({
|
||||
ruleId,
|
||||
productName,
|
||||
pricingMode: 'fixed',
|
||||
})
|
||||
assert.equal(fixedModeRule.rule.pricingMode, 'fixed')
|
||||
assert.equal(fixedModeRule.rule.rewardAmount, 1300)
|
||||
assert.equal(fixedModeRule.rule.unitPriceFen, 0)
|
||||
|
||||
const enabled = await saveAdminWorkProductRule({
|
||||
ruleId,
|
||||
productName,
|
||||
|
||||
@@ -97,10 +97,7 @@ export async function syncWorkOrderTemplateProduct(
|
||||
ruleKey: rule.rule_key,
|
||||
categoryId: rule.category_id ? Number(rule.category_id) : null,
|
||||
templateEnabled: rule.enabled === true,
|
||||
pricingMode:
|
||||
Number(rule.unit_price_fen || 0) > 0 && Number(rule.reward_amount || 0) === 0
|
||||
? 'unit'
|
||||
: 'fixed',
|
||||
pricingMode: rule.pricing_mode === 'unit' ? 'unit' : 'fixed',
|
||||
rewardAmountFen: Number(rule.reward_amount || 0),
|
||||
unitPriceFen: Number(rule.unit_price_fen || 0),
|
||||
sharingEnabled: rule.sharing_enabled === true,
|
||||
|
||||
@@ -28,6 +28,7 @@ export function resolveWorkOrderRulePricing(input: {
|
||||
skuQuantity: number
|
||||
unitPriceFen: number
|
||||
fixedRewardAmount: number
|
||||
pricingMode: 'fixed' | 'unit' | string
|
||||
sharingEnabled: boolean
|
||||
sharingAutoFromOrder: boolean
|
||||
sharingTotalQuantity: number
|
||||
@@ -43,7 +44,7 @@ export function resolveWorkOrderRulePricing(input: {
|
||||
: Math.max(0, Number(input.sharingUnitReward) || 0)
|
||||
const rewardAmount = input.sharingEnabled
|
||||
? sharingTotalQuantity * sharingUnitReward
|
||||
: unitPriceFen > 0
|
||||
: input.pricingMode === 'unit'
|
||||
? unitPriceFen * skuQuantity
|
||||
: Math.max(0, Number(input.fixedRewardAmount) || 0)
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ function buildRule(overrides: Partial<WorkProductRuleRow> = {}): WorkProductRule
|
||||
auto_create: true,
|
||||
reward_amount: 0,
|
||||
unit_price_fen: 0,
|
||||
pricing_mode: 'fixed',
|
||||
required_deposit_amount: 0,
|
||||
deposit_threshold_amount: 20000,
|
||||
sharing_enabled: false,
|
||||
@@ -141,6 +142,7 @@ const commanderRule = buildRule({
|
||||
rule_key: 'bb',
|
||||
product_name: '指挥官密钥',
|
||||
unit_price_fen: 950,
|
||||
pricing_mode: 'unit',
|
||||
sharing_enabled: true,
|
||||
sharing_auto_from_order: true,
|
||||
})
|
||||
@@ -199,6 +201,7 @@ test('抖音:命中指挥官模板后按数量计价并开拼单(9.5 元/个
|
||||
skuQuantity,
|
||||
unitPriceFen: commanderRule.unit_price_fen,
|
||||
fixedRewardAmount: commanderRule.reward_amount,
|
||||
pricingMode: commanderRule.pricing_mode,
|
||||
sharingEnabled: commanderRule.sharing_enabled === true,
|
||||
sharingAutoFromOrder: commanderRule.sharing_auto_from_order === true,
|
||||
sharingTotalQuantity: commanderRule.sharing_total_quantity,
|
||||
|
||||
@@ -108,6 +108,7 @@ function buildWorkOrderPlan(
|
||||
skuQuantity,
|
||||
unitPriceFen,
|
||||
fixedRewardAmount: Number(rule.reward_amount || 0),
|
||||
pricingMode: rule.pricing_mode,
|
||||
sharingEnabled: rule.sharing_enabled === true,
|
||||
sharingAutoFromOrder: rule.sharing_auto_from_order === true,
|
||||
sharingTotalQuantity: Number(rule.sharing_total_quantity || 1),
|
||||
@@ -120,7 +121,7 @@ function buildWorkOrderPlan(
|
||||
return {
|
||||
skuQuantity,
|
||||
unitPriceFen,
|
||||
pricingMode: unitPriceFen > 0 ? 'unit' : 'fixed',
|
||||
pricingMode: rule.pricing_mode === 'unit' ? 'unit' : 'fixed',
|
||||
rewardAmount: pricing.rewardAmount,
|
||||
sharingEnabled,
|
||||
sharingTotalQuantity: sharingEnabled ? pricing.sharingTotalQuantity : 1,
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
upsertWorkProductRule,
|
||||
} from '../../repositories/worker-platform/work-product-rule-repo.js'
|
||||
import type { WorkOrderRow, WorkProductRuleRow } from '../../repositories/worker-platform/types.js'
|
||||
import { withTransaction } from '../../db/client.js'
|
||||
import { query, withTransaction } from '../../db/client.js'
|
||||
import {
|
||||
archiveDeletedWorkOrderTemplateProduct,
|
||||
assertWorkOrderTemplateCanEnable,
|
||||
@@ -394,6 +394,34 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
|
||||
const incomingUnitPriceFen = normalizeAmountFen(payload.unitPrice ?? payload.unitPriceYuan, 0)
|
||||
const existingRewardAmount = Number(existingRule?.reward_amount || 0)
|
||||
const existingUnitPriceFen = Number(existingRule?.unit_price_fen || 0)
|
||||
const rawPricingMode = String(payload.pricingMode ?? payload.pricing_mode ?? '').trim()
|
||||
if (rawPricingMode && rawPricingMode !== 'fixed' && rawPricingMode !== 'unit') {
|
||||
throw createHttpError('接单模板计价方式无效', {
|
||||
statusCode: 400,
|
||||
errorCode: 'work_product_rule_pricing_mode_invalid',
|
||||
})
|
||||
}
|
||||
const existingPricingMode = existingRule?.pricing_mode === 'unit' ? 'unit' : 'fixed'
|
||||
const pricingMode: 'fixed' | 'unit' =
|
||||
rawPricingMode === 'unit' ? 'unit' : rawPricingMode === 'fixed' ? 'fixed' : existingPricingMode
|
||||
let carriedPricingAmount =
|
||||
existingPricingMode === 'unit' ? existingUnitPriceFen : existingRewardAmount
|
||||
if (existingRule && pricingMode !== existingPricingMode) {
|
||||
const activeCostResult = await query<{ amount_fen: number }>(
|
||||
`SELECT amount_fen
|
||||
FROM cost_versions
|
||||
WHERE work_product_rule_id = $1
|
||||
AND scope_type = 'rule'
|
||||
AND status = 'active'
|
||||
AND effective_from <= NOW()
|
||||
AND (effective_to IS NULL OR effective_to > NOW())
|
||||
ORDER BY effective_from DESC, id DESC
|
||||
LIMIT 1`,
|
||||
[existingRule.id],
|
||||
)
|
||||
const activeCostAmount = Number(activeCostResult.rows[0]?.amount_fen || 0)
|
||||
if (activeCostAmount > 0) carriedPricingAmount = activeCostAmount
|
||||
}
|
||||
|
||||
// 模板保存只维护业务配置。价格只能由商品与成本中心的统一命令写入。
|
||||
if (
|
||||
@@ -409,8 +437,18 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
|
||||
},
|
||||
)
|
||||
}
|
||||
const rewardAmount = existingRewardAmount
|
||||
const unitPriceFen = existingUnitPriceFen
|
||||
const rewardAmount =
|
||||
pricingMode !== existingPricingMode || !existingRule
|
||||
? pricingMode === 'fixed'
|
||||
? carriedPricingAmount
|
||||
: 0
|
||||
: existingRewardAmount
|
||||
const unitPriceFen =
|
||||
pricingMode !== existingPricingMode || !existingRule
|
||||
? pricingMode === 'unit'
|
||||
? carriedPricingAmount
|
||||
: 0
|
||||
: existingUnitPriceFen
|
||||
const sharingEnabled = hasOwnPayload(payload, 'sharingEnabled')
|
||||
? normalizeBoolean(payload.sharingEnabled, false)
|
||||
: hasOwnPayload(payload, 'sharing_enabled')
|
||||
@@ -431,12 +469,14 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
|
||||
hasConfiguredSharingValue(payload.sharingTotalQuantity ?? payload.sharing_total_quantity) ||
|
||||
hasConfiguredSharingValue(payload.sharingUnitReward ?? payload.sharingUnitRewardYuan) ||
|
||||
hasConfiguredSharingValue(payload.sharingTotalAmount ?? payload.sharingTotalAmountYuan)
|
||||
const sharingAutoFromOrder = hasOwnPayload(payload, 'sharingAutoFromOrder')
|
||||
const requestedSharingAutoFromOrder = hasOwnPayload(payload, 'sharingAutoFromOrder')
|
||||
? normalizeBoolean(payload.sharingAutoFromOrder, false)
|
||||
: hasOwnPayload(payload, 'sharing_auto_from_order')
|
||||
? normalizeBoolean(payload.sharing_auto_from_order, false)
|
||||
: (existingRule?.sharing_auto_from_order === true && !hasManualSharingConfig) ||
|
||||
(!existingRule && sharingEnabled && unitPriceFen > 0 && !hasManualSharingConfig)
|
||||
(!existingRule && sharingEnabled && pricingMode === 'unit' && !hasManualSharingConfig)
|
||||
const sharingAutoFromOrder =
|
||||
pricingMode === 'unit' && requestedSharingAutoFromOrder && !hasManualSharingConfig
|
||||
const hasManualSharingQuantity = hasConfiguredSharingValue(
|
||||
payload.sharingTotalQuantity ?? payload.sharing_total_quantity,
|
||||
)
|
||||
@@ -445,7 +485,7 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
|
||||
)
|
||||
if (
|
||||
sharingEnabled &&
|
||||
unitPriceFen > 0 &&
|
||||
pricingMode === 'unit' &&
|
||||
!sharingAutoFromOrder &&
|
||||
hasManualSharingQuantity !== hasManualSharingUnitReward
|
||||
) {
|
||||
@@ -525,6 +565,7 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
|
||||
autoCreate: normalizeBoolean(payload.autoCreate ?? payload.auto_create, true),
|
||||
rewardAmount: finalRewardAmount,
|
||||
unitPriceFen,
|
||||
pricingMode,
|
||||
// 商品规则不配置押金;工单创建后由运营按需手工填写。
|
||||
requiredDepositAmount: 0,
|
||||
depositThresholdAmount: 0,
|
||||
@@ -585,6 +626,7 @@ export function resolveWorkOrderTemplateSyncPrice(input: {
|
||||
skuQuantity,
|
||||
unitPriceFen: Number(input.rule.unit_price_fen || 0),
|
||||
fixedRewardAmount: Number(input.rule.reward_amount || 0),
|
||||
pricingMode: input.rule.pricing_mode,
|
||||
sharingEnabled: input.rule.sharing_enabled === true,
|
||||
sharingAutoFromOrder: input.rule.sharing_auto_from_order === true,
|
||||
sharingTotalQuantity: Number(input.rule.sharing_total_quantity || 1),
|
||||
|
||||
@@ -16,6 +16,7 @@ function rule(
|
||||
sort_order: input.sort_order ?? 100,
|
||||
unit_price_fen: input.unit_price_fen ?? 0,
|
||||
reward_amount: input.reward_amount ?? 0,
|
||||
pricing_mode: input.pricing_mode ?? 'fixed',
|
||||
sharing_enabled: input.sharing_enabled ?? false,
|
||||
sharing_total_quantity: input.sharing_total_quantity ?? 1,
|
||||
sharing_unit_reward: input.sharing_unit_reward ?? 0,
|
||||
|
||||
@@ -231,6 +231,7 @@ export async function syncWorkerOrdersForSourceOrder(
|
||||
skuQuantity,
|
||||
unitPriceFen: Number(rule.unit_price_fen || 0),
|
||||
fixedRewardAmount: Number(rule.reward_amount || 0),
|
||||
pricingMode: rule.pricing_mode,
|
||||
sharingEnabled: rule.sharing_enabled === true,
|
||||
sharingAutoFromOrder: rule.sharing_auto_from_order === true,
|
||||
sharingTotalQuantity: Number(rule.sharing_total_quantity || 1),
|
||||
|
||||
@@ -64,6 +64,7 @@ export function mapWorkProductRule(rule: WorkProductRuleRow | null | undefined)
|
||||
autoCreate: Boolean(rule.auto_create),
|
||||
rewardAmount: Number(rule.reward_amount || 0),
|
||||
unitPriceFen: Number(rule.unit_price_fen || 0),
|
||||
pricingMode: rule.pricing_mode === 'unit' ? 'unit' : 'fixed',
|
||||
sharing: {
|
||||
enabled: rule.sharing_enabled === true,
|
||||
totalQuantity: Number(rule.sharing_total_quantity || 1),
|
||||
|
||||
@@ -745,6 +745,7 @@ function buildProductRule(overrides: Partial<WorkProductRuleRow> = {}): WorkProd
|
||||
auto_create: true,
|
||||
reward_amount: 2500,
|
||||
unit_price_fen: 0,
|
||||
pricing_mode: 'fixed',
|
||||
required_deposit_amount: 0,
|
||||
deposit_threshold_amount: 20000,
|
||||
sharing_enabled: false,
|
||||
@@ -1624,6 +1625,7 @@ test('按件拼单未手动覆盖时,使用匹配数量与模板单价', () =>
|
||||
skuQuantity: 10,
|
||||
unitPriceFen: 20,
|
||||
fixedRewardAmount: 0,
|
||||
pricingMode: 'unit',
|
||||
sharingEnabled: true,
|
||||
sharingAutoFromOrder: true,
|
||||
sharingTotalQuantity: 1,
|
||||
@@ -1642,6 +1644,7 @@ test('按件拼单手动覆盖数量和单价时,保留手工配置', () => {
|
||||
skuQuantity: 10,
|
||||
unitPriceFen: 20,
|
||||
fixedRewardAmount: 0,
|
||||
pricingMode: 'unit',
|
||||
sharingEnabled: true,
|
||||
sharingAutoFromOrder: false,
|
||||
sharingTotalQuantity: 3,
|
||||
@@ -1657,7 +1660,7 @@ test('按件拼单手动覆盖数量和单价时,保留手工配置', () => {
|
||||
|
||||
test('模板同步价格按工单来源规格重新计算普通按件单', () => {
|
||||
const price = resolveWorkOrderTemplateSyncPrice({
|
||||
rule: buildProductRule({ unit_price_fen: 200 }),
|
||||
rule: buildProductRule({ unit_price_fen: 200, pricing_mode: 'unit' }),
|
||||
workOrder: buildWorkOrderRow({
|
||||
product_name: '指挥官秘钥15个',
|
||||
material_json: { source: { skuQuantity: 8 } },
|
||||
@@ -1667,10 +1670,39 @@ test('模板同步价格按工单来源规格重新计算普通按件单', () =>
|
||||
assert.deepEqual(price, { rewardAmount: 1600 })
|
||||
})
|
||||
|
||||
test('模板同步价格以显式计价方式为准,不按两个金额字段反推', () => {
|
||||
const workOrder = buildWorkOrderRow({ product_name: '指挥官秘钥15个' })
|
||||
|
||||
assert.deepEqual(
|
||||
resolveWorkOrderTemplateSyncPrice({
|
||||
rule: buildProductRule({
|
||||
reward_amount: 2500,
|
||||
unit_price_fen: 200,
|
||||
pricing_mode: 'fixed',
|
||||
}),
|
||||
workOrder,
|
||||
}),
|
||||
{ rewardAmount: 2500 },
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
resolveWorkOrderTemplateSyncPrice({
|
||||
rule: buildProductRule({
|
||||
reward_amount: 2500,
|
||||
unit_price_fen: 200,
|
||||
pricing_mode: 'unit',
|
||||
}),
|
||||
workOrder,
|
||||
}),
|
||||
{ rewardAmount: 3000 },
|
||||
)
|
||||
})
|
||||
|
||||
test('模板同步价格为拼单返回新的份额单价', () => {
|
||||
const price = resolveWorkOrderTemplateSyncPrice({
|
||||
rule: buildProductRule({
|
||||
unit_price_fen: 200,
|
||||
pricing_mode: 'unit',
|
||||
sharing_enabled: true,
|
||||
sharing_auto_from_order: true,
|
||||
}),
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
AccountBookOutlined,
|
||||
ApartmentOutlined,
|
||||
BellOutlined,
|
||||
BranchesOutlined,
|
||||
BugOutlined,
|
||||
DashboardOutlined,
|
||||
FileSearchOutlined,
|
||||
@@ -182,6 +183,7 @@ export default function AdminLayout() {
|
||||
const isFinanceKey = [
|
||||
'/admin/sales-profit',
|
||||
'/admin/cost-center',
|
||||
'/admin/fulfillment-channel',
|
||||
'/admin/fund',
|
||||
'/admin/finance',
|
||||
'/admin/finance/ledger',
|
||||
@@ -198,6 +200,7 @@ export default function AdminLayout() {
|
||||
const financeSubItems: MenuProps['items'] = [
|
||||
{ key: '/admin/sales-profit', icon: <LineChartOutlined />, label: '销售毛利' },
|
||||
{ key: '/admin/cost-center', icon: <ApartmentOutlined />, label: '商品与成本' },
|
||||
{ key: '/admin/fulfillment-channel', icon: <BranchesOutlined />, label: '履约渠道' },
|
||||
{ key: '/admin/fund', icon: <WalletOutlined />, label: '钱包管理' },
|
||||
{ key: '/admin/finance', icon: <AccountBookOutlined />, label: '收支明细' },
|
||||
{ key: '/admin/finance/ledger', icon: <AuditOutlined />, label: '总账与对账' },
|
||||
@@ -846,6 +849,7 @@ function resolveSelectedKey(pathname: string) {
|
||||
if (pathname.startsWith('/admin/orders')) return '/admin/orders'
|
||||
if (pathname.startsWith('/admin/sales-profit')) return '/admin/sales-profit'
|
||||
if (pathname.startsWith('/admin/cost-center')) return '/admin/cost-center'
|
||||
if (pathname.startsWith('/admin/fulfillment-channel')) return '/admin/fulfillment-channel'
|
||||
if (pathname.startsWith('/admin/fund')) return '/admin/fund'
|
||||
if (pathname.startsWith('/admin/finance/ledger')) return '/admin/finance/ledger'
|
||||
if (pathname.startsWith('/admin/finance')) return '/admin/finance'
|
||||
|
||||
@@ -342,7 +342,6 @@ export function ProductCatalogPanel() {
|
||||
}
|
||||
|
||||
function openSetProductPrice(item: ProductCatalogItem) {
|
||||
const isTemplateProduct = item.canonicalSku.startsWith('tpl:')
|
||||
setPricingProduct(item)
|
||||
priceForm.resetFields()
|
||||
priceForm.setFieldsValue({
|
||||
@@ -350,7 +349,6 @@ export function ProductCatalogPanel() {
|
||||
item.activeCostAmountFen !== null && item.activeCostAmountFen !== undefined
|
||||
? (item.activeCostAmountFen / 100).toFixed(2)
|
||||
: undefined,
|
||||
pricingMode: isTemplateProduct ? item.templatePricingMode || 'fixed' : undefined,
|
||||
effectiveScope: 'immediate',
|
||||
changeReason: '在商品与成本中心统一调整价格',
|
||||
syncSnapshots: true,
|
||||
@@ -360,7 +358,6 @@ export function ProductCatalogPanel() {
|
||||
|
||||
async function submitProductPrice(values: {
|
||||
amountYuan: number | string
|
||||
pricingMode?: 'fixed' | 'unit'
|
||||
effectiveScope: 'immediate' | 'custom'
|
||||
effectiveFromCustom?: string
|
||||
changeReason?: string
|
||||
@@ -381,7 +378,6 @@ export function ProductCatalogPanel() {
|
||||
try {
|
||||
const response = await setAdminProductPrice(pricingProduct.id, {
|
||||
amountFen,
|
||||
pricingMode: values.pricingMode,
|
||||
effectiveFrom,
|
||||
changeReason: values.changeReason || '在商品与成本中心统一调整价格',
|
||||
syncOpenOrders: true,
|
||||
@@ -1022,17 +1018,15 @@ export function ProductCatalogPanel() {
|
||||
</div>
|
||||
|
||||
{pricingProduct.canonicalSku.startsWith('tpl:') ? (
|
||||
<Form.Item
|
||||
name="pricingMode"
|
||||
label="接单计价方式"
|
||||
rules={[{ required: true, message: '请选择计价方式' }]}
|
||||
extra="保存后会同步到接单模板;固定金额按整单,按件单价按 SKU 数量计算"
|
||||
>
|
||||
<Radio.Group optionType="button" buttonStyle="solid">
|
||||
<Radio.Button value="fixed">固定金额</Radio.Button>
|
||||
<Radio.Button value="unit">按件计价</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
message={`当前计价方式:${
|
||||
pricingProduct.templatePricingMode === 'unit' ? '按 SKU 按件' : '固定金额'
|
||||
}`}
|
||||
description="计价方式由接单模板统一设置;这里只维护对应金额。"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Form.Item
|
||||
@@ -1041,9 +1035,9 @@ export function ProductCatalogPanel() {
|
||||
pricingProduct.source === 'affiliate_dash'
|
||||
? '采购单价(元)'
|
||||
: pricingProduct.canonicalSku.startsWith('tpl:')
|
||||
? pricingProduct.templateSharingEnabled
|
||||
? '拼单总成本(元)'
|
||||
: '接单成本(元)'
|
||||
? pricingProduct.templatePricingMode === 'unit'
|
||||
? '每件接单成本(元)'
|
||||
: '整单接单成本(元)'
|
||||
: '标准结算单价(元)'
|
||||
}
|
||||
rules={[{ required: true, message: '请填写价格' }]}
|
||||
@@ -1051,7 +1045,9 @@ export function ProductCatalogPanel() {
|
||||
pricingProduct.source === 'affiliate_dash'
|
||||
? '上游每发货 1 件扣除的采购成本金额'
|
||||
: pricingProduct.canonicalSku.startsWith('tpl:')
|
||||
? '固定金额按整单计;按件计价时填写每个 SKU 的接单成本'
|
||||
? pricingProduct.templatePricingMode === 'unit'
|
||||
? `订单有 N 件时,本单接单成本 = 当前金额 × N`
|
||||
: '无论订单件数,当前金额就是整单接单成本'
|
||||
: '接单打手发单/验收的基础结算单价'
|
||||
}
|
||||
>
|
||||
|
||||
@@ -293,6 +293,7 @@ export default function ProductRulesPanel() {
|
||||
itemTitleMatchType: rule.match.itemTitleMatchType,
|
||||
skuNickMatchType: rule.match.skuNickMatchType,
|
||||
categoryId: rule.categoryId || undefined,
|
||||
pricingMode: rule.pricingMode || 'fixed',
|
||||
sharingEnabled: rule.sharing?.enabled || false,
|
||||
sharingAutoFromOrder: rule.sharing?.autoFromOrder || false,
|
||||
sharingTotalQuantity: hasManualSharingOverride ? rule.sharing?.totalQuantity : undefined,
|
||||
@@ -349,6 +350,7 @@ export default function ProductRulesPanel() {
|
||||
fieldsText?: string
|
||||
enabled?: boolean
|
||||
autoCreate?: boolean
|
||||
pricingMode?: 'fixed' | 'unit'
|
||||
sortOrder?: number
|
||||
}) {
|
||||
try {
|
||||
@@ -374,9 +376,7 @@ export default function ProductRulesPanel() {
|
||||
isFriendGift && giftCooldownUnit === 'minutes' ? Number(giftCooldownValue || 0) : 0,
|
||||
sharingEnabled: isFriendGift ? false : values.sharingEnabled === true,
|
||||
sharingAutoFromOrder:
|
||||
editingRule?.unitPriceFen !== undefined &&
|
||||
editingRule.unitPriceFen > 0 &&
|
||||
editingRule.rewardAmount === 0 &&
|
||||
values.pricingMode === 'unit' &&
|
||||
values.sharingEnabled === true &&
|
||||
!hasManualSharingOverride,
|
||||
})
|
||||
@@ -420,9 +420,12 @@ export default function ProductRulesPanel() {
|
||||
platform: rule.platform,
|
||||
productName: rule.productName,
|
||||
categoryId: rule.categoryId || undefined,
|
||||
pricingMode: rule.pricingMode,
|
||||
sharingEnabled: rule.sharing?.enabled || false,
|
||||
sharingAutoFromOrder:
|
||||
rule.unitPriceFen > 0 && rule.sharing?.enabled === true && !hasManualSharingOverride,
|
||||
rule.pricingMode === 'unit' &&
|
||||
rule.sharing?.enabled === true &&
|
||||
!hasManualSharingOverride,
|
||||
sharingTotalQuantity: hasManualSharingOverride ? rule.sharing?.totalQuantity : undefined,
|
||||
sharingUnitReward: hasManualSharingOverride
|
||||
? (rule.sharing?.unitReward || 0) / 100
|
||||
@@ -753,22 +756,17 @@ export default function ProductRulesPanel() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '接单金额',
|
||||
title: '接单价格',
|
||||
key: 'reward',
|
||||
width: '20%',
|
||||
render: (_, row) => (
|
||||
<div className="cell-stack">
|
||||
{row.rewardAmount > 0 || row.unitPriceFen > 0 ? (
|
||||
<>
|
||||
<Typography.Text strong style={{ color: '#3f8600', fontSize: 12 }}>
|
||||
{row.sharing?.autoFromOrder ? '随订单计算' : formatMoney(row.rewardAmount)}
|
||||
</Typography.Text>
|
||||
{row.unitPriceFen > 0 ? (
|
||||
<Typography.Text type="secondary" style={{ fontSize: 11 }}>
|
||||
按件: {formatMoney(row.unitPriceFen)}
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</>
|
||||
{(row.pricingMode === 'unit' ? row.unitPriceFen : row.rewardAmount) > 0 ? (
|
||||
<Typography.Text strong style={{ color: '#3f8600', fontSize: 12 }}>
|
||||
{row.pricingMode === 'unit'
|
||||
? `${formatMoney(row.unitPriceFen)} / 件`
|
||||
: formatMoney(row.rewardAmount)}
|
||||
</Typography.Text>
|
||||
) : (
|
||||
<Tag color="orange" style={{ width: 'fit-content', margin: 0 }}>
|
||||
待成本配置
|
||||
@@ -889,6 +887,7 @@ export default function ProductRulesPanel() {
|
||||
skuNickMatchType: 'exact',
|
||||
enabled: false,
|
||||
autoCreate: true,
|
||||
pricingMode: 'fixed',
|
||||
sharingEnabled: false,
|
||||
acceptanceMode: 'standard',
|
||||
giftCooldownUnit: 'hours',
|
||||
@@ -902,10 +901,10 @@ export default function ProductRulesPanel() {
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
message="接单模板只维护匹配和履约规则"
|
||||
message="接单模板维护计价方式和履约规则"
|
||||
description={
|
||||
<span>
|
||||
固定金额、按件单价和价格同步统一由
|
||||
在这里选择“固定金额”或“按 SKU 按件”;具体金额与价格同步统一由
|
||||
<a href="#/admin/cost-center" style={{ margin: '0 4px', fontWeight: 500 }}>
|
||||
商品与成本中心
|
||||
</a>
|
||||
@@ -940,6 +939,22 @@ export default function ProductRulesPanel() {
|
||||
<Form.Item label="任务时限" name="timeoutMinutes">
|
||||
<InputNumber min={0} step={5} addonAfter="分钟" className="full-width" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="计价方式"
|
||||
name="pricingMode"
|
||||
rules={[{ required: true, message: '请选择计价方式' }]}
|
||||
extra="固定金额按整单;按 SKU 按件会把成本中心金额乘以订单件数"
|
||||
>
|
||||
<Radio.Group
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
className="full-width"
|
||||
options={[
|
||||
{ value: 'fixed', label: '固定金额' },
|
||||
{ value: 'unit', label: '按 SKU 按件' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* 验收模式:好友赠送模式与拼单互斥 */}
|
||||
@@ -1047,9 +1062,7 @@ export default function ProductRulesPanel() {
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{({ getFieldValue }) => {
|
||||
const sharingEnabled = getFieldValue('sharingEnabled')
|
||||
const unitMode =
|
||||
Number(editingRule?.unitPriceFen || 0) > 0 &&
|
||||
Number(editingRule?.rewardAmount || 0) === 0
|
||||
const unitMode = getFieldValue('pricingMode') === 'unit'
|
||||
const friendGiftMode = getFieldValue('acceptanceMode') === 'friend_gift'
|
||||
const hasConfiguredPrice =
|
||||
Number(editingRule?.unitPriceFen || 0) > 0 ||
|
||||
|
||||
@@ -499,7 +499,6 @@ export function setAdminProductPrice(
|
||||
productId: number,
|
||||
payload: {
|
||||
amountFen: number
|
||||
pricingMode?: 'fixed' | 'unit'
|
||||
effectiveFrom?: string
|
||||
changeReason?: string
|
||||
syncOpenOrders?: boolean
|
||||
|
||||
@@ -290,6 +290,7 @@ export function saveAdminWorkProductRule(payload: {
|
||||
categoryId?: number
|
||||
enabled?: boolean
|
||||
autoCreate?: boolean
|
||||
pricingMode?: 'fixed' | 'unit'
|
||||
sharingEnabled?: boolean
|
||||
sharingAutoFromOrder?: boolean
|
||||
sharingTotalQuantity?: number
|
||||
|
||||
@@ -438,6 +438,7 @@ export type WorkProductRule = {
|
||||
autoCreate: boolean
|
||||
rewardAmount: number
|
||||
unitPriceFen: number
|
||||
pricingMode: 'fixed' | 'unit'
|
||||
sharing?: WorkOrderSharingConfig
|
||||
requirement: {
|
||||
fields: CollectField[]
|
||||
|
||||
Reference in New Issue
Block a user