新增接单模板价格同步
This commit is contained in:
@@ -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)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user