修复接单物品规则管理
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
|
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>
|
||||||
|
<Space>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
保存规则
|
{editingRule ? '保存修改' : '创建规则'}
|
||||||
</Button>
|
</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 },
|
||||||
|
|||||||
Reference in New Issue
Block a user