接单平台新增 VIP 等级体系与邀请绑定

- 等级按累计验收通过工单数自动升级(阈值后台可配),VIP1-5 默认配置 10/30/50/100
- 存量打手统一归 VIP1,原 regular 等级停用
- 注册支持邀请码绑定邀请人,每人生成唯一邀请码
- 新增 worker_level_logs 升级流水表
- 个人中心展示邀请码与升级进度,管理端等级表单支持升级阈值配置
This commit is contained in:
yml2213
2026-08-01 14:50:08 +08:00
parent 6e635b2499
commit 51d72cc283
9 changed files with 353 additions and 21 deletions
@@ -26,6 +26,10 @@ export type WorkerUserRow = {
created_at: string
updated_at: string
reviewed_at: string | null
inviter_id: number | null
invite_code: string
inviter_username?: string
inviter_display_name?: string
level_key?: string
level_name?: string
level_permission_json?: string | Record<string, unknown>
@@ -192,6 +196,8 @@ type CreateWorkerInput = {
phone: string
levelId: number | null
status: string
inviterId?: number | null
inviteCode: string
now: string
}
@@ -218,12 +224,15 @@ const WORKER_USER_SELECT = `
wl.level_key,
wl.name AS level_name,
wl.permission_json AS level_permission_json,
inv.username AS inviter_username,
inv.display_name AS inviter_display_name,
ww.available_amount,
ww.frozen_deposit_amount,
ww.total_credited_amount,
ww.total_settled_amount
FROM worker_users wu
LEFT JOIN worker_levels wl ON wl.id = wu.level_id
LEFT JOIN worker_users inv ON inv.id = wu.inviter_id
LEFT JOIN worker_wallets ww ON ww.worker_id = wu.id
`
@@ -325,6 +334,16 @@ export async function getWorkerUserByUsername(username: string): Promise<WorkerU
return result.rows[0] || null
}
export async function getWorkerUserByInviteCode(
inviteCode: string,
): Promise<WorkerUserRow | null> {
const result = await query<WorkerUserRow>(
`${WORKER_USER_SELECT} WHERE wu.invite_code = $1 LIMIT 1`,
[String(inviteCode || '').trim().toUpperCase()],
)
return result.rows[0] || null
}
export async function getWorkerUserById(workerId: number | string): Promise<WorkerUserRow | null> {
const result = await query<WorkerUserRow>(`${WORKER_USER_SELECT} WHERE wu.id = $1 LIMIT 1`, [
Number(workerId),
@@ -338,8 +357,9 @@ export async function createWorkerUser(input: CreateWorkerInput): Promise<Worker
`
INSERT INTO worker_users (
username, password_hash, display_name, phone, level_id, status,
inviter_id, invite_code,
created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING id
`,
[
@@ -349,6 +369,8 @@ export async function createWorkerUser(input: CreateWorkerInput): Promise<Worker
input.phone,
input.levelId,
input.status,
input.inviterId || null,
input.inviteCode,
input.now,
input.now,
],
@@ -824,6 +846,64 @@ export async function countWorkerAcceptedOrders(workerId: number | string): Prom
return Number(result.rows[0]?.total || 0)
}
export async function maybeUpgradeWorkerLevelWithClient(
client: PoolClient,
workerId: number,
now: string,
): Promise<WorkerUserRow | null> {
const acceptedResult = await client.query<{ total: number }>(
`
SELECT COUNT(*)::int AS total
FROM work_orders
WHERE assigned_worker_id = $1
AND status = 'accepted'
`,
[workerId],
)
const acceptedCount = Number(acceptedResult.rows[0]?.total || 0)
const nextLevelResult = await client.query<{ id: number }>(
`
SELECT id
FROM worker_levels
WHERE status = 'active'
AND COALESCE((permission_json->>'upgradeThreshold')::int, 0) <= $1
ORDER BY COALESCE((permission_json->>'upgradeThreshold')::int, 0) DESC, sort_order ASC
LIMIT 1
`,
[acceptedCount],
)
const nextLevelId = Number(nextLevelResult.rows[0]?.id || 0)
if (!nextLevelId) return null
const current = await client.query<{ id: number; level_id: number | null }>(
'SELECT id, level_id FROM worker_users WHERE id = $1 LIMIT 1',
[workerId],
)
const currentRow = current.rows[0]
if (!currentRow || Number(currentRow.level_id || 0) === nextLevelId) return null
await client.query(
'UPDATE worker_users SET level_id = $1, updated_at = $2 WHERE id = $3',
[nextLevelId, now, workerId],
)
await client.query(
`
INSERT INTO worker_level_logs (
worker_id, from_level_id, to_level_id, reason, payload_json, created_at
) VALUES ($1, $2, $3, 'auto', $4::jsonb, $5)
`,
[
workerId,
currentRow.level_id || null,
nextLevelId,
JSON.stringify({ acceptedCount }),
now,
],
)
return getWorkerUserByIdWithClient(client, workerId)
}
export async function getWorkCategoryByKey(categoryKey: string): Promise<WorkCategoryRow | null> {
const result = await query<WorkCategoryRow>(
'SELECT * FROM work_categories WHERE category_key = $1 LIMIT 1',
@@ -1304,6 +1384,10 @@ export async function acceptWorkOrderAndSettle(input: {
now: input.now,
})
if (workerId > 0) {
await maybeUpgradeWorkerLevelWithClient(client, workerId, input.now)
}
return getWorkOrderByIdWithClient(client, input.workOrderId)
})
}