拆分接单平台超长文件,按域组织代码

- worker-platform-service.ts (2733行) 拆为 worker-service/admin-service/mappers + index 聚合
- worker-platform-repo.ts (2456行) 拆为 repositories/worker-platform/ 目录(types/shared/worker-repo/work-order-repo)
- AdminWorkerPlatformPage.tsx (2784行) 拆为主页面 + panels/ 目录(7 个面板文件)
- 引用方统一改指向 index 聚合,行为不变
This commit is contained in:
yml2213
2026-08-01 16:44:20 +08:00
parent c33e859e96
commit 70e64155bc
27 changed files with 7200 additions and 6463 deletions
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { resolveOutstandingDepositAmount } from './worker-platform-repo.js'
import { resolveOutstandingDepositAmount } from './worker-platform/index.js'
test('resolveOutstandingDepositAmount deducts released and deducted history', () => {
assert.equal(
@@ -0,0 +1,4 @@
export * from './types.js'
export * from './shared.js'
export * from './worker-repo.js'
export * from './work-order-repo.js'
@@ -0,0 +1,44 @@
import type { PoolClient } from 'pg'
import type { WorkerWalletRow } from './types.js'
export async function ensureWorkerWalletWithClient(
client: PoolClient,
workerId: number,
now: string,
): Promise<void> {
if (!workerId) return
await client.query(
`
INSERT INTO worker_wallets (
worker_id, available_amount, frozen_deposit_amount,
total_credited_amount, total_settled_amount, created_at, updated_at
) VALUES ($1, 0, 0, 0, 0, $2, $3)
ON CONFLICT (worker_id) DO NOTHING
`,
[workerId, now, now],
)
}
export async function getWorkerWalletWithClient(
client: PoolClient,
workerId: number,
): Promise<WorkerWalletRow | null> {
const result = await client.query<WorkerWalletRow>(
'SELECT * FROM worker_wallets WHERE worker_id = $1 LIMIT 1 FOR UPDATE',
[workerId],
)
return result.rows[0] || null
}
export function toJsonString(value: unknown): string {
if (typeof value === 'string') return value
return JSON.stringify(value ?? {})
}
export function toPositiveInteger(value: unknown, fallback: number): number {
const parsed = Number(value ?? '')
if (!Number.isFinite(parsed)) return fallback
return Math.max(0, Math.round(parsed))
}
@@ -0,0 +1,246 @@
export type WorkerLevelRow = {
id: number
level_key: string
name: string
sort_order: number
status: string
permission_json: string | Record<string, unknown>
created_at: string
updated_at: string
}
export type WorkerUserRow = {
id: number
username: string
password_hash: string
display_name: string
phone: string
level_id: number | null
status: string
review_note: string
session_version: number
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>
available_amount?: number
frozen_deposit_amount?: number
total_credited_amount?: number
total_settled_amount?: number
}
export type WorkerWalletRow = {
worker_id: number
available_amount: number
frozen_deposit_amount: number
total_credited_amount: number
total_settled_amount: number
created_at: string
updated_at: string
}
export type WorkerWalletLedgerRow = {
id: number
worker_id: number
ledger_type: string
amount: number
balance_after: number
frozen_after: number
related_work_order_id: number | null
audit_status: string
note: string
payload_json: string | Record<string, unknown>
created_at: string
}
export type WorkerFinanceRequestRow = {
id: number
worker_id: number
request_type: string
status: string
amount: number
account_channel: string
account_name: string
account_no: string
note: string
reviewed_note: string
payload_json: string | Record<string, unknown>
created_at: string
updated_at: string
reviewed_at: string | null
worker_username?: string
worker_display_name?: string
worker_phone?: string
}
export type WorkCategoryRow = {
id: number
category_key: string
name: string
sort_order: number
status: string
created_at: string
updated_at: string
}
export type WorkProductRuleRow = {
id: number
rule_key: string
provider: string
platform: string
shop_id: string
sku_code: string
product_name: string
match_type: string
category_id: number | null
enabled: boolean
auto_create: boolean
reward_amount: number
required_deposit_amount: number
deposit_threshold_amount: number
sharing_enabled: boolean
sharing_total_quantity: number
sharing_unit_reward: number
requirement_json: string | Record<string, unknown>
sort_order: number
created_at: string
updated_at: string
category_name?: string
}
export type WorkOrderShareRow = {
id: number
work_order_id: number
worker_id: number
quantity: number
unit_reward: number
share_reward: number
share_deposit: number
status: string
acceptance_json: string | Record<string, unknown>
submitted_at: string | null
accepted_at: string | null
created_at: string
updated_at: string
worker_username?: string
worker_display_name?: string
}
export type WorkOrderRow = {
id: number
work_order_no: string
order_id: number | null
order_item_id: number | null
task_id: number | null
platform_order_id: string
product_name: string
category_id: number | null
status: string
reward_amount: number
required_deposit_amount: number
deposit_threshold_amount: number
assigned_worker_id: number | null
material_json: string | Record<string, unknown>
requirement_json: string | Record<string, unknown>
acceptance_json: string | Record<string, unknown>
problem_note: string
sharing_enabled: boolean
sharing_total_quantity: number
sharing_unit_reward: number
published_at: string | null
assigned_at: string | null
submitted_at: string | null
accepted_at: string | null
created_at: string
updated_at: string
category_name?: string
worker_username?: string
worker_display_name?: string
}
export type ProblemWorkOrderResolutionAction =
| 'return_to_worker'
| 'reopen'
| 'cancel_release'
| 'cancel_deduct'
export type GrabWorkOrderFailureReason =
| 'worker_active_order_limit'
| 'worker_deposit_insufficient'
| 'work_order_not_open'
export type GrabWorkOrderResult = {
order: WorkOrderRow | null
failureReason: GrabWorkOrderFailureReason | null
}
export type ListInput = {
page?: number
pageSize?: number
status?: string
keyword?: string
workerId?: number
categoryId?: number
workerSharingId?: number
}
export type ProductRuleListInput = {
enabled?: boolean | null
keyword?: string
}
export type WalletLedgerListInput = {
page?: number
pageSize?: number
workerId?: number
ledgerType?: string
}
export type FinanceRequestListInput = {
page?: number
pageSize?: number
workerId?: number
status?: string
requestType?: string
keyword?: string
}
export type CreateWorkerInput = {
username: string
passwordHash: string
displayName: string
phone: string
levelId: number | null
status: string
inviterId?: number | null
inviteCode: string
now: string
}
export type CreateWorkOrderInput = {
workOrderNo: string
orderId?: number | null
orderItemId?: number | null
taskId?: number | null
platformOrderId: string
productName: string
categoryId?: number | null
status: string
rewardAmount: number
requiredDepositAmount: number
depositThresholdAmount: number
sharingEnabled?: boolean
sharingTotalQuantity?: number
sharingUnitReward?: number
materialJson: string
requirementJson: string
now: string
}
@@ -0,0 +1,794 @@
import { query, withTransaction } from '../../db/client.js'
import { ensureWorkerWalletWithClient, getWorkerWalletWithClient } from './shared.js'
import type { CreateWorkerInput, FinanceRequestListInput, ListInput, WalletLedgerListInput, WorkerFinanceRequestRow, WorkerLevelRow, WorkerUserRow, WorkerWalletLedgerRow, WorkerWalletRow } from './types.js'
import type { PoolClient } from 'pg'
const WORKER_USER_SELECT = `
SELECT
wu.*,
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
`
const WORKER_FINANCE_REQUEST_SELECT = `
SELECT
wfr.*,
wu.username AS worker_username,
wu.display_name AS worker_display_name,
wu.phone AS worker_phone
FROM worker_finance_requests wfr
LEFT JOIN worker_users wu ON wu.id = wfr.worker_id
`
export async function getWorkerLevelByKey(levelKey: string): Promise<WorkerLevelRow | null> {
const result = await query<WorkerLevelRow>(
'SELECT * FROM worker_levels WHERE level_key = $1 LIMIT 1',
[String(levelKey || '').trim()],
)
return result.rows[0] || null
}
export async function getWorkerLevelById(levelId: number | string): Promise<WorkerLevelRow | null> {
const result = await query<WorkerLevelRow>('SELECT * FROM worker_levels WHERE id = $1 LIMIT 1', [
Number(levelId),
])
return result.rows[0] || null
}
export async function listWorkerLevels(): Promise<WorkerLevelRow[]> {
const result = await query<WorkerLevelRow>(
'SELECT * FROM worker_levels ORDER BY sort_order ASC, id ASC',
)
return result.rows
}
export async function upsertWorkerLevel(input: {
levelKey: string
name: string
sortOrder: number
status: string
permissionJson: string
now: string
}): Promise<WorkerLevelRow | null> {
const result = await query<WorkerLevelRow>(
`
INSERT INTO worker_levels (
level_key, name, sort_order, status, permission_json, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5::jsonb, $6, $7)
ON CONFLICT (level_key) DO UPDATE
SET
name = EXCLUDED.name,
sort_order = EXCLUDED.sort_order,
status = EXCLUDED.status,
permission_json = EXCLUDED.permission_json,
updated_at = EXCLUDED.updated_at
RETURNING *
`,
[
input.levelKey,
input.name,
input.sortOrder,
input.status,
input.permissionJson,
input.now,
input.now,
],
)
return result.rows[0] || null
}
export async function countWorkerLevelUsages(levelId: number | string): Promise<number> {
const result = await query<{ total: number }>(
'SELECT COUNT(*)::int AS total FROM worker_users WHERE level_id = $1',
[Number(levelId)],
)
return Number(result.rows[0]?.total || 0)
}
export async function deleteWorkerLevel(levelId: number | string): Promise<{ deleted: boolean }> {
const result = await query<{ id: number }>(
'DELETE FROM worker_levels WHERE id = $1 RETURNING id',
[Number(levelId)],
)
return { deleted: Boolean(result.rows[0]) }
}
export async function getWorkerUserByUsername(username: string): Promise<WorkerUserRow | null> {
const result = await query<WorkerUserRow>(
`${WORKER_USER_SELECT} WHERE wu.username = $1 LIMIT 1`,
[
String(username || '')
.trim()
.toLowerCase(),
],
)
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),
])
return result.rows[0] || null
}
export async function createWorkerUser(input: CreateWorkerInput): Promise<WorkerUserRow | null> {
return withTransaction(async (client) => {
const result = await client.query<{ id: number }>(
`
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, $9, $10)
RETURNING id
`,
[
input.username,
input.passwordHash,
input.displayName,
input.phone,
input.levelId,
input.status,
input.inviterId || null,
input.inviteCode,
input.now,
input.now,
],
)
const workerId = Number(result.rows[0]?.id || 0)
await ensureWorkerWalletWithClient(client, workerId, input.now)
return getWorkerUserByIdWithClient(client, workerId)
})
}
export async function updateWorkerUser(
workerId: number | string,
patch: Partial<
Pick<
WorkerUserRow,
'status' | 'review_note' | 'level_id' | 'session_version' | 'updated_at' | 'reviewed_at'
>
>,
): Promise<WorkerUserRow | null> {
const current = await getWorkerUserById(workerId)
if (!current) return null
const next = { ...current, ...patch }
await query(
`
UPDATE worker_users
SET
status = $1,
review_note = $2,
level_id = $3,
session_version = $4,
reviewed_at = $5,
updated_at = $6
WHERE id = $7
`,
[
next.status,
next.review_note || '',
next.level_id || null,
Number(next.session_version || 1),
next.reviewed_at || null,
next.updated_at,
Number(workerId),
],
)
return getWorkerUserById(workerId)
}
export async function updateWorkerPassword(input: {
workerId: number
passwordHash: string
sessionVersion: number
now: string
}): Promise<WorkerUserRow | null> {
const result = await query<{ id: number }>(
`
UPDATE worker_users
SET
password_hash = $1,
session_version = $2,
updated_at = $3
WHERE id = $4
RETURNING id
`,
[input.passwordHash, input.sessionVersion, input.now, input.workerId],
)
if (!result.rows[0]) {
return null
}
return getWorkerUserById(input.workerId)
}
export async function incrementWorkerSessionVersion(
workerId: number | string,
now: string,
): Promise<WorkerUserRow | null> {
const result = await query<{ id: number }>(
`
UPDATE worker_users
SET session_version = session_version + 1, updated_at = $2
WHERE id = $1
RETURNING id
`,
[Number(workerId), now],
)
if (!result.rows[0]) {
return null
}
return getWorkerUserById(workerId)
}
export async function listWorkerUsers({
page = 1,
pageSize = 20,
status = '',
keyword = '',
}: ListInput = {}): Promise<{ items: WorkerUserRow[]; total: number }> {
const { whereClause, params } = buildWorkerUserWhere({ status, keyword })
const totalResult = await query<{ total: number }>(
`SELECT COUNT(*)::int AS total FROM worker_users wu ${whereClause}`,
params,
)
const offset = (page - 1) * pageSize
params.push(pageSize, offset)
const itemsResult = await query<WorkerUserRow>(
`${WORKER_USER_SELECT}
${whereClause}
ORDER BY wu.id DESC
LIMIT $${params.length - 1} OFFSET $${params.length}`,
params,
)
return { items: itemsResult.rows, total: Number(totalResult.rows[0]?.total || 0) }
}
export async function ensureWorkerWallet(
workerId: number | string,
now: string,
): Promise<WorkerWalletRow | null> {
return withTransaction(async (client) => {
await ensureWorkerWalletWithClient(client, Number(workerId), now)
return getWorkerWalletWithClient(client, Number(workerId))
})
}
export async function getWorkerWallet(workerId: number | string): Promise<WorkerWalletRow | null> {
const result = await query<WorkerWalletRow>(
'SELECT * FROM worker_wallets WHERE worker_id = $1 LIMIT 1',
[Number(workerId)],
)
return result.rows[0] || null
}
export async function listWorkerWalletLedgers({
page = 1,
pageSize = 20,
workerId = 0,
ledgerType = '',
}: WalletLedgerListInput = {}): Promise<{ items: WorkerWalletLedgerRow[]; total: number }> {
const { whereClause, params } = buildWorkerWalletLedgerWhere({
workerId,
ledgerType,
})
const totalResult = await query<{ total: number }>(
`SELECT COUNT(*)::int AS total FROM worker_wallet_ledgers wwl ${whereClause}`,
params,
)
const offset = (page - 1) * pageSize
params.push(pageSize, offset)
const itemsResult = await query<WorkerWalletLedgerRow>(
`
SELECT *
FROM worker_wallet_ledgers wwl
${whereClause}
ORDER BY wwl.created_at DESC, wwl.id DESC
LIMIT $${params.length - 1} OFFSET $${params.length}
`,
params,
)
return {
items: itemsResult.rows,
total: Number(totalResult.rows[0]?.total || 0),
}
}
export async function addWorkerWalletCredit(input: {
workerId: number
amount: number
note: string
payloadJson: string
now: string
}): Promise<WorkerWalletRow | null> {
return withTransaction(async (client) => {
await ensureWorkerWalletWithClient(client, input.workerId, input.now)
const wallet = await getWorkerWalletWithClient(client, input.workerId)
const nextAvailable = Number(wallet?.available_amount || 0) + input.amount
const nextFrozen = Number(wallet?.frozen_deposit_amount || 0)
await client.query(
`
UPDATE worker_wallets
SET
available_amount = $1,
total_credited_amount = total_credited_amount + $2,
updated_at = $3
WHERE worker_id = $4
`,
[nextAvailable, input.amount, input.now, input.workerId],
)
await client.query(
`
INSERT INTO worker_wallet_ledgers (
worker_id, ledger_type, amount, balance_after, frozen_after,
audit_status, note, payload_json, created_at
) VALUES ($1, 'manual_credit', $2, $3, $4, 'approved', $5, $6::jsonb, $7)
`,
[
input.workerId,
input.amount,
nextAvailable,
nextFrozen,
input.note,
input.payloadJson,
input.now,
],
)
return getWorkerWalletWithClient(client, input.workerId)
})
}
export async function createWorkerFinanceRequest(input: {
workerId: number
requestType: string
amount: number
accountChannel: string
accountName: string
accountNo: string
note: string
payloadJson: string
now: string
}): Promise<WorkerFinanceRequestRow | null> {
const result = await query<WorkerFinanceRequestRow>(
`
INSERT INTO worker_finance_requests (
worker_id, request_type, status, amount,
account_channel, account_name, account_no,
note, reviewed_note, payload_json, created_at, updated_at
) VALUES ($1, $2, 'pending', $3, $4, $5, $6, $7, '', $8::jsonb, $9, $10)
RETURNING *
`,
[
input.workerId,
input.requestType,
input.amount,
input.accountChannel,
input.accountName,
input.accountNo,
input.note,
input.payloadJson,
input.now,
input.now,
],
)
return result.rows[0] || null
}
export async function getWorkerFinanceRequestById(
requestId: number | string,
): Promise<WorkerFinanceRequestRow | null> {
const result = await query<WorkerFinanceRequestRow>(
`${WORKER_FINANCE_REQUEST_SELECT} WHERE wfr.id = $1 LIMIT 1`,
[Number(requestId)],
)
return result.rows[0] || null
}
export async function listWorkerFinanceRequests({
page = 1,
pageSize = 20,
workerId = 0,
status = '',
requestType = '',
keyword = '',
}: FinanceRequestListInput = {}): Promise<{ items: WorkerFinanceRequestRow[]; total: number }> {
const { whereClause, params } = buildWorkerFinanceRequestWhere({
workerId,
status,
requestType,
keyword,
})
const totalResult = await query<{ total: number }>(
`SELECT COUNT(*)::int AS total FROM worker_finance_requests wfr
LEFT JOIN worker_users wu ON wu.id = wfr.worker_id
${whereClause}`,
params,
)
const offset = (page - 1) * pageSize
params.push(pageSize, offset)
const itemsResult = await query<WorkerFinanceRequestRow>(
`${WORKER_FINANCE_REQUEST_SELECT}
${whereClause}
ORDER BY wfr.created_at DESC, wfr.id DESC
LIMIT $${params.length - 1} OFFSET $${params.length}`,
params,
)
return {
items: itemsResult.rows,
total: Number(totalResult.rows[0]?.total || 0),
}
}
export async function reviewWorkerFinanceRequest(input: {
requestId: number
status: string
reviewedNote: string
now: string
}): Promise<{
request: WorkerFinanceRequestRow | null
failureReason: 'request_not_pending' | 'withdraw_insufficient' | null
}> {
return withTransaction(async (client) => {
const currentResult = await client.query<WorkerFinanceRequestRow>(
`
SELECT *
FROM worker_finance_requests
WHERE id = $1
LIMIT 1
FOR UPDATE
`,
[input.requestId],
)
const current = currentResult.rows[0] || null
if (!current) {
return {
request: null,
failureReason: null,
}
}
if (current.status !== 'pending') {
return {
request: await getWorkerFinanceRequestByIdWithClient(client, input.requestId),
failureReason: 'request_not_pending',
}
}
if (input.status === 'approved') {
await ensureWorkerWalletWithClient(client, Number(current.worker_id), input.now)
const wallet = await getWorkerWalletWithClient(client, Number(current.worker_id))
const availableAmount = Number(wallet?.available_amount || 0)
const frozenAmount = Number(wallet?.frozen_deposit_amount || 0)
const amount = Number(current.amount || 0)
const note =
input.reviewedNote ||
(current.request_type === 'withdraw' ? '提现申请审核通过' : '充值申请审核通过')
if (current.request_type === 'withdraw') {
if (availableAmount < amount) {
return {
request: await getWorkerFinanceRequestByIdWithClient(client, input.requestId),
failureReason: 'withdraw_insufficient',
}
}
const nextAvailable = availableAmount - amount
await client.query(
`
UPDATE worker_wallets
SET available_amount = $1, updated_at = $2
WHERE worker_id = $3
`,
[nextAvailable, input.now, Number(current.worker_id)],
)
await client.query(
`
INSERT INTO worker_wallet_ledgers (
worker_id, ledger_type, amount, balance_after, frozen_after,
audit_status, note, payload_json, created_at
) VALUES ($1, 'withdraw_paid', $2, $3, $4, 'approved', $5, $6::jsonb, $7)
`,
[
Number(current.worker_id),
-amount,
nextAvailable,
frozenAmount,
note,
JSON.stringify({
requestId: Number(current.id),
requestType: current.request_type,
source: 'admin_finance_review',
}),
input.now,
],
)
} else {
const nextAvailable = availableAmount + amount
await client.query(
`
UPDATE worker_wallets
SET
available_amount = $1,
total_credited_amount = total_credited_amount + $2,
updated_at = $3
WHERE worker_id = $4
`,
[nextAvailable, amount, input.now, Number(current.worker_id)],
)
await client.query(
`
INSERT INTO worker_wallet_ledgers (
worker_id, ledger_type, amount, balance_after, frozen_after,
audit_status, note, payload_json, created_at
) VALUES ($1, 'manual_credit', $2, $3, $4, 'approved', $5, $6::jsonb, $7)
`,
[
Number(current.worker_id),
amount,
nextAvailable,
frozenAmount,
note,
JSON.stringify({
requestId: Number(current.id),
requestType: current.request_type,
source: 'admin_finance_review',
}),
input.now,
],
)
}
}
await client.query(
`
UPDATE worker_finance_requests
SET
status = $1,
reviewed_note = $2,
reviewed_at = $3,
updated_at = $3
WHERE id = $4
`,
[input.status, input.reviewedNote, input.now, input.requestId],
)
return {
request: await getWorkerFinanceRequestByIdWithClient(client, input.requestId),
failureReason: null,
}
})
}
export async function getWorkerFinanceRequestSummary(workerId: number | string) {
const result = await query<{
pending_withdraw_amount: number
approved_withdraw_amount: number
pending_recharge_amount: number
}>(
`
SELECT
COALESCE(
SUM(CASE WHEN request_type = 'withdraw' AND status = 'pending' THEN amount ELSE 0 END),
0
)::int AS pending_withdraw_amount,
COALESCE(
SUM(CASE WHEN request_type = 'withdraw' AND status = 'approved' THEN amount ELSE 0 END),
0
)::int AS approved_withdraw_amount,
COALESCE(
SUM(CASE WHEN request_type = 'recharge' AND status = 'pending' THEN amount ELSE 0 END),
0
)::int AS pending_recharge_amount
FROM worker_finance_requests
WHERE worker_id = $1
`,
[Number(workerId)],
)
return {
pendingWithdrawAmount: Number(result.rows[0]?.pending_withdraw_amount || 0),
approvedWithdrawAmount: Number(result.rows[0]?.approved_withdraw_amount || 0),
pendingRechargeAmount: Number(result.rows[0]?.pending_recharge_amount || 0),
}
}
export async function countWorkerAcceptedOrders(workerId: number | string): Promise<number> {
const result = await query<{ total: number }>(
`
SELECT
(SELECT COUNT(*)::int FROM work_orders WHERE assigned_worker_id = $1 AND status = 'accepted')
+
COALESCE(
(SELECT SUM(quantity)::int FROM work_order_shares WHERE worker_id = $1 AND status = 'accepted'),
0
) AS total
`,
[Number(workerId)],
)
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
(SELECT COUNT(*)::int FROM work_orders WHERE assigned_worker_id = $1 AND status = 'accepted')
+
COALESCE(
(SELECT SUM(quantity)::int FROM work_order_shares WHERE worker_id = $1 AND status = 'accepted'),
0
) AS total
`,
[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)
}
async function getWorkerUserByIdWithClient(
client: PoolClient,
workerId: number | string,
): Promise<WorkerUserRow | null> {
const result = await client.query<WorkerUserRow>(
`${WORKER_USER_SELECT} WHERE wu.id = $1 LIMIT 1`,
[Number(workerId)],
)
return result.rows[0] || null
}
async function getWorkerFinanceRequestByIdWithClient(
client: PoolClient,
requestId: number | string,
): Promise<WorkerFinanceRequestRow | null> {
const result = await client.query<WorkerFinanceRequestRow>(
`${WORKER_FINANCE_REQUEST_SELECT} WHERE wfr.id = $1 LIMIT 1`,
[Number(requestId)],
)
return result.rows[0] || null
}
function buildWorkerUserWhere({
status = '',
keyword = '',
}: Pick<ListInput, 'status' | 'keyword'>) {
const filters: string[] = []
const params: unknown[] = []
if (status) {
params.push(status)
filters.push(`wu.status = $${params.length}`)
}
if (keyword) {
params.push(`%${keyword}%`)
filters.push(
`(wu.username ILIKE $${params.length} OR wu.display_name ILIKE $${params.length} OR wu.phone ILIKE $${params.length})`,
)
}
return {
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
params,
}
}
function buildWorkerWalletLedgerWhere({
workerId = 0,
ledgerType = '',
}: WalletLedgerListInput) {
const filters: string[] = []
const params: unknown[] = []
if (workerId) {
params.push(workerId)
filters.push(`wwl.worker_id = $${params.length}`)
}
if (ledgerType) {
params.push(ledgerType)
filters.push(`wwl.ledger_type = $${params.length}`)
}
return {
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
params,
}
}
function buildWorkerFinanceRequestWhere({
workerId = 0,
status = '',
requestType = '',
keyword = '',
}: FinanceRequestListInput) {
const filters: string[] = []
const params: unknown[] = []
if (workerId) {
params.push(workerId)
filters.push(`wfr.worker_id = $${params.length}`)
}
if (status) {
params.push(status)
filters.push(`wfr.status = $${params.length}`)
}
if (requestType) {
params.push(requestType)
filters.push(`wfr.request_type = $${params.length}`)
}
if (keyword) {
params.push(`%${keyword}%`)
filters.push(
`(wu.username ILIKE $${params.length}
OR wu.display_name ILIKE $${params.length}
OR wu.phone ILIKE $${params.length}
OR wfr.account_name ILIKE $${params.length}
OR wfr.account_no ILIKE $${params.length})`,
)
}
return {
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
params,
}
}