完善接单平台上传与物品规则

This commit is contained in:
yml2213
2026-07-25 15:56:18 +08:00
parent f6d3f00f21
commit a2587eda01
40 changed files with 4978 additions and 1008 deletions
@@ -0,0 +1,96 @@
import { query } from '../db/client.js'
export type FileAssetRow = {
id: number
object_key: string
scene: string
original_filename: string
content_type: string
size_bytes: number
thumbnail_object_key: string
medium_object_key: string
url: string
thumbnail_url: string
medium_url: string
uploader_type: string
uploader_id: string
status: string
metadata_json: string | Record<string, unknown>
created_at: string
updated_at: string
}
type CreateFileAssetInput = {
objectKey: string
scene: string
originalFilename: string
contentType: string
sizeBytes: number
thumbnailObjectKey: string
mediumObjectKey: string
url: string
thumbnailUrl: string
mediumUrl: string
uploaderType: string
uploaderId: string
metadataJson: string
now: string
}
export async function createFileAsset(input: CreateFileAssetInput): Promise<FileAssetRow | null> {
const result = await query<FileAssetRow>(
`
INSERT INTO file_assets (
object_key, scene, original_filename, content_type, size_bytes,
thumbnail_object_key, medium_object_key, url, thumbnail_url, medium_url,
uploader_type, uploader_id, metadata_json, created_at, updated_at
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8, $9, $10,
$11, $12, $13::jsonb, $14, $15
)
ON CONFLICT (object_key) DO UPDATE
SET
scene = EXCLUDED.scene,
original_filename = EXCLUDED.original_filename,
content_type = EXCLUDED.content_type,
size_bytes = EXCLUDED.size_bytes,
thumbnail_object_key = EXCLUDED.thumbnail_object_key,
medium_object_key = EXCLUDED.medium_object_key,
url = EXCLUDED.url,
thumbnail_url = EXCLUDED.thumbnail_url,
medium_url = EXCLUDED.medium_url,
uploader_type = EXCLUDED.uploader_type,
uploader_id = EXCLUDED.uploader_id,
metadata_json = EXCLUDED.metadata_json,
updated_at = EXCLUDED.updated_at
RETURNING *
`,
[
input.objectKey,
input.scene,
input.originalFilename,
input.contentType,
input.sizeBytes,
input.thumbnailObjectKey,
input.mediumObjectKey,
input.url,
input.thumbnailUrl,
input.mediumUrl,
input.uploaderType,
input.uploaderId,
input.metadataJson,
input.now,
input.now,
],
)
return result.rows[0] || null
}
export async function getFileAssetByObjectKey(objectKey: string): Promise<FileAssetRow | null> {
const result = await query<FileAssetRow>(
'SELECT * FROM file_assets WHERE object_key = $1 AND status = $2 LIMIT 1',
[String(objectKey || '').trim(), 'active'],
)
return result.rows[0] || null
}
+29 -9
View File
@@ -4,11 +4,7 @@ import type {
OrderListQueryInput,
OrderUpdateInput,
} from '../types/repository/inputs.js'
import type {
OrderListQueryResult,
OrderListRow,
OrderRow,
} from '../types/repository/rows.js'
import type { OrderListQueryResult, OrderListRow, OrderRow } from '../types/repository/rows.js'
type OrderPlatformLookupInput = {
provider?: string
@@ -55,6 +51,23 @@ export async function findLatestOrderByPlatformOrderId({
return result.rows[0] || null
}
export async function findLatestOrderByAnyPlatformOrderId(
platformOrderId: string,
): Promise<OrderRow | null> {
const result = await query<OrderRow>(
`
SELECT *
FROM orders
WHERE platform_order_id = $1
ORDER BY id DESC
LIMIT 1
`,
[String(platformOrderId || '').trim()],
)
return result.rows[0] || null
}
export async function createOrder(input: OrderCreateInput): Promise<OrderRow | null> {
const result = await query<OrderRow>(
`
@@ -101,7 +114,10 @@ export async function createOrder(input: OrderCreateInput): Promise<OrderRow | n
return result.rows[0] || null
}
export async function updateOrder(orderId: number | string, input: OrderUpdateInput): Promise<OrderRow | null> {
export async function updateOrder(
orderId: number | string,
input: OrderUpdateInput,
): Promise<OrderRow | null> {
const result = await query<OrderRow>(
`
UPDATE orders
@@ -146,7 +162,9 @@ export async function updateOrder(orderId: number | string, input: OrderUpdateIn
}
export async function getOrderById(orderId: number | string): Promise<OrderRow | null> {
const result = await query<OrderRow>('SELECT * FROM orders WHERE id = $1 LIMIT 1', [Number(orderId)])
const result = await query<OrderRow>('SELECT * FROM orders WHERE id = $1 LIMIT 1', [
Number(orderId),
])
return result.rows[0] || null
}
@@ -175,7 +193,9 @@ export async function listOrders({
if (skuCode) {
params.push(skuCode)
filters.push(`EXISTS (SELECT 1 FROM order_items oi WHERE oi.order_id = o.id AND oi.sku_code = $${params.length})`)
filters.push(
`EXISTS (SELECT 1 FROM order_items oi WHERE oi.order_id = o.id AND oi.sku_code = $${params.length})`,
)
}
if (dateFrom) {
@@ -189,7 +209,7 @@ export async function listOrders({
}
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
const totalResult = await query<{ [column: string]: unknown, total: number }>(
const totalResult = await query<{ [column: string]: unknown; total: number }>(
`SELECT COUNT(*)::int AS total FROM orders o ${whereClause}`,
params,
)
@@ -53,6 +53,28 @@ export type WorkCategoryRow = {
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
requirement_json: string | Record<string, unknown>
sort_order: number
created_at: string
updated_at: string
category_name?: string
}
export type WorkOrderRow = {
id: number
work_order_no: string
@@ -90,6 +112,11 @@ type ListInput = {
workerId?: number
}
type ProductRuleListInput = {
enabled?: boolean | null
keyword?: string
}
type CreateWorkerInput = {
username: string
passwordHash: string
@@ -141,6 +168,14 @@ const WORK_ORDER_SELECT = `
LEFT JOIN worker_users wu ON wu.id = wo.assigned_worker_id
`
const WORK_PRODUCT_RULE_SELECT = `
SELECT
wpr.*,
wc.name AS category_name
FROM work_product_rules wpr
LEFT JOIN work_categories wc ON wc.id = wpr.category_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',
@@ -150,7 +185,9 @@ export async function getWorkerLevelByKey(levelKey: string): Promise<WorkerLevel
}
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)])
const result = await query<WorkerLevelRow>('SELECT * FROM worker_levels WHERE id = $1 LIMIT 1', [
Number(levelId),
])
return result.rows[0] || null
}
@@ -199,16 +236,19 @@ export async function upsertWorkerLevel(input: {
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()],
[
String(username || '')
.trim()
.toLowerCase(),
],
)
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)],
)
const result = await query<WorkerUserRow>(`${WORKER_USER_SELECT} WHERE wu.id = $1 LIMIT 1`, [
Number(workerId),
])
return result.rows[0] || null
}
@@ -241,7 +281,12 @@ export async function createWorkerUser(input: CreateWorkerInput): Promise<Worker
export async function updateWorkerUser(
workerId: number | string,
patch: Partial<Pick<WorkerUserRow, 'status' | 'review_note' | 'level_id' | 'session_version' | 'updated_at' | 'reviewed_at'>>,
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
@@ -296,7 +341,10 @@ export async function listWorkerUsers({
return { items: itemsResult.rows, total: Number(totalResult.rows[0]?.total || 0) }
}
export async function ensureWorkerWallet(workerId: number | string, now: string): Promise<WorkerWalletRow | null> {
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))
@@ -341,7 +389,15 @@ export async function addWorkerWalletCredit(input: {
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],
[
input.workerId,
input.amount,
nextAvailable,
nextFrozen,
input.note,
input.payloadJson,
input.now,
],
)
return getWorkerWalletWithClient(client, input.workerId)
})
@@ -362,6 +418,13 @@ export async function listWorkCategories(): Promise<WorkCategoryRow[]> {
return result.rows
}
export async function listAllWorkCategories(): Promise<WorkCategoryRow[]> {
const result = await query<WorkCategoryRow>(
`SELECT * FROM work_categories ORDER BY sort_order ASC, id ASC`,
)
return result.rows
}
export async function upsertWorkCategory(input: {
categoryKey: string
name: string
@@ -382,6 +445,101 @@ export async function upsertWorkCategory(input: {
return result.rows[0] || null
}
export async function listWorkProductRules({
enabled = null,
keyword = '',
}: ProductRuleListInput = {}): Promise<WorkProductRuleRow[]> {
const { whereClause, params } = buildWorkProductRuleWhere({ enabled, keyword })
const result = await query<WorkProductRuleRow>(
`${WORK_PRODUCT_RULE_SELECT}
${whereClause}
ORDER BY wpr.sort_order ASC, wpr.id DESC`,
params,
)
return result.rows
}
export async function getWorkProductRuleByKey(ruleKey: string): Promise<WorkProductRuleRow | null> {
const result = await query<WorkProductRuleRow>(
`${WORK_PRODUCT_RULE_SELECT} WHERE wpr.rule_key = $1 LIMIT 1`,
[String(ruleKey || '').trim()],
)
return result.rows[0] || null
}
export async function upsertWorkProductRule(input: {
ruleKey: string
provider: string
platform: string
shopId: string
skuCode: string
productName: string
matchType: string
categoryId: number | null
enabled: boolean
autoCreate: boolean
rewardAmount: number
requiredDepositAmount: number
depositThresholdAmount: number
requirementJson: string
sortOrder: number
now: string
}): Promise<WorkProductRuleRow | null> {
const result = await query<WorkProductRuleRow>(
`
INSERT INTO work_product_rules (
rule_key, provider, platform, shop_id, sku_code, product_name,
match_type, category_id, enabled, auto_create, reward_amount,
required_deposit_amount, deposit_threshold_amount, requirement_json,
sort_order, created_at, updated_at
) VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, $9, $10, $11,
$12, $13, $14::jsonb,
$15, $16, $17
)
ON CONFLICT (rule_key) DO UPDATE
SET
provider = EXCLUDED.provider,
platform = EXCLUDED.platform,
shop_id = EXCLUDED.shop_id,
sku_code = EXCLUDED.sku_code,
product_name = EXCLUDED.product_name,
match_type = EXCLUDED.match_type,
category_id = EXCLUDED.category_id,
enabled = EXCLUDED.enabled,
auto_create = EXCLUDED.auto_create,
reward_amount = EXCLUDED.reward_amount,
required_deposit_amount = EXCLUDED.required_deposit_amount,
deposit_threshold_amount = EXCLUDED.deposit_threshold_amount,
requirement_json = EXCLUDED.requirement_json,
sort_order = EXCLUDED.sort_order,
updated_at = EXCLUDED.updated_at
RETURNING *
`,
[
input.ruleKey,
input.provider,
input.platform,
input.shopId,
input.skuCode,
input.productName,
input.matchType,
input.categoryId,
input.enabled,
input.autoCreate,
input.rewardAmount,
input.requiredDepositAmount,
input.depositThresholdAmount,
input.requirementJson,
input.sortOrder,
input.now,
input.now,
],
)
return getWorkProductRuleByKey(result.rows[0]?.rule_key || input.ruleKey)
}
export async function createWorkOrder(input: CreateWorkOrderInput): Promise<WorkOrderRow | null> {
const result = await query<{ id: number }>(
`
@@ -416,14 +574,23 @@ export async function createWorkOrder(input: CreateWorkOrderInput): Promise<Work
return getWorkOrderById(result.rows[0]?.id || 0)
}
export async function getWorkOrderById(workOrderId: number | string): Promise<WorkOrderRow | null> {
export async function getWorkOrderByOrderItemId(
orderItemId: number | string,
): Promise<WorkOrderRow | null> {
const result = await query<WorkOrderRow>(
`${WORK_ORDER_SELECT} WHERE wo.id = $1 LIMIT 1`,
[Number(workOrderId)],
`${WORK_ORDER_SELECT} WHERE wo.order_item_id = $1 LIMIT 1`,
[Number(orderItemId)],
)
return result.rows[0] || null
}
export async function getWorkOrderById(workOrderId: number | string): Promise<WorkOrderRow | null> {
const result = await query<WorkOrderRow>(`${WORK_ORDER_SELECT} WHERE wo.id = $1 LIMIT 1`, [
Number(workOrderId),
])
return result.rows[0] || null
}
export async function findPendingMaterialWorkOrderByPlatformOrderId(
platformOrderId: string,
): Promise<WorkOrderRow | null> {
@@ -467,19 +634,21 @@ export async function listWorkOrders({
export async function updateWorkOrder(
workOrderId: number | string,
patch: Partial<Pick<
WorkOrderRow,
| 'status'
| 'assigned_worker_id'
| 'material_json'
| 'acceptance_json'
| 'problem_note'
| 'published_at'
| 'assigned_at'
| 'submitted_at'
| 'accepted_at'
| 'updated_at'
>>,
patch: Partial<
Pick<
WorkOrderRow,
| 'status'
| 'assigned_worker_id'
| 'material_json'
| 'acceptance_json'
| 'problem_note'
| 'published_at'
| 'assigned_at'
| 'submitted_at'
| 'accepted_at'
| 'updated_at'
>
>,
): Promise<WorkOrderRow | null> {
const current = await getWorkOrderById(workOrderId)
if (!current) return null
@@ -568,7 +737,14 @@ export async function grabWorkOrder(input: {
related_work_order_id, note, payload_json, created_at
) VALUES ($1, 'deposit_freeze', $2, $3, $4, $5, '抢单冻结押金', '{}'::jsonb, $6)
`,
[input.workerId, -input.depositAmount, nextAvailable, nextFrozen, input.workOrderId, input.now],
[
input.workerId,
-input.depositAmount,
nextAvailable,
nextFrozen,
input.workOrderId,
input.now,
],
)
}
@@ -756,7 +932,11 @@ async function createWorkOrderEventWithClient(
)
}
async function ensureWorkerWalletWithClient(client: PoolClient, workerId: number, now: string): Promise<void> {
async function ensureWorkerWalletWithClient(
client: PoolClient,
workerId: number,
now: string,
): Promise<void> {
if (!workerId) return
await client.query(
`
@@ -770,7 +950,10 @@ async function ensureWorkerWalletWithClient(client: PoolClient, workerId: number
)
}
async function getWorkerWalletWithClient(client: PoolClient, workerId: number): Promise<WorkerWalletRow | null> {
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',
[workerId],
@@ -778,7 +961,10 @@ async function getWorkerWalletWithClient(client: PoolClient, workerId: number):
return result.rows[0] || null
}
async function getWorkerUserByIdWithClient(client: PoolClient, workerId: number | string): Promise<WorkerUserRow | null> {
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)],
@@ -786,15 +972,20 @@ async function getWorkerUserByIdWithClient(client: PoolClient, workerId: number
return result.rows[0] || null
}
async function getWorkOrderByIdWithClient(client: PoolClient, workOrderId: number | string): Promise<WorkOrderRow | null> {
const result = await client.query<WorkOrderRow>(
`${WORK_ORDER_SELECT} WHERE wo.id = $1 LIMIT 1`,
[Number(workOrderId)],
)
async function getWorkOrderByIdWithClient(
client: PoolClient,
workOrderId: number | string,
): Promise<WorkOrderRow | null> {
const result = await client.query<WorkOrderRow>(`${WORK_ORDER_SELECT} WHERE wo.id = $1 LIMIT 1`, [
Number(workOrderId),
])
return result.rows[0] || null
}
function buildWorkerUserWhere({ status = '', keyword = '' }: Pick<ListInput, 'status' | 'keyword'>) {
function buildWorkerUserWhere({
status = '',
keyword = '',
}: Pick<ListInput, 'status' | 'keyword'>) {
const filters: string[] = []
const params: unknown[] = []
if (status) {
@@ -803,7 +994,9 @@ function buildWorkerUserWhere({ status = '', keyword = '' }: Pick<ListInput, 'st
}
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})`)
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 ')}` : '',
@@ -820,7 +1013,9 @@ function buildWorkOrderWhere({ status = '', keyword = '', workerId = 0 }: ListIn
}
if (keyword) {
params.push(`%${keyword}%`)
filters.push(`(wo.work_order_no ILIKE $${params.length} OR wo.platform_order_id ILIKE $${params.length} OR wo.product_name ILIKE $${params.length})`)
filters.push(
`(wo.work_order_no ILIKE $${params.length} OR wo.platform_order_id ILIKE $${params.length} OR wo.product_name ILIKE $${params.length})`,
)
}
if (workerId) {
params.push(workerId)
@@ -832,6 +1027,27 @@ function buildWorkOrderWhere({ status = '', keyword = '', workerId = 0 }: ListIn
}
}
function buildWorkProductRuleWhere({ enabled = null, keyword = '' }: ProductRuleListInput) {
const filters: string[] = []
const params: unknown[] = []
if (enabled !== null && enabled !== undefined) {
params.push(Boolean(enabled))
filters.push(`wpr.enabled = $${params.length}`)
}
if (keyword) {
params.push(`%${keyword}%`)
filters.push(`(
wpr.rule_key ILIKE $${params.length}
OR wpr.sku_code ILIKE $${params.length}
OR wpr.product_name ILIKE $${params.length}
)`)
}
return {
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
params,
}
}
function toJsonString(value: unknown): string {
if (typeof value === 'string') return value || '{}'
return JSON.stringify(value || {})