feat: 增加打手工单备注
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
-- 020_worker_order_notes.sql —— 打手工单备注。
|
||||
--
|
||||
-- 整单与拼单份额分别保存,确保拼单打手只能查看和修改自己的备注。
|
||||
|
||||
ALTER TABLE work_orders
|
||||
ADD COLUMN IF NOT EXISTS worker_note TEXT NOT NULL DEFAULT '';
|
||||
|
||||
ALTER TABLE work_order_shares
|
||||
ADD COLUMN IF NOT EXISTS worker_note TEXT NOT NULL DEFAULT '';
|
||||
|
||||
COMMENT ON COLUMN work_orders.worker_note IS '整单打手维护的个人备注';
|
||||
COMMENT ON COLUMN work_order_shares.worker_note IS '拼单打手维护的个人备注';
|
||||
@@ -0,0 +1,14 @@
|
||||
-- 021_worker_order_notes_by_worker.sql —— 按打手保存工单备注。
|
||||
--
|
||||
-- 工单可重新分配,拼单也有多个打手;备注必须按工单和打手独立存储。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS worker_work_order_notes (
|
||||
work_order_id BIGINT NOT NULL REFERENCES work_orders(id) ON DELETE CASCADE,
|
||||
worker_id BIGINT NOT NULL REFERENCES worker_users(id) ON DELETE CASCADE,
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
PRIMARY KEY (work_order_id, worker_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE worker_work_order_notes IS '打手维护的个人工单备注';
|
||||
@@ -384,6 +384,50 @@ export async function updateWorkOrderShareAcceptanceRecord(input: {
|
||||
return getWorkOrderShareById(share.id)
|
||||
}
|
||||
|
||||
export async function listWorkerWorkOrderNotes(
|
||||
workerId: number | string,
|
||||
workOrderIds: number[],
|
||||
): Promise<Array<{ work_order_id: number; note: string }>> {
|
||||
if (workOrderIds.length === 0) return []
|
||||
const result = await query<{ work_order_id: number; note: string }>(
|
||||
`
|
||||
SELECT work_order_id, note
|
||||
FROM worker_work_order_notes
|
||||
WHERE worker_id = $1 AND work_order_id = ANY($2::bigint[])
|
||||
`,
|
||||
[Number(workerId), workOrderIds],
|
||||
)
|
||||
return result.rows
|
||||
}
|
||||
|
||||
export async function upsertWorkerWorkOrderNote(input: {
|
||||
workOrderId: number
|
||||
workerId: number
|
||||
note: string
|
||||
now: string
|
||||
}): Promise<string> {
|
||||
const result = await query<{ note: string }>(
|
||||
`
|
||||
INSERT INTO worker_work_order_notes (
|
||||
work_order_id, worker_id, note, created_at, updated_at
|
||||
) VALUES ($1, $2, $3, $4, $4)
|
||||
ON CONFLICT (work_order_id, worker_id) DO UPDATE
|
||||
SET note = EXCLUDED.note, updated_at = EXCLUDED.updated_at
|
||||
RETURNING note
|
||||
`,
|
||||
[input.workOrderId, input.workerId, input.note, input.now],
|
||||
)
|
||||
return String(result.rows[0]?.note || '')
|
||||
}
|
||||
|
||||
async function getWorkOrderShareById(shareId: number): Promise<WorkOrderShareRow | null> {
|
||||
const result = await query<WorkOrderShareRow>(
|
||||
`${WORK_ORDER_SHARE_SELECT} WHERE wos.id = $1 LIMIT 1`,
|
||||
[shareId],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
export async function getWorkCategoryByKey(categoryKey: string): Promise<WorkCategoryRow | null> {
|
||||
const result = await query<WorkCategoryRow>(
|
||||
'SELECT * FROM work_categories WHERE category_key = $1 LIMIT 1',
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
registerWorker,
|
||||
sendWorkerSmsCode,
|
||||
saveWorkerOrderAcceptanceDraft,
|
||||
saveWorkerOrderNote,
|
||||
submitWorkerOrderAcceptance,
|
||||
} from '../services/worker-platform/index.js'
|
||||
import { buildNotFoundPayload, createRouteHandler } from '../utils/http.js'
|
||||
@@ -229,6 +230,24 @@ router.get(
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/orders/:workOrderId/note',
|
||||
requireActiveWorker,
|
||||
createRouteHandler(
|
||||
(req) =>
|
||||
saveWorkerOrderNote(
|
||||
String(req.params.workOrderId || ''),
|
||||
req.body || {},
|
||||
getRequiredWorkerSession(req),
|
||||
),
|
||||
{
|
||||
successMessage: '备注已保存',
|
||||
errorMessage: '保存备注失败',
|
||||
scope: '[worker/orders/:workOrderId/note]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/orders/:workOrderId/submit-acceptance',
|
||||
requireActiveWorker,
|
||||
|
||||
@@ -416,6 +416,7 @@ export function mapWorkOrderForWorker(
|
||||
workOrder: WorkOrderRow,
|
||||
permissions: { depositFreeAmount: number },
|
||||
myShare?: WorkOrderShareRow | null,
|
||||
workerNote = '',
|
||||
) {
|
||||
const mapped = mapWorkOrderAdmin(workOrder)
|
||||
const {
|
||||
@@ -429,6 +430,7 @@ export function mapWorkOrderForWorker(
|
||||
return {
|
||||
...visibleOrder,
|
||||
freezeDepositAmount: resolveFreezeDepositAmount(workOrder, permissions),
|
||||
workerNote,
|
||||
myShare: myShare ? mapWorkOrderShare(myShare) : null,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
listWorkerFinanceRequests,
|
||||
listWorkerSharesByWorker,
|
||||
listWorkerWalletLedgers,
|
||||
listWorkerWorkOrderNotes,
|
||||
listWorkCategories,
|
||||
listWorkOrders,
|
||||
listWorkProductRules,
|
||||
@@ -67,6 +68,7 @@ import {
|
||||
updateWorkOrderDraftAcceptance,
|
||||
updateWorkOrderShareAcceptanceRecord,
|
||||
updateWorkOrderShareDraftAcceptance,
|
||||
upsertWorkerWorkOrderNote,
|
||||
updateWorkerPassword,
|
||||
updateWorkerUser,
|
||||
upsertWorkCategory,
|
||||
@@ -981,14 +983,69 @@ export async function listWorkerMyOrders(query: JsonObject = {}, session: Worker
|
||||
const myShareByOrderId = new Map(
|
||||
myShares.map((share) => [Number(share.work_order_id), share]),
|
||||
)
|
||||
const notes = await listWorkerWorkOrderNotes(
|
||||
worker.id,
|
||||
items.map((item) => Number(item.id)),
|
||||
)
|
||||
const noteByOrderId = new Map(
|
||||
notes.map((item) => [Number(item.work_order_id), String(item.note || '')]),
|
||||
)
|
||||
return {
|
||||
items: items.map((item) =>
|
||||
mapWorkOrderForWorker(item, permissions, myShareByOrderId.get(Number(item.id))),
|
||||
mapWorkOrderForWorker(
|
||||
item,
|
||||
permissions,
|
||||
myShareByOrderId.get(Number(item.id)),
|
||||
noteByOrderId.get(Number(item.id)) || '',
|
||||
),
|
||||
),
|
||||
pagination: { page, pageSize, total },
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveWorkerOrderNote(
|
||||
workOrderId: number | string,
|
||||
payload: JsonObject = {},
|
||||
session: WorkerSession,
|
||||
) {
|
||||
requireActiveWorkerSession(session)
|
||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||
const workerId = Number(session.workerId)
|
||||
const note = String(payload.note || '').trim()
|
||||
if (note.length > 100) {
|
||||
throw createHttpError('备注不能超过 100 个字符', {
|
||||
statusCode: 400,
|
||||
errorCode: 'work_order_note_too_long',
|
||||
})
|
||||
}
|
||||
|
||||
const worker = await getRequiredWorker(workerId)
|
||||
const sharingShare = await getWorkOrderShare(workOrder.id, workerId)
|
||||
const isOrderOwner =
|
||||
Number(workOrder.assigned_worker_id || 0) === workerId ||
|
||||
Number(workOrder.last_assigned_worker_id || 0) === workerId
|
||||
if (!sharingShare && !isOrderOwner) {
|
||||
throw createHttpError('只能修改自己的订单备注', {
|
||||
statusCode: 403,
|
||||
errorCode: 'work_order_note_owner_required',
|
||||
})
|
||||
}
|
||||
const savedNote = await upsertWorkerWorkOrderNote({
|
||||
workOrderId: Number(workOrder.id),
|
||||
workerId,
|
||||
note,
|
||||
now: nowIso(),
|
||||
})
|
||||
return {
|
||||
order: mapWorkOrderForWorker(
|
||||
workOrder,
|
||||
resolveWorkerPermissions(worker),
|
||||
sharingShare,
|
||||
savedNote,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export async function joinWorkerSharingOrder(
|
||||
workOrderId: number | string,
|
||||
payload: JsonObject = {},
|
||||
|
||||
Reference in New Issue
Block a user