修复接单物品规则管理

This commit is contained in:
yml2213
2026-08-10 15:53:11 +08:00
parent bcde47ef97
commit 16f9531b40
6 changed files with 185 additions and 7 deletions
@@ -0,0 +1,19 @@
-- 修复历史库中迁移记录已存在、但 008/009 的接单字段未实际落库的情况。
-- 所有变更均幂等,正常完成过旧迁移的数据库不会受影响。
ALTER TABLE work_orders ADD COLUMN IF NOT EXISTS sharing_enabled BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE work_orders ADD COLUMN IF NOT EXISTS sharing_total_quantity INTEGER NOT NULL DEFAULT 1;
ALTER TABLE work_orders ADD COLUMN IF NOT EXISTS sharing_unit_reward INTEGER NOT NULL DEFAULT 0;
ALTER TABLE work_orders ADD COLUMN IF NOT EXISTS deadline_at TIMESTAMPTZ;
ALTER TABLE work_orders ADD COLUMN IF NOT EXISTS timeout_minutes INTEGER NOT NULL DEFAULT 0;
ALTER TABLE work_orders ADD COLUMN IF NOT EXISTS timeout_policy TEXT NOT NULL DEFAULT 'reopen';
ALTER TABLE work_product_rules ADD COLUMN IF NOT EXISTS sharing_enabled BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE work_product_rules ADD COLUMN IF NOT EXISTS sharing_total_quantity INTEGER NOT NULL DEFAULT 1;
ALTER TABLE work_product_rules ADD COLUMN IF NOT EXISTS sharing_unit_reward INTEGER NOT NULL DEFAULT 0;
ALTER TABLE work_product_rules ADD COLUMN IF NOT EXISTS timeout_minutes INTEGER NOT NULL DEFAULT 0;
ALTER TABLE work_product_rules ADD COLUMN IF NOT EXISTS timeout_policy TEXT NOT NULL DEFAULT 'reopen';
CREATE INDEX IF NOT EXISTS idx_work_orders_deadline
ON work_orders(status, deadline_at)
WHERE deadline_at IS NOT NULL;
@@ -391,6 +391,16 @@ export async function getWorkProductRuleByKey(ruleKey: string): Promise<WorkProd
return result.rows[0] || null return result.rows[0] || null
} }
export async function deleteWorkProductRule(
ruleId: number | string,
): Promise<{ deleted: boolean }> {
const result = await query<{ id: number }>(
'DELETE FROM work_product_rules WHERE id = $1 RETURNING id',
[Number(ruleId)],
)
return { deleted: Boolean(result.rows[0]) }
}
export async function upsertWorkProductRule(input: { export async function upsertWorkProductRule(input: {
ruleKey: string ruleKey: string
provider: string provider: string
@@ -6,6 +6,7 @@ import {
createAdminMockWorkOrder, createAdminMockWorkOrder,
deductAdminWorkOrderPendingDeposit, deductAdminWorkOrderPendingDeposit,
deleteAdminWorkCategory, deleteAdminWorkCategory,
deleteAdminWorkProductRule,
deleteAdminWorkOrder, deleteAdminWorkOrder,
deleteAdminWorkerLevel, deleteAdminWorkerLevel,
getAdminWorkerFinanceConfig, getAdminWorkerFinanceConfig,
@@ -157,6 +158,22 @@ router.post(
}), }),
) )
router.delete(
'/worker-platform/product-rules/:ruleId',
requireAdminRoles(['admin', 'operator']),
createJsonHandler((req) => deleteAdminWorkProductRule(String(req.params.ruleId || '')), {
successMessage: '接单物品规则已删除',
errorMessage: '删除接单物品规则失败',
scope: '[admin/worker-platform/product-rules/:ruleId]',
audit: (_req, data) => ({
action: 'work_product_rule_deleted',
targetType: 'work_product_rule',
targetId: String(_req.params.ruleId || ''),
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
}),
}),
)
router.get( router.get(
'/worker-platform/workers', '/worker-platform/workers',
requireAdminRoles(['admin', 'operator', 'support']), requireAdminRoles(['admin', 'operator', 'support']),
@@ -21,6 +21,7 @@ import {
createWorkerUser, createWorkerUser,
deductPendingDepositUnfreeze, deductPendingDepositUnfreeze,
deleteWorkCategory, deleteWorkCategory,
deleteWorkProductRule,
deleteWorkOrder, deleteWorkOrder,
deleteWorkerLevel, deleteWorkerLevel,
getWorkerFinanceRequestById, getWorkerFinanceRequestById,
@@ -293,6 +294,24 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
return { rule: mapWorkProductRule(rule) } return { rule: mapWorkProductRule(rule) }
} }
export async function deleteAdminWorkProductRule(ruleId: number | string) {
const normalizedRuleId = normalizeOptionalId(ruleId)
if (!normalizedRuleId) {
throw createHttpError('物品规则 ID 不正确', {
statusCode: 400,
errorCode: 'work_product_rule_id_invalid',
})
}
const { deleted } = await deleteWorkProductRule(normalizedRuleId)
if (!deleted) {
throw createHttpError('物品规则不存在或已删除', {
statusCode: 404,
errorCode: 'work_product_rule_not_found',
})
}
return { deleted }
}
export async function listAdminWorkerUsers(query: JsonObject = {}) { export async function listAdminWorkerUsers(query: JsonObject = {}) {
const page = normalizePage(query.page) const page = normalizePage(query.page)
const pageSize = normalizePageSize(query.pageSize) const pageSize = normalizePageSize(query.pageSize)
@@ -1,5 +1,7 @@
import { import {
CheckOutlined, CheckOutlined,
DeleteOutlined,
EditOutlined,
FormOutlined, FormOutlined,
PlusOutlined, PlusOutlined,
ReloadOutlined, ReloadOutlined,
@@ -19,6 +21,7 @@ import {
Input, Input,
InputNumber, InputNumber,
Modal, Modal,
Popconfirm,
Select, Select,
Space, Space,
Statistic, Statistic,
@@ -41,6 +44,7 @@ import {
createAdminMockWorkOrder, createAdminMockWorkOrder,
creditAdminWorkerWallet, creditAdminWorkerWallet,
deleteAdminWorkCategory, deleteAdminWorkCategory,
deleteAdminWorkProductRule,
deleteAdminWorkerLevel, deleteAdminWorkerLevel,
fetchAdminWorkerFinanceConfig, fetchAdminWorkerFinanceConfig,
fetchAdminWorkerFinanceRequests, fetchAdminWorkerFinanceRequests,
@@ -88,6 +92,7 @@ export default function ProductRulesPanel() {
const { message } = App.useApp() const { message } = App.useApp()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [form] = Form.useForm() const [form] = Form.useForm()
const [editingRule, setEditingRule] = useState<WorkProductRule | null>(null)
const rulesQuery = useQuery({ const rulesQuery = useQuery({
queryKey: ['admin-worker-platform-product-rules'], queryKey: ['admin-worker-platform-product-rules'],
queryFn: () => fetchAdminWorkProductRules(), queryFn: () => fetchAdminWorkProductRules(),
@@ -121,8 +126,8 @@ export default function ProductRulesPanel() {
}) { }) {
try { try {
await saveAdminWorkProductRule(values) await saveAdminWorkProductRule(values)
message.success('物品规则已保存') message.success(editingRule ? '物品规则已更新' : '物品规则已创建')
form.resetFields() resetRuleForm()
await Promise.all([ await Promise.all([
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-product-rules'], queryKey: ['admin-worker-platform-product-rules'],
@@ -136,6 +141,54 @@ export default function ProductRulesPanel() {
} }
} }
function resetRuleForm() {
setEditingRule(null)
form.resetFields()
}
function editRule(rule: WorkProductRule) {
setEditingRule(rule)
form.setFieldsValue({
ruleKey: rule.ruleKey,
provider: rule.provider,
platform: rule.platform,
shopId: rule.shopId,
skuCode: rule.skuCode,
productName: rule.productName,
matchType: rule.matchType,
categoryId: rule.categoryId || undefined,
rewardAmount: rule.rewardAmount / 100,
requiredDepositAmount: rule.requiredDepositAmount / 100,
depositThresholdAmount: rule.depositThresholdAmount / 100,
sharingEnabled: rule.sharing?.enabled || false,
sharingTotalQuantity: rule.sharing?.totalQuantity || 1,
sharingUnitReward: (rule.sharing?.unitReward || 0) / 100,
sharingTotalAmount:
((rule.sharing?.totalQuantity || 1) * (rule.sharing?.unitReward || 0)) / 100,
timeoutMinutes: rule.timeoutMinutes || 0,
timeoutPolicy: rule.timeoutPolicy || 'reopen',
fieldsText: formatRuleFields(rule.requirement?.fields || []),
sortOrder: rule.sortOrder,
enabled: rule.enabled,
autoCreate: rule.autoCreate,
})
}
async function removeRule(rule: WorkProductRule) {
try {
await deleteAdminWorkProductRule(rule.ruleId)
if (editingRule?.ruleId === rule.ruleId) {
resetRuleForm()
}
message.success('物品规则已删除')
await queryClient.invalidateQueries({
queryKey: ['admin-worker-platform-product-rules'],
})
} catch (error) {
message.error(error instanceof Error ? error.message : '删除失败')
}
}
function syncSharingRuleForm(next: { function syncSharingRuleForm(next: {
sharingTotalQuantity?: number sharingTotalQuantity?: number
sharingUnitReward?: number sharingUnitReward?: number
@@ -224,11 +277,52 @@ export default function ProductRulesPanel() {
</Space> </Space>
), ),
}, },
{
title: '操作',
key: 'actions',
width: 104,
fixed: 'right',
render: (_, row) => (
<Space size={0}>
<Button
type="text"
icon={<EditOutlined />}
aria-label={`编辑规则 ${row.ruleKey}`}
title="编辑规则"
onClick={() => editRule(row)}
/>
<Popconfirm
title={`删除规则“${row.productName || row.ruleKey}”?`}
description="删除后不会影响已创建的工单。"
okText="删除"
cancelText="取消"
okButtonProps={{ danger: true }}
onConfirm={() => removeRule(row)}
>
<Button
type="text"
danger
icon={<DeleteOutlined />}
aria-label={`删除规则 ${row.ruleKey}`}
title="删除规则"
/>
</Popconfirm>
</Space>
),
},
] ]
return ( return (
<section className="platform-panel-stack"> <section className="platform-panel-stack">
<Card title="保存物品规则" bordered={false}> <Card
title={editingRule ? `编辑物品规则 · ${editingRule.ruleKey}` : '新建物品规则'}
extra={
editingRule ? (
<Button onClick={resetRuleForm}></Button>
) : null
}
bordered={false}
>
<Form <Form
form={form} form={form}
layout="vertical" layout="vertical"
@@ -255,7 +349,7 @@ export default function ProductRulesPanel() {
name="ruleKey" name="ruleKey"
rules={[{ required: true, message: '请输入规则标识' }]} rules={[{ required: true, message: '请输入规则标识' }]}
> >
<Input placeholder="skin-training" /> <Input placeholder="skin-training" disabled={Boolean(editingRule)} />
</Form.Item> </Form.Item>
<Form.Item label="来源" name="provider"> <Form.Item label="来源" name="provider">
<Input placeholder="留空通配,如 91kaquan" /> <Input placeholder="留空通配,如 91kaquan" />
@@ -424,9 +518,12 @@ export default function ProductRulesPanel() {
]} ]}
/> />
</Form.Item> </Form.Item>
<Button type="primary" htmlType="submit"> <Space>
<Button type="primary" htmlType="submit">
</Button> {editingRule ? '保存修改' : '创建规则'}
</Button>
{!editingRule ? null : <Button onClick={resetRuleForm}></Button>}
</Space>
</Form> </Form>
</Card> </Card>
<Card <Card
@@ -455,3 +552,13 @@ export default function ProductRulesPanel() {
) )
} }
function formatRuleFields(fields: CollectField[]) {
return fields
.map((field) => {
const options = Array.isArray(field.options) && field.options.length > 0
? `#${field.options.join(',')}`
: ''
return `${field.key}:${field.label}${options}`
})
.join('\n')
}
@@ -115,6 +115,12 @@ export function saveAdminWorkProductRule(payload: {
) )
} }
export function deleteAdminWorkProductRule(ruleId: number) {
return apiDelete<{ deleted: boolean }>(
`/api/v1/admin/worker-platform/product-rules/${ruleId}`,
)
}
export function reviewAdminWorkerUser( export function reviewAdminWorkerUser(
workerId: number, workerId: number,
payload: { status: string; reviewNote?: string; levelId?: number }, payload: { status: string; reviewNote?: string; levelId?: number },