feat(collect): 补资料改为订单号直连并按状态一次性提交
- 公开补资料页 /supplement/:订单号 直接按平台订单号查询,不再依赖链接记录;旧 token 链接一律 404 - 仅「待完善 + 未指派 + 无拼单份额」可提交,提交后状态变为待分配,重复提交返回 409 - 非可编辑工单只返回商品、工单号、状态与客服提示,不返回已填资料和截图 - 管理端新增「重置补资料」:待分配且未指派、无份额、未发布时一键重置回待完善 - 新增 canResetMaterial 能力与 material_reset 流转事件,工单状态文案统一为「待分配」
This commit is contained in:
@@ -537,6 +537,8 @@ export type WorkOrderCapabilities = {
|
||||
canPin: boolean
|
||||
canUnpublish: boolean
|
||||
canUpdateMaterial: boolean
|
||||
/** 待分配且未指派、无份额、未发布时,客服可以把补资料状态重置回待完善。 */
|
||||
canResetMaterial: boolean
|
||||
canAccept: boolean
|
||||
canMarkProblem: boolean
|
||||
canDelete: boolean
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { PoolClient } from 'pg'
|
||||
|
||||
import { query } from '../../db/client.js'
|
||||
import type { WorkOrderRow } from './types.js'
|
||||
|
||||
export type WorkOrderMaterialLinkStatus = 'active' | 'used' | 'expired' | 'revoked'
|
||||
@@ -17,52 +16,31 @@ export type WorkOrderMaterialLinkRow = {
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export async function findWorkOrderMaterialLinkByToken(
|
||||
token: string,
|
||||
): Promise<WorkOrderMaterialLinkRow | null> {
|
||||
const result = await query<WorkOrderMaterialLinkRow>(
|
||||
'SELECT * FROM work_order_material_links WHERE token = $1 LIMIT 1',
|
||||
[String(token || '').trim()],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function findActiveWorkOrderMaterialLink(
|
||||
workOrderId: number | string,
|
||||
now: string,
|
||||
): Promise<WorkOrderMaterialLinkRow | null> {
|
||||
const result = await query<WorkOrderMaterialLinkRow>(
|
||||
`SELECT * FROM work_order_material_links
|
||||
WHERE work_order_id = $1 AND status = 'active' AND expires_at > $2
|
||||
ORDER BY id DESC
|
||||
LIMIT 1`,
|
||||
[Number(workOrderId), now],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁工单并回读「有效拼单份额」。
|
||||
*
|
||||
* `SELECT * FROM work_orders` 本身没有聚合字段,直接看 `active_share_quantity`
|
||||
* 会永远是 0,导致补资料绕过「已有拼单份额不允许改资料」的守卫。
|
||||
*/
|
||||
export async function getWorkOrderForUpdateWithClient(
|
||||
client: PoolClient,
|
||||
workOrderId: number | string,
|
||||
): Promise<WorkOrderRow | null> {
|
||||
const result = await client.query<WorkOrderRow>(
|
||||
'SELECT * FROM work_orders WHERE id = $1 FOR UPDATE',
|
||||
`SELECT wo.*,
|
||||
COALESCE((
|
||||
SELECT SUM(wos.quantity)
|
||||
FROM work_order_shares wos
|
||||
WHERE wos.work_order_id = wo.id AND wos.status != 'cancelled'
|
||||
), 0)::int AS active_share_quantity
|
||||
FROM work_orders wo
|
||||
WHERE wo.id = $1
|
||||
FOR UPDATE`,
|
||||
[Number(workOrderId)],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function getWorkOrderMaterialLinkForUpdateWithClient(
|
||||
client: PoolClient,
|
||||
linkId: number | string,
|
||||
): Promise<WorkOrderMaterialLinkRow | null> {
|
||||
const result = await client.query<WorkOrderMaterialLinkRow>(
|
||||
'SELECT * FROM work_order_material_links WHERE id = $1 FOR UPDATE',
|
||||
[Number(linkId)],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function findActiveWorkOrderMaterialLinkWithClient(
|
||||
client: PoolClient,
|
||||
workOrderId: number | string,
|
||||
@@ -120,33 +98,6 @@ export async function createWorkOrderMaterialLinkWithClient(
|
||||
return result.rows[0]!
|
||||
}
|
||||
|
||||
export async function markWorkOrderMaterialLinkUsedWithClient(
|
||||
client: PoolClient,
|
||||
linkId: number | string,
|
||||
now: string,
|
||||
): Promise<WorkOrderMaterialLinkRow | null> {
|
||||
const result = await client.query<WorkOrderMaterialLinkRow>(
|
||||
`UPDATE work_order_material_links
|
||||
SET status = 'used', used_at = $2, updated_at = $2
|
||||
WHERE id = $1 AND status = 'active' AND expires_at > $2
|
||||
RETURNING *`,
|
||||
[Number(linkId), now],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function markWorkOrderMaterialLinkExpired(
|
||||
linkId: number | string,
|
||||
now: string,
|
||||
): Promise<void> {
|
||||
await query(
|
||||
`UPDATE work_order_material_links
|
||||
SET status = 'expired', updated_at = $2
|
||||
WHERE id = $1 AND status = 'active'`,
|
||||
[Number(linkId), now],
|
||||
)
|
||||
}
|
||||
|
||||
export async function updateWorkOrderMaterialWithClient(
|
||||
client: PoolClient,
|
||||
input: {
|
||||
@@ -165,3 +116,32 @@ export async function updateWorkOrderMaterialWithClient(
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 客服重置补资料状态:只允许「待分配、未指派、未发布」的工单回到待完善。
|
||||
*
|
||||
* 服务层已经在事务里锁工单并校验过拼单份额;这里的 WHERE 条件再兜一层,
|
||||
* 防止并发或调用方漏校验。
|
||||
*/
|
||||
export async function resetWorkOrderMaterialStatusWithClient(
|
||||
client: PoolClient,
|
||||
input: {
|
||||
workOrderId: number | string
|
||||
fromStatus: string
|
||||
toStatus: string
|
||||
now: string
|
||||
},
|
||||
): Promise<WorkOrderRow | null> {
|
||||
const result = await client.query<WorkOrderRow>(
|
||||
`UPDATE work_orders
|
||||
SET status = $2, updated_at = $3
|
||||
WHERE id = $1
|
||||
AND status = $4
|
||||
AND assigned_worker_id IS NULL
|
||||
AND published_at IS NULL
|
||||
AND hall_queued_at IS NULL
|
||||
RETURNING *`,
|
||||
[Number(input.workOrderId), input.toStatus, input.now, input.fromStatus],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user