优化按件拼单自动计算
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user