合并远程 main 最新改动
This commit is contained in:
@@ -33,7 +33,12 @@ const AFTER_SALES_CASE_SELECT = `
|
||||
) AS shop_name,
|
||||
wu.username AS worker_username,
|
||||
wu.display_name AS worker_display_name,
|
||||
wos.quantity AS share_quantity
|
||||
wos.quantity AS share_quantity,
|
||||
-- 售后追缴口径:整单取工单发单金额,拼单取责任份额的一份价格与合计金额
|
||||
wo.reward_amount AS work_order_reward_amount,
|
||||
wo.sharing_enabled AS work_order_sharing_enabled,
|
||||
wos.unit_reward AS share_unit_reward,
|
||||
wos.share_reward AS share_reward_amount
|
||||
FROM worker_after_sales_cases ac
|
||||
INNER JOIN work_orders wo ON wo.id = ac.work_order_id
|
||||
LEFT JOIN orders o ON o.id = wo.order_id
|
||||
|
||||
@@ -201,6 +201,10 @@ export type WorkerAfterSalesCaseRow = {
|
||||
worker_username?: string
|
||||
worker_display_name?: string
|
||||
share_quantity?: number | null
|
||||
work_order_reward_amount?: number | string | null
|
||||
work_order_sharing_enabled?: boolean | null
|
||||
share_unit_reward?: number | string | null
|
||||
share_reward_amount?: number | string | null
|
||||
}
|
||||
|
||||
export type WorkerAfterSalesCaseEventRow = {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 售后默认追缴金额口径(单位:分):
|
||||
* - 整单售后(没有责任份额):取工单发单金额 work_orders.reward_amount;
|
||||
* - 拼单售后:取该责任份额的一份价格 work_order_shares.unit_reward,
|
||||
* 历史上 unit_reward 可能为 0,此时回退为 share_reward / quantity(至少 1 份)。
|
||||
*
|
||||
* 注意:这里只给前端一个建议默认值,最终追缴金额仍由管理员在弹窗里确认/修改。
|
||||
*/
|
||||
export function resolveAfterSalesSuggestedRecoveryFen(input: {
|
||||
shareId: number | null
|
||||
shareUnitReward: number
|
||||
shareRewardAmount: number
|
||||
shareQuantity: number | null
|
||||
workOrderRewardAmount: number
|
||||
}): number {
|
||||
const workOrderRewardAmount = Math.max(0, Math.round(Number(input.workOrderRewardAmount || 0)))
|
||||
if (!input.shareId) return workOrderRewardAmount
|
||||
|
||||
const unitReward = Math.max(0, Math.round(Number(input.shareUnitReward || 0)))
|
||||
if (unitReward > 0) return unitReward
|
||||
|
||||
const quantity = Math.max(1, Math.round(Number(input.shareQuantity || 1)))
|
||||
return Math.max(0, Math.round(Number(input.shareRewardAmount || 0) / quantity))
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import test from 'node:test'
|
||||
|
||||
import { resolveAfterSalesSuggestedRecoveryFen } from './after-sales-recovery-amount.js'
|
||||
|
||||
test('整单售后:默认追缴金额取工单发单金额', () => {
|
||||
assert.equal(
|
||||
resolveAfterSalesSuggestedRecoveryFen({
|
||||
shareId: null,
|
||||
shareUnitReward: 0,
|
||||
shareRewardAmount: 0,
|
||||
shareQuantity: null,
|
||||
workOrderRewardAmount: 12_800,
|
||||
}),
|
||||
12_800,
|
||||
)
|
||||
})
|
||||
|
||||
test('拼单售后:默认追缴金额取该责任份额的一份价格', () => {
|
||||
assert.equal(
|
||||
resolveAfterSalesSuggestedRecoveryFen({
|
||||
shareId: 99,
|
||||
shareUnitReward: 3_200,
|
||||
shareRewardAmount: 9_600,
|
||||
shareQuantity: 3,
|
||||
workOrderRewardAmount: 12_800,
|
||||
}),
|
||||
3_200,
|
||||
)
|
||||
})
|
||||
|
||||
test('拼单售后:一份价格缺失时回退为 合计 / 份数', () => {
|
||||
assert.equal(
|
||||
resolveAfterSalesSuggestedRecoveryFen({
|
||||
shareId: 99,
|
||||
shareUnitReward: 0,
|
||||
shareRewardAmount: 9_600,
|
||||
shareQuantity: 3,
|
||||
workOrderRewardAmount: 12_800,
|
||||
}),
|
||||
3_200,
|
||||
)
|
||||
})
|
||||
|
||||
test('拼单售后:份数缺失时按 1 份处理,不出现除零', () => {
|
||||
assert.equal(
|
||||
resolveAfterSalesSuggestedRecoveryFen({
|
||||
shareId: 99,
|
||||
shareUnitReward: 0,
|
||||
shareRewardAmount: 4_500,
|
||||
shareQuantity: null,
|
||||
workOrderRewardAmount: 4_500,
|
||||
}),
|
||||
4_500,
|
||||
)
|
||||
})
|
||||
|
||||
test('金额异常(负数、NaN)一律归零,不产生负数追缴', () => {
|
||||
assert.equal(
|
||||
resolveAfterSalesSuggestedRecoveryFen({
|
||||
shareId: null,
|
||||
shareUnitReward: 0,
|
||||
shareRewardAmount: 0,
|
||||
shareQuantity: null,
|
||||
workOrderRewardAmount: -100,
|
||||
}),
|
||||
0,
|
||||
)
|
||||
assert.equal(
|
||||
resolveAfterSalesSuggestedRecoveryFen({
|
||||
shareId: 1,
|
||||
shareUnitReward: Number.NaN,
|
||||
shareRewardAmount: Number.NaN,
|
||||
shareQuantity: 2,
|
||||
workOrderRewardAmount: 0,
|
||||
}),
|
||||
0,
|
||||
)
|
||||
})
|
||||
|
||||
test('售后列表 SQL 必须带出发单金额与责任份额金额,否则处置弹窗拿不到默认值', async () => {
|
||||
const source = await readFile(
|
||||
new URL('../../repositories/worker-platform/after-sales-repo.ts', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
for (const fragment of [
|
||||
'wo.reward_amount AS work_order_reward_amount',
|
||||
'wo.sharing_enabled AS work_order_sharing_enabled',
|
||||
'wos.unit_reward AS share_unit_reward',
|
||||
'wos.share_reward AS share_reward_amount',
|
||||
]) {
|
||||
assert.ok(source.includes(fragment), `AFTER_SALES_CASE_SELECT 缺少 ${fragment}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('售后 mapper 必须返回建议追缴金额,供前端默认填充', async () => {
|
||||
const source = await readFile(new URL('./worker-finance-mappers.ts', import.meta.url), 'utf8')
|
||||
assert.ok(
|
||||
source.includes('suggestedRecoveryAmount: resolveAfterSalesSuggestedRecoveryFen({'),
|
||||
'mapWorkerAfterSalesCase 必须计算 suggestedRecoveryAmount',
|
||||
)
|
||||
assert.ok(source.includes('workOrderRewardAmount,'), 'mapper 必须返回 workOrderRewardAmount')
|
||||
assert.ok(source.includes('shareUnitReward,'), 'mapper 必须返回 shareUnitReward')
|
||||
assert.ok(source.includes('shareRewardAmount,'), 'mapper 必须返回 shareRewardAmount')
|
||||
})
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import { safeParseJson } from '../admin/admin-query-utils.js'
|
||||
import { refreshUploadedFileUrls } from '../file-storage/file-storage-service.js'
|
||||
import { resolveAfterSalesSuggestedRecoveryFen } from './after-sales-recovery-amount.js'
|
||||
import { createWorkerSessionToken } from './worker-auth-utils.js'
|
||||
import {
|
||||
normalizeBoolean,
|
||||
@@ -227,20 +228,45 @@ export function mapWorkerAfterSalesCase(afterSalesCase: {
|
||||
worker_username?: string
|
||||
worker_display_name?: string
|
||||
share_quantity?: number | null
|
||||
work_order_reward_amount?: number | string | null
|
||||
work_order_sharing_enabled?: boolean | null
|
||||
share_unit_reward?: number | string | null
|
||||
share_reward_amount?: number | string | null
|
||||
}) {
|
||||
const snapshot = safeParseJson(afterSalesCase.acceptance_snapshot_json)
|
||||
const shareId = afterSalesCase.work_order_share_id
|
||||
? Number(afterSalesCase.work_order_share_id)
|
||||
: null
|
||||
const shareQuantity = afterSalesCase.share_quantity ? Number(afterSalesCase.share_quantity) : null
|
||||
const workOrderRewardAmount = Math.max(
|
||||
0,
|
||||
Math.round(Number(afterSalesCase.work_order_reward_amount || 0)),
|
||||
)
|
||||
const shareUnitReward = Math.max(0, Math.round(Number(afterSalesCase.share_unit_reward || 0)))
|
||||
const shareRewardAmount = Math.max(0, Math.round(Number(afterSalesCase.share_reward_amount || 0)))
|
||||
return {
|
||||
caseId: Number(afterSalesCase.id),
|
||||
caseNo: String(afterSalesCase.case_no || ''),
|
||||
workOrderId: Number(afterSalesCase.work_order_id),
|
||||
shareId: afterSalesCase.work_order_share_id ? Number(afterSalesCase.work_order_share_id) : null,
|
||||
shareId,
|
||||
workerId: Number(afterSalesCase.worker_id),
|
||||
workOrderNo: String(afterSalesCase.work_order_no || ''),
|
||||
platformOrderId: String(afterSalesCase.platform_order_id || ''),
|
||||
productName: String(afterSalesCase.product_name || ''),
|
||||
shopName: String(afterSalesCase.shop_name || ''),
|
||||
workerName: String(afterSalesCase.worker_display_name || afterSalesCase.worker_username || ''),
|
||||
shareQuantity: afterSalesCase.share_quantity ? Number(afterSalesCase.share_quantity) : null,
|
||||
shareQuantity,
|
||||
workOrderRewardAmount,
|
||||
sharingEnabled: Boolean(afterSalesCase.work_order_sharing_enabled),
|
||||
shareUnitReward,
|
||||
shareRewardAmount,
|
||||
suggestedRecoveryAmount: resolveAfterSalesSuggestedRecoveryFen({
|
||||
shareId,
|
||||
shareUnitReward,
|
||||
shareRewardAmount,
|
||||
shareQuantity,
|
||||
workOrderRewardAmount,
|
||||
}),
|
||||
problemType: String(afterSalesCase.problem_type || ''),
|
||||
complaintNote: String(afterSalesCase.complaint_note || ''),
|
||||
complaintFiles: normalizeUploadedFiles(safeParseJson(afterSalesCase.complaint_files_json)).map(
|
||||
|
||||
Reference in New Issue
Block a user