新增接单模板价格同步
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
-- 045_work_order_source_product_rule.sql —— 记录工单的来源接单模板,支持后续按模板同步价格。
|
||||
|
||||
ALTER TABLE work_orders
|
||||
ADD COLUMN IF NOT EXISTS product_rule_id BIGINT
|
||||
REFERENCES work_product_rules(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_work_orders_product_rule_open
|
||||
ON work_orders(product_rule_id, id DESC)
|
||||
WHERE status = 'open';
|
||||
|
||||
-- 历史自动建单已在 source_synced 事件中记录 ruleKey,迁移时尽可能补齐关联。
|
||||
UPDATE work_orders wo
|
||||
SET product_rule_id = wpr.id
|
||||
FROM work_product_rules wpr
|
||||
WHERE wo.product_rule_id IS NULL
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM work_order_events event
|
||||
WHERE event.work_order_id = wo.id
|
||||
AND event.event_type = 'source_synced'
|
||||
AND event.payload_json ->> 'ruleKey' = wpr.rule_key
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN work_orders.product_rule_id
|
||||
IS '自动建单时命中的接单模板;用于模板价格同步,删除模板后保留工单但置空';
|
||||
@@ -260,6 +260,8 @@ export type WorkOrderShareRow = {
|
||||
export type WorkOrderRow = {
|
||||
id: number
|
||||
work_order_no: string
|
||||
/** 自动建单命中的接单模板;历史或人工工单可能为空。 */
|
||||
product_rule_id?: number | null
|
||||
order_id: number | null
|
||||
order_item_id: number | null
|
||||
task_id: number | null
|
||||
@@ -411,6 +413,7 @@ export type CreateWorkerInput = {
|
||||
|
||||
export type CreateWorkOrderInput = {
|
||||
workOrderNo: string
|
||||
productRuleId?: number | null
|
||||
orderId?: number | null
|
||||
orderItemId?: number | null
|
||||
taskId?: number | null
|
||||
|
||||
@@ -867,6 +867,183 @@ export async function getWorkProductRuleById(
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
/** 查询仍在接单大厅、且明确来源于指定模板的工单。 */
|
||||
export async function listOpenWorkOrdersByProductRuleId(
|
||||
ruleId: number | string,
|
||||
): Promise<WorkOrderRow[]> {
|
||||
const result = await query<WorkOrderRow>(
|
||||
`${WORK_ORDER_SELECT}
|
||||
WHERE wo.product_rule_id = $1
|
||||
AND wo.status = 'open'
|
||||
ORDER BY wo.id ASC`,
|
||||
[Number(ruleId)],
|
||||
)
|
||||
return result.rows
|
||||
}
|
||||
|
||||
export type SyncOpenWorkOrderPriceResult = {
|
||||
updated: boolean
|
||||
reason: 'updated' | 'price_unchanged' | 'not_open' | 'assigned' | 'sharing_filled'
|
||||
workOrderId: number
|
||||
sharingPartial: boolean
|
||||
workerIds: number[]
|
||||
}
|
||||
|
||||
/** 拼单改价时保留已加入份额的报酬,只按新价计算剩余份额。 */
|
||||
export function resolveOpenSharingSyncReward(input: {
|
||||
totalQuantity: number
|
||||
joinedQuantity: number
|
||||
lockedShareReward: number
|
||||
nextUnitReward: number
|
||||
}) {
|
||||
const totalQuantity = toPositiveInteger(input.totalQuantity, 1)
|
||||
const joinedQuantity = Math.min(totalQuantity, Math.max(0, Number(input.joinedQuantity || 0)))
|
||||
return (
|
||||
Math.max(0, Number(input.lockedShareReward || 0)) +
|
||||
(totalQuantity - joinedQuantity) * toPositiveInteger(input.nextUnitReward, 0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步大厅工单的模板价格。
|
||||
* 已加入的拼单份额报酬是合同快照,只更新尚未加入的剩余份额价格。
|
||||
*/
|
||||
export async function syncOpenWorkOrderPriceFromRule(input: {
|
||||
workOrderId: number
|
||||
productRuleId: number
|
||||
rewardAmount: number
|
||||
sharingUnitReward?: number
|
||||
actorName: string
|
||||
now: string
|
||||
}): Promise<SyncOpenWorkOrderPriceResult> {
|
||||
return withTransaction(async (client) => {
|
||||
const orderResult = await client.query<WorkOrderRow>(
|
||||
`SELECT *
|
||||
FROM work_orders
|
||||
WHERE id = $1 AND product_rule_id = $2
|
||||
FOR UPDATE`,
|
||||
[input.workOrderId, input.productRuleId],
|
||||
)
|
||||
const workOrder = orderResult.rows[0] || null
|
||||
if (!workOrder || workOrder.status !== 'open') {
|
||||
return {
|
||||
updated: false,
|
||||
reason: 'not_open',
|
||||
workOrderId: input.workOrderId,
|
||||
sharingPartial: false,
|
||||
workerIds: [],
|
||||
}
|
||||
}
|
||||
if (workOrder.assigned_worker_id) {
|
||||
return {
|
||||
updated: false,
|
||||
reason: 'assigned',
|
||||
workOrderId: input.workOrderId,
|
||||
sharingPartial: false,
|
||||
workerIds: [],
|
||||
}
|
||||
}
|
||||
|
||||
const sharesResult = await client.query<WorkOrderShareRow>(
|
||||
`SELECT *
|
||||
FROM work_order_shares
|
||||
WHERE work_order_id = $1
|
||||
FOR UPDATE`,
|
||||
[workOrder.id],
|
||||
)
|
||||
const activeShares = sharesResult.rows.filter((share) => share.status !== 'cancelled')
|
||||
const joinedQuantity = activeShares.reduce(
|
||||
(sum, share) => sum + Math.max(0, Number(share.quantity || 0)),
|
||||
0,
|
||||
)
|
||||
const workerIds = [
|
||||
...new Set(
|
||||
activeShares.map((share) => Number(share.worker_id)).filter((workerId) => workerId > 0),
|
||||
),
|
||||
]
|
||||
const sharingPartial = workOrder.sharing_enabled === true && joinedQuantity > 0
|
||||
let nextRewardAmount = toPositiveInteger(input.rewardAmount, 0)
|
||||
let nextSharingUnitReward = Number(workOrder.sharing_unit_reward || 0)
|
||||
|
||||
if (workOrder.sharing_enabled === true) {
|
||||
const totalQuantity = toPositiveInteger(workOrder.sharing_total_quantity, 1)
|
||||
if (joinedQuantity >= totalQuantity) {
|
||||
return {
|
||||
updated: false,
|
||||
reason: 'sharing_filled',
|
||||
workOrderId: Number(workOrder.id),
|
||||
sharingPartial,
|
||||
workerIds,
|
||||
}
|
||||
}
|
||||
nextSharingUnitReward = toPositiveInteger(input.sharingUnitReward, 0)
|
||||
const lockedShareReward = activeShares.reduce(
|
||||
(sum, share) => sum + Math.max(0, Number(share.share_reward || 0)),
|
||||
0,
|
||||
)
|
||||
nextRewardAmount = resolveOpenSharingSyncReward({
|
||||
totalQuantity,
|
||||
joinedQuantity,
|
||||
lockedShareReward,
|
||||
nextUnitReward: nextSharingUnitReward,
|
||||
})
|
||||
}
|
||||
|
||||
const unchanged =
|
||||
Number(workOrder.reward_amount || 0) === nextRewardAmount &&
|
||||
(!workOrder.sharing_enabled ||
|
||||
Number(workOrder.sharing_unit_reward || 0) === nextSharingUnitReward)
|
||||
if (unchanged) {
|
||||
return {
|
||||
updated: false,
|
||||
reason: 'price_unchanged',
|
||||
workOrderId: Number(workOrder.id),
|
||||
sharingPartial,
|
||||
workerIds,
|
||||
}
|
||||
}
|
||||
|
||||
await client.query(
|
||||
`UPDATE work_orders
|
||||
SET reward_amount = $1,
|
||||
sharing_unit_reward = $2,
|
||||
updated_at = $3
|
||||
WHERE id = $4`,
|
||||
[nextRewardAmount, nextSharingUnitReward, input.now, workOrder.id],
|
||||
)
|
||||
await client.query(
|
||||
`INSERT INTO work_order_events (
|
||||
work_order_id, actor_type, actor_id, event_type, from_status, to_status, payload_json, created_at
|
||||
) VALUES ($1, 'admin', $2, 'price_synced_from_rule', $3, $3, $4::jsonb, $5)`,
|
||||
[
|
||||
workOrder.id,
|
||||
input.actorName,
|
||||
workOrder.status,
|
||||
JSON.stringify({
|
||||
productRuleId: input.productRuleId,
|
||||
previousRewardAmount: Number(workOrder.reward_amount || 0),
|
||||
rewardAmount: nextRewardAmount,
|
||||
previousSharingUnitReward: Number(workOrder.sharing_unit_reward || 0),
|
||||
sharingUnitReward: nextSharingUnitReward,
|
||||
joinedQuantity,
|
||||
lockedShareReward: activeShares.reduce(
|
||||
(sum, share) => sum + Math.max(0, Number(share.share_reward || 0)),
|
||||
0,
|
||||
),
|
||||
}),
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
return {
|
||||
updated: true,
|
||||
reason: 'updated',
|
||||
workOrderId: Number(workOrder.id),
|
||||
sharingPartial,
|
||||
workerIds,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function deleteWorkProductRule(
|
||||
ruleId: number | string,
|
||||
): Promise<{ deleted: boolean }> {
|
||||
@@ -986,19 +1163,20 @@ export async function createWorkOrder(input: CreateWorkOrderInput): Promise<Work
|
||||
const result = await query<{ id: number }>(
|
||||
`
|
||||
INSERT INTO work_orders (
|
||||
work_order_no, order_id, order_item_id, task_id, platform_order_id,
|
||||
work_order_no, product_rule_id, order_id, order_item_id, task_id, platform_order_id,
|
||||
product_name, category_id, status, reward_amount, required_deposit_amount,
|
||||
deposit_threshold_amount, sharing_enabled, sharing_total_quantity,
|
||||
sharing_unit_reward, timeout_minutes, timeout_policy,
|
||||
material_json, requirement_json, created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
|
||||
$11, $12, $13, $14, $15, $16, $17::jsonb, $18::jsonb, $19, $20
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11,
|
||||
$12, $13, $14, $15, $16, $17, $18::jsonb, $19::jsonb, $20, $21
|
||||
)
|
||||
RETURNING id
|
||||
`,
|
||||
[
|
||||
input.workOrderNo,
|
||||
input.productRuleId || null,
|
||||
input.orderId || null,
|
||||
input.orderItemId || null,
|
||||
input.taskId || null,
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
saveAdminWorkCategory,
|
||||
saveAdminWorkProductRuleMapping,
|
||||
saveAdminWorkProductRule,
|
||||
syncAdminWorkProductRulePrice,
|
||||
testAdminKuaishouProductMatch,
|
||||
saveAdminWorkerLevel,
|
||||
submitAdminWorkOrderMaterial,
|
||||
@@ -186,6 +187,29 @@ router.post(
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/product-rules/:ruleId/sync-price',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
syncAdminWorkProductRulePrice(
|
||||
String(req.params.ruleId || ''),
|
||||
req.adminSession?.username || '',
|
||||
),
|
||||
{
|
||||
successMessage: '模板价格已同步',
|
||||
errorMessage: '同步模板价格失败',
|
||||
scope: '[admin/worker-platform/product-rules/:ruleId/sync-price]',
|
||||
audit: (req, data) => ({
|
||||
action: 'work_product_rule_price_synced',
|
||||
targetType: 'work_product_rule',
|
||||
targetId: String(req.params.ruleId || ''),
|
||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/worker-platform/product-rules/:ruleId',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -230,6 +230,16 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
||||
return { title: '更改了订单信息', color: 'default' }
|
||||
case 'sharing_config_updated':
|
||||
return { title: '更新了拼单配置', color: 'default' }
|
||||
case 'price_synced_from_rule': {
|
||||
const rewardAmount = Number(readPayload(event, 'rewardAmount') || 0)
|
||||
const unitReward = Number(readPayload(event, 'sharingUnitReward') || 0)
|
||||
return {
|
||||
title: unitReward > 0 ? '从接单模板同步了拼单剩余份额价格' : '从接单模板同步了接单价格',
|
||||
description:
|
||||
rewardAmount > 0 ? `同步后预计总报酬:${(rewardAmount / 100).toFixed(2)} 元` : undefined,
|
||||
color: 'default',
|
||||
}
|
||||
}
|
||||
case 'sharing_share_cancelled_by_admin': {
|
||||
const workerId = readPayload(event, 'workerId')
|
||||
const quantity = readPayload(event, 'quantity')
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Radio,
|
||||
Select,
|
||||
@@ -38,6 +39,7 @@ import {
|
||||
fetchAdminWorkProductRules,
|
||||
reprocessAdminKuaishouWorkOrderMatches,
|
||||
saveAdminWorkProductRule,
|
||||
syncAdminWorkProductRulePrice,
|
||||
} from '@/services/admin'
|
||||
import type { CollectField, WorkProductRule, WorkProductRuleMapping } from '@/types/worker-platform'
|
||||
import type { AdminKuaishouIndustryShopOption } from '@/types/admin'
|
||||
@@ -54,6 +56,7 @@ export default function ProductRulesPanel() {
|
||||
const hasInitializedRuleSelection = useRef(false)
|
||||
|
||||
const [editingRule, setEditingRule] = useState<WorkProductRule | null>(null)
|
||||
const [syncingPrice, setSyncingPrice] = useState(false)
|
||||
const [activeMappingId, setActiveMappingId] = useState<number | 'new' | null>(null)
|
||||
const [sourcePrefill, setSourcePrefill] = useState<SourcePrefill | null>(null)
|
||||
|
||||
@@ -282,6 +285,36 @@ export default function ProductRulesPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
function confirmSyncRulePrice() {
|
||||
if (!editingRule) return
|
||||
Modal.confirm({
|
||||
title: '同步模板价格',
|
||||
content:
|
||||
'将同步接单大厅中该模板的待抢单价格。部分拼单仅调整剩余份额价格,已加入份额报酬保持不变。',
|
||||
okText: '确认同步',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
setSyncingPrice(true)
|
||||
try {
|
||||
const response = await syncAdminWorkProductRulePrice(editingRule.ruleId)
|
||||
const result = response.data
|
||||
message.success(
|
||||
`已同步 ${result.updatedCount} 单:待抢单 ${result.updatedNormalCount} 单,部分拼单 ${result.updatedPartialSharingCount} 单`,
|
||||
)
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-product-rules'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-orders'] }),
|
||||
])
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '同步价格失败')
|
||||
throw error
|
||||
} finally {
|
||||
setSyncingPrice(false)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function toggleRuleEnabled(rule: WorkProductRule, enabled: boolean) {
|
||||
try {
|
||||
const hasManualSharingOverride = hasManualSharingInput(
|
||||
@@ -668,6 +701,11 @@ export default function ProductRulesPanel() {
|
||||
+ 新建模板
|
||||
</Button>
|
||||
) : null}
|
||||
{editingRule ? (
|
||||
<Button loading={syncingPrice} size="middle" onClick={confirmSyncRulePrice}>
|
||||
同步价格
|
||||
</Button>
|
||||
) : null}
|
||||
<Button type="primary" size="middle" htmlType="submit" form="product-rule-form">
|
||||
{editingRule ? '保存模板' : '创建模板'}
|
||||
</Button>
|
||||
|
||||
@@ -182,6 +182,18 @@ export function saveAdminWorkProductRule(payload: {
|
||||
return apiPost<{ rule: WorkProductRule }>('/api/v1/admin/worker-platform/product-rules', payload)
|
||||
}
|
||||
|
||||
export function syncAdminWorkProductRulePrice(ruleId: number) {
|
||||
return apiPost<{
|
||||
candidateCount: number
|
||||
updatedCount: number
|
||||
updatedNormalCount: number
|
||||
updatedPartialSharingCount: number
|
||||
skippedInvalidPricingCount: number
|
||||
skippedUnchangedCount: number
|
||||
skippedUnavailableCount: number
|
||||
}>(`/api/v1/admin/worker-platform/product-rules/${ruleId}/sync-price`)
|
||||
}
|
||||
|
||||
export function deleteAdminWorkProductRule(ruleId: number) {
|
||||
return apiDelete<{ deleted: boolean }>(`/api/v1/admin/worker-platform/product-rules/${ruleId}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user