优化按件拼单自动计算

This commit is contained in:
yml2213
2026-08-18 14:37:13 +08:00
parent c2305d419e
commit 7ccf3a6cee
9 changed files with 193 additions and 38 deletions
@@ -133,6 +133,7 @@ import {
resolveKuaishouWorkProductMatchContext,
resolveRequirementFields,
resolveSkuNameQuantity,
resolveWorkOrderRulePricing,
resolveWorkerPermissions,
} from './mappers.js'
import {
@@ -604,12 +605,47 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
})
}
const ruleId = normalizeOptionalId(payload.ruleId ?? payload.rule_id)
const existingRule = ruleId ? await getWorkProductRuleById(ruleId) : null
if (ruleId && !existingRule) {
throw createHttpError('接单模板不存在', {
statusCode: 404,
errorCode: 'work_product_rule_not_found',
})
}
const rewardAmount = normalizeAmountFen(payload.rewardAmount ?? payload.rewardAmountYuan, 0)
const unitPriceFen =
payload.unitPrice === undefined && payload.unitPriceYuan === undefined
? 0
: normalizeAmountFen(payload.unitPrice ?? payload.unitPriceYuan, 0)
const sharingEnabled = normalizeBoolean(payload.sharingEnabled ?? payload.sharing_enabled, false)
const hasManualSharingConfig =
hasConfiguredSharingValue(payload.sharingTotalQuantity ?? payload.sharing_total_quantity) ||
hasConfiguredSharingValue(payload.sharingUnitReward ?? payload.sharingUnitRewardYuan) ||
hasConfiguredSharingValue(payload.sharingTotalAmount ?? payload.sharingTotalAmountYuan)
const sharingAutoFromOrder =
sharingEnabled &&
unitPriceFen > 0 &&
(normalizeBoolean(payload.sharingAutoFromOrder ?? payload.sharing_auto_from_order, false) ||
(!hasManualSharingConfig && existingRule?.sharing_auto_from_order !== false))
const hasManualSharingQuantity = hasConfiguredSharingValue(
payload.sharingTotalQuantity ?? payload.sharing_total_quantity,
)
const hasManualSharingUnitReward = hasConfiguredSharingValue(
payload.sharingUnitReward ?? payload.sharingUnitRewardYuan,
)
if (
sharingEnabled &&
unitPriceFen > 0 &&
!sharingAutoFromOrder &&
hasManualSharingQuantity !== hasManualSharingUnitReward
) {
throw createHttpError('按件计价的拼单覆盖值请同时填写拼单数量和拼单单价', {
statusCode: 400,
errorCode: 'work_product_rule_sharing_override_incomplete',
})
}
const sharingUnitReward = normalizeAmountFen(
payload.sharingUnitReward ?? payload.sharingUnitRewardYuan,
0,
@@ -625,7 +661,7 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
)
const resolvedSharingTotalAmount =
sharingTotalAmount > 0 ? sharingTotalAmount : sharingUnitReward * sharingTotalQuantity
if (sharingEnabled) {
if (sharingEnabled && !sharingAutoFromOrder) {
if (resolvedSharingTotalAmount <= 0) {
throw createHttpError('启用拼单时请填写拼单总价或单价', {
statusCode: 400,
@@ -633,16 +669,11 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
})
}
}
const finalRewardAmount = sharingEnabled ? resolvedSharingTotalAmount : rewardAmount
const ruleId = normalizeOptionalId(payload.ruleId ?? payload.rule_id)
const existingRule = ruleId ? await getWorkProductRuleById(ruleId) : null
if (ruleId && !existingRule) {
throw createHttpError('接单模板不存在', {
statusCode: 404,
errorCode: 'work_product_rule_not_found',
})
}
const finalRewardAmount = sharingEnabled
? sharingAutoFromOrder
? 0
: resolvedSharingTotalAmount
: rewardAmount
const fields = normalizeRequirementFieldsFromPayload(payload)
const ruleKey = existingRule?.rule_key || randomId('rule-')
const rule = await upsertWorkProductRule({
@@ -658,7 +689,7 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
defaults.category?.id ||
null,
enabled: normalizeBoolean(payload.enabled, true),
autoCreate: normalizeBoolean(payload.autoCreate ?? payload.auto_create, false),
autoCreate: normalizeBoolean(payload.autoCreate ?? payload.auto_create, true),
rewardAmount: finalRewardAmount,
unitPriceFen,
// 商品规则不配置押金;工单创建后由运营按需手工填写。
@@ -667,6 +698,7 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
sharingEnabled,
sharingTotalQuantity,
sharingUnitReward,
sharingAutoFromOrder,
timeoutMinutes: Math.max(0, normalizeInteger(payload.timeoutMinutes, 0)),
timeoutPolicy: normalizeWorkOrderTimeoutPolicy(payload.timeoutPolicy),
requirementJson: JSON.stringify({ fields }),
@@ -1287,6 +1319,10 @@ function resolveSharingQuantity(
return Math.max(1, fallbackQuantity)
}
function hasConfiguredSharingValue(value: unknown) {
return value !== undefined && value !== null && String(value).trim() !== ''
}
export async function submitAdminWorkOrderMaterial(
workOrderId: number | string,
payload: JsonObject = {},
@@ -2070,8 +2106,15 @@ export async function syncWorkerOrdersForSourceOrder(
const skuQuantity = resolveSkuNameQuantity(item.sku_name)
const unitPriceFen = Number(rule.unit_price_fen || 0)
// 按数量计价:单价 × SKU 名称文字中的规格数量(如 指挥官秘钥15个);未启用则用固定接单金额
const rewardAmount =
unitPriceFen > 0 ? unitPriceFen * skuQuantity : Number(rule.reward_amount || 0)
const { sharingTotalQuantity, sharingUnitReward, rewardAmount } = resolveWorkOrderRulePricing({
skuQuantity,
unitPriceFen,
fixedRewardAmount: Number(rule.reward_amount || 0),
sharingEnabled: rule.sharing_enabled === true,
sharingAutoFromOrder: rule.sharing_auto_from_order === true,
sharingTotalQuantity: Number(rule.sharing_total_quantity || 1),
sharingUnitReward: Number(rule.sharing_unit_reward || 0),
})
const now = nowIso()
const materialComplete = fields.every((field) => !field.required)
const workOrder = await createWorkOrder({
@@ -2087,8 +2130,8 @@ export async function syncWorkerOrdersForSourceOrder(
requiredDepositAmount: 0,
depositThresholdAmount: 0,
sharingEnabled: rule.sharing_enabled === true,
sharingTotalQuantity: Number(rule.sharing_total_quantity || 1),
sharingUnitReward: Number(rule.sharing_unit_reward || 0),
sharingTotalQuantity,
sharingUnitReward,
timeoutMinutes: Number(rule.timeout_minutes || 0),
timeoutPolicy: String(rule.timeout_policy || 'reopen').trim(),
materialJson: JSON.stringify({
@@ -200,6 +200,7 @@ export function mapWorkProductRule(rule: WorkProductRuleRow | null | undefined)
enabled: rule.sharing_enabled === true,
totalQuantity: Number(rule.sharing_total_quantity || 1),
unitReward: Number(rule.sharing_unit_reward || 0),
autoFromOrder: rule.sharing_auto_from_order === true,
},
timeoutMinutes: Number(rule.timeout_minutes || 0),
timeoutPolicy: String(rule.timeout_policy || 'reopen').trim(),
@@ -990,6 +991,32 @@ export function resolveSkuNameQuantity(productName: unknown): number {
return 1
}
export function resolveWorkOrderRulePricing(input: {
skuQuantity: number
unitPriceFen: number
fixedRewardAmount: number
sharingEnabled: boolean
sharingAutoFromOrder: boolean
sharingTotalQuantity: number
sharingUnitReward: number
}) {
const skuQuantity = Math.max(1, Number(input.skuQuantity) || 1)
const unitPriceFen = Math.max(0, Number(input.unitPriceFen) || 0)
const sharingTotalQuantity = input.sharingAutoFromOrder
? skuQuantity
: Math.max(1, Number(input.sharingTotalQuantity) || 1)
const sharingUnitReward = input.sharingAutoFromOrder
? unitPriceFen
: Math.max(0, Number(input.sharingUnitReward) || 0)
const rewardAmount = input.sharingEnabled
? sharingTotalQuantity * sharingUnitReward
: unitPriceFen > 0
? unitPriceFen * skuQuantity
: Math.max(0, Number(input.fixedRewardAmount) || 0)
return { sharingTotalQuantity, sharingUnitReward, rewardAmount }
}
const CHINESE_DIGITS: Record<string, number> = {
: 0,
: 1,
@@ -20,6 +20,7 @@ import {
resolveMatchingProductRuleDecision,
resolveWorkerPermissions,
resolveSkuNameQuantity,
resolveWorkOrderRulePricing,
validateWorkerPassword,
} from './index.js'
@@ -87,6 +88,7 @@ function buildProductRule(overrides: Partial<WorkProductRuleRow> = {}): WorkProd
sharing_enabled: false,
sharing_total_quantity: 1,
sharing_unit_reward: 0,
sharing_auto_from_order: false,
timeout_minutes: 0,
timeout_policy: 'reopen',
requirement_json: {},
@@ -519,6 +521,42 @@ test('resolveSkuNameQuantity falls back to 1 when no quantity is found', () => {
assert.equal(resolveSkuNameQuantity('指挥官秘钥礼包'), 1)
})
test('按件拼单未手动覆盖时,使用匹配数量与模板单价', () => {
const pricing = resolveWorkOrderRulePricing({
skuQuantity: 10,
unitPriceFen: 20,
fixedRewardAmount: 0,
sharingEnabled: true,
sharingAutoFromOrder: true,
sharingTotalQuantity: 1,
sharingUnitReward: 0,
})
assert.deepEqual(pricing, {
sharingTotalQuantity: 10,
sharingUnitReward: 20,
rewardAmount: 200,
})
})
test('按件拼单手动覆盖数量和单价时,保留手工配置', () => {
const pricing = resolveWorkOrderRulePricing({
skuQuantity: 10,
unitPriceFen: 20,
fixedRewardAmount: 0,
sharingEnabled: true,
sharingAutoFromOrder: false,
sharingTotalQuantity: 3,
sharingUnitReward: 50,
})
assert.deepEqual(pricing, {
sharingTotalQuantity: 3,
sharingUnitReward: 50,
rewardAmount: 150,
})
})
function buildWorkOrderRow(overrides: Partial<WorkOrderRow> = {}): WorkOrderRow {
return {
id: 1,