修复接单物品规则管理
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
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: {
|
||||
ruleKey: string
|
||||
provider: string
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
createAdminMockWorkOrder,
|
||||
deductAdminWorkOrderPendingDeposit,
|
||||
deleteAdminWorkCategory,
|
||||
deleteAdminWorkProductRule,
|
||||
deleteAdminWorkOrder,
|
||||
deleteAdminWorkerLevel,
|
||||
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(
|
||||
'/worker-platform/workers',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
createWorkerUser,
|
||||
deductPendingDepositUnfreeze,
|
||||
deleteWorkCategory,
|
||||
deleteWorkProductRule,
|
||||
deleteWorkOrder,
|
||||
deleteWorkerLevel,
|
||||
getWorkerFinanceRequestById,
|
||||
@@ -293,6 +294,24 @@ export async function saveAdminWorkProductRule(payload: JsonObject = {}) {
|
||||
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 = {}) {
|
||||
const page = normalizePage(query.page)
|
||||
const pageSize = normalizePageSize(query.pageSize)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {
|
||||
CheckOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
FormOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
@@ -19,6 +21,7 @@ import {
|
||||
Input,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Popconfirm,
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
@@ -41,6 +44,7 @@ import {
|
||||
createAdminMockWorkOrder,
|
||||
creditAdminWorkerWallet,
|
||||
deleteAdminWorkCategory,
|
||||
deleteAdminWorkProductRule,
|
||||
deleteAdminWorkerLevel,
|
||||
fetchAdminWorkerFinanceConfig,
|
||||
fetchAdminWorkerFinanceRequests,
|
||||
@@ -88,6 +92,7 @@ export default function ProductRulesPanel() {
|
||||
const { message } = App.useApp()
|
||||
const queryClient = useQueryClient()
|
||||
const [form] = Form.useForm()
|
||||
const [editingRule, setEditingRule] = useState<WorkProductRule | null>(null)
|
||||
const rulesQuery = useQuery({
|
||||
queryKey: ['admin-worker-platform-product-rules'],
|
||||
queryFn: () => fetchAdminWorkProductRules(),
|
||||
@@ -121,8 +126,8 @@ export default function ProductRulesPanel() {
|
||||
}) {
|
||||
try {
|
||||
await saveAdminWorkProductRule(values)
|
||||
message.success('物品规则已保存')
|
||||
form.resetFields()
|
||||
message.success(editingRule ? '物品规则已更新' : '物品规则已创建')
|
||||
resetRuleForm()
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
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: {
|
||||
sharingTotalQuantity?: number
|
||||
sharingUnitReward?: number
|
||||
@@ -224,11 +277,52 @@ export default function ProductRulesPanel() {
|
||||
</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 (
|
||||
<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}
|
||||
layout="vertical"
|
||||
@@ -255,7 +349,7 @@ export default function ProductRulesPanel() {
|
||||
name="ruleKey"
|
||||
rules={[{ required: true, message: '请输入规则标识' }]}
|
||||
>
|
||||
<Input placeholder="skin-training" />
|
||||
<Input placeholder="skin-training" disabled={Boolean(editingRule)} />
|
||||
</Form.Item>
|
||||
<Form.Item label="来源" name="provider">
|
||||
<Input placeholder="留空通配,如 91kaquan" />
|
||||
@@ -424,9 +518,12 @@ export default function ProductRulesPanel() {
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button type="primary" htmlType="submit">
|
||||
保存规则
|
||||
</Button>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{editingRule ? '保存修改' : '创建规则'}
|
||||
</Button>
|
||||
{!editingRule ? null : <Button onClick={resetRuleForm}>取消</Button>}
|
||||
</Space>
|
||||
</Form>
|
||||
</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(
|
||||
workerId: number,
|
||||
payload: { status: string; reviewNote?: string; levelId?: number },
|
||||
|
||||
Reference in New Issue
Block a user