新增接单模板价格同步

This commit is contained in:
yml2213
2026-08-20 20:24:00 +08:00
parent 6539e0fb21
commit e05a6797a3
9 changed files with 437 additions and 3 deletions
@@ -36,6 +36,7 @@ import {
listWorkerFinanceRequests,
listWorkOrders,
listWorkOrderEventsByOrderId,
listOpenWorkOrdersByProductRuleId,
listWorkProductRules,
listWorkProductRuleMappings,
listWorkProductRuleMappingsPage,
@@ -47,6 +48,7 @@ import {
listWorkOrderShares,
listWorkOrderSharesByOrderIds,
sumPendingUnfreezeByOrderIds,
syncOpenWorkOrderPriceFromRule,
resolveProblemWorkOrder,
reviewWorkerFinanceRequest,
reopenCancelledWorkOrder,
@@ -67,6 +69,7 @@ import {
type WorkOrderRow,
type WorkProductMatchLogRow,
type WorkProductRuleMappingRow,
type WorkProductRuleRow,
type WorkerWithdrawalAccountRow,
} from '../../repositories/worker-platform/index.js'
import type { JsonObject } from '../../types/json.js'
@@ -881,6 +884,103 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
return { rule: mapWorkProductRule(rule) }
}
/** 根据工单创建时的规格,计算当前模板可同步的价格。 */
export function resolveWorkOrderTemplateSyncPrice(input: {
rule: WorkProductRuleRow
workOrder: WorkOrderRow
}): { rewardAmount: number; sharingUnitReward?: number } | null {
const material = safeParseJson(input.workOrder.material_json)
const source = safeParseJson(material.source)
const sourceSkuQuantity = Number(source.skuQuantity || 0)
const skuQuantity =
Number.isSafeInteger(sourceSkuQuantity) && sourceSkuQuantity > 0
? sourceSkuQuantity
: resolveSkuNameQuantity(input.workOrder.product_name)
const pricing = resolveWorkOrderRulePricing({
skuQuantity,
unitPriceFen: Number(input.rule.unit_price_fen || 0),
fixedRewardAmount: Number(input.rule.reward_amount || 0),
sharingEnabled: input.rule.sharing_enabled === true,
sharingAutoFromOrder: input.rule.sharing_auto_from_order === true,
sharingTotalQuantity: Number(input.rule.sharing_total_quantity || 1),
sharingUnitReward: Number(input.rule.sharing_unit_reward || 0),
})
if (input.workOrder.sharing_enabled === true) {
if (input.rule.sharing_enabled !== true || pricing.sharingUnitReward <= 0) {
return null
}
return {
rewardAmount: pricing.rewardAmount,
sharingUnitReward: pricing.sharingUnitReward,
}
}
return pricing.rewardAmount > 0 ? { rewardAmount: pricing.rewardAmount } : null
}
/** 将指定模板的当前价格同步到仍在接单大厅的来源工单。 */
export async function syncAdminWorkProductRulePrice(ruleId: number | string, actorName = '') {
const normalizedRuleId = normalizeOptionalId(ruleId)
const rule = normalizedRuleId ? await getWorkProductRuleById(normalizedRuleId) : null
if (!rule) {
throw createHttpError('接单模板不存在', {
statusCode: 404,
errorCode: 'work_product_rule_not_found',
})
}
const candidates = await listOpenWorkOrdersByProductRuleId(rule.id)
const result = {
candidateCount: candidates.length,
updatedCount: 0,
updatedNormalCount: 0,
updatedPartialSharingCount: 0,
skippedInvalidPricingCount: 0,
skippedUnchangedCount: 0,
skippedUnavailableCount: 0,
}
const now = nowIso()
for (const workOrder of candidates) {
const price = resolveWorkOrderTemplateSyncPrice({ rule, workOrder })
if (!price) {
result.skippedInvalidPricingCount += 1
continue
}
const synced = await syncOpenWorkOrderPriceFromRule({
workOrderId: Number(workOrder.id),
productRuleId: Number(rule.id),
rewardAmount: price.rewardAmount,
...(price.sharingUnitReward === undefined
? {}
: { sharingUnitReward: price.sharingUnitReward }),
actorName,
now,
})
if (!synced.updated) {
if (synced.reason === 'price_unchanged') {
result.skippedUnchangedCount += 1
} else {
result.skippedUnavailableCount += 1
}
continue
}
result.updatedCount += 1
if (synced.sharingPartial) {
result.updatedPartialSharingCount += 1
} else {
result.updatedNormalCount += 1
}
publishWorkOrderRealtimeChange({
workOrderId: synced.workOrderId,
workerIds: synced.workerIds,
hallChanged: true,
})
}
return { rule: mapWorkProductRule(rule), ...result }
}
export async function deleteAdminWorkProductRule(ruleId: number | string) {
const normalizedRuleId = normalizeOptionalId(ruleId)
if (!normalizedRuleId) {
@@ -2584,6 +2684,7 @@ export async function syncWorkerOrdersForSourceOrder(
const materialComplete = fields.every((field) => !field.required)
const workOrder = await createWorkOrder({
workOrderNo: randomId('WO'),
productRuleId: Number(rule.id),
orderId: Number(order.id),
orderItemId: Number(item.id),
taskId: null,
@@ -11,6 +11,7 @@ import type {
import {
resolveWorkOrderShareCancellationStatus,
resolveWorkOrderShareJoinQuantity,
resolveOpenSharingSyncReward,
} from '../../repositories/worker-platform/index.js'
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
import {
@@ -30,6 +31,7 @@ import {
resolveWorkerPermissions,
resolveSkuNameQuantity,
resolveWorkOrderRulePricing,
resolveWorkOrderTemplateSyncPrice,
resolveWorkOrderSharingEnabled,
validateWorkerPassword,
} from './index.js'
@@ -592,6 +594,47 @@ test('按件拼单手动覆盖数量和单价时,保留手工配置', () => {
})
})
test('模板同步价格按工单来源规格重新计算普通按件单', () => {
const price = resolveWorkOrderTemplateSyncPrice({
rule: buildProductRule({ unit_price_fen: 200 }),
workOrder: buildWorkOrderRow({
product_name: '指挥官秘钥15个',
material_json: { source: { skuQuantity: 8 } },
}),
})
assert.deepEqual(price, { rewardAmount: 1600 })
})
test('模板同步价格为拼单返回新的份额单价', () => {
const price = resolveWorkOrderTemplateSyncPrice({
rule: buildProductRule({
unit_price_fen: 200,
sharing_enabled: true,
sharing_auto_from_order: true,
}),
workOrder: buildWorkOrderRow({
product_name: '指挥官秘钥15个',
sharing_enabled: true,
sharing_total_quantity: 15,
}),
})
assert.deepEqual(price, { rewardAmount: 3000, sharingUnitReward: 200 })
})
test('部分拼单同步后总报酬保留已加入份额的旧价格', () => {
assert.equal(
resolveOpenSharingSyncReward({
totalQuantity: 10,
joinedQuantity: 4,
lockedShareReward: 4000,
nextUnitReward: 1200,
}),
11_200,
)
})
test('模板启用拼单但实际只有一份时生成普通工单', () => {
assert.equal(resolveWorkOrderSharingEnabled(true, 1), false)
})