新增打手问题反馈
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
-- 打手问题反馈:关联工单,由客服回复并关闭,不改变工单状态或资金。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS worker_order_feedbacks (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
work_order_id BIGINT NOT NULL REFERENCES work_orders(id) ON DELETE CASCADE,
|
||||
work_order_share_id BIGINT REFERENCES work_order_shares(id) ON DELETE SET NULL,
|
||||
worker_id BIGINT NOT NULL REFERENCES worker_users(id) ON DELETE CASCADE,
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
reply TEXT NOT NULL DEFAULT '',
|
||||
handled_by TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
handled_at TIMESTAMPTZ,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_worker_order_feedbacks_status_created
|
||||
ON worker_order_feedbacks (status, created_at DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_worker_order_feedbacks_worker_created
|
||||
ON worker_order_feedbacks (worker_id, created_at DESC);
|
||||
|
||||
COMMENT ON TABLE worker_order_feedbacks IS '打手针对工单提交的客户资料和处理问题反馈';
|
||||
@@ -87,6 +87,25 @@ export type WorkerCancelRequestRow = {
|
||||
share_quantity?: number | null
|
||||
}
|
||||
|
||||
export type WorkerOrderFeedbackRow = {
|
||||
id: number
|
||||
work_order_id: number
|
||||
work_order_share_id: number | null
|
||||
worker_id: number
|
||||
content: string
|
||||
status: string
|
||||
reply: string
|
||||
handled_by: string
|
||||
created_at: string
|
||||
handled_at: string | null
|
||||
updated_at: string
|
||||
work_order_no?: string
|
||||
product_name?: string
|
||||
worker_username?: string
|
||||
worker_display_name?: string
|
||||
share_quantity?: number | null
|
||||
}
|
||||
|
||||
export type WorkerDepositUnfreezeRow = {
|
||||
id: number
|
||||
worker_id: number
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
WorkCategoryRow,
|
||||
WorkerDepositUnfreezeRow,
|
||||
WorkerCancelRequestRow,
|
||||
WorkerOrderFeedbackRow,
|
||||
WorkOrderRow,
|
||||
WorkOrderShareRow,
|
||||
WorkOrderStatistics,
|
||||
@@ -74,6 +75,20 @@ const WORKER_CANCEL_REQUEST_SELECT = `
|
||||
LEFT JOIN work_order_shares wos ON wos.id = cr.work_order_share_id
|
||||
`
|
||||
|
||||
const WORKER_ORDER_FEEDBACK_SELECT = `
|
||||
SELECT
|
||||
wf.*,
|
||||
COALESCE(NULLIF(BTRIM(wo.platform_order_id), ''), wo.work_order_no) AS work_order_no,
|
||||
wo.product_name,
|
||||
wu.username AS worker_username,
|
||||
wu.display_name AS worker_display_name,
|
||||
wos.quantity AS share_quantity
|
||||
FROM worker_order_feedbacks wf
|
||||
INNER JOIN work_orders wo ON wo.id = wf.work_order_id
|
||||
INNER JOIN worker_users wu ON wu.id = wf.worker_id
|
||||
LEFT JOIN work_order_shares wos ON wos.id = wf.work_order_share_id
|
||||
`
|
||||
|
||||
type WorkerOrderOverviewRow = {
|
||||
status: string
|
||||
order_count: number
|
||||
@@ -1722,6 +1737,142 @@ export async function cancelWorkerWorkOrder(input: {
|
||||
})
|
||||
}
|
||||
|
||||
/** 打手提交工单问题反馈,不改变工单状态、押金或资金流水。 */
|
||||
export async function createWorkerOrderFeedback(input: {
|
||||
workOrderId: number
|
||||
workerId: number
|
||||
content: string
|
||||
now: string
|
||||
}): Promise<{ feedback: WorkerOrderFeedbackRow | null; failureReason: 'owner_required' | null }> {
|
||||
return withTransaction(async (client) => {
|
||||
const orderResult = await client.query<WorkOrderRow>(
|
||||
'SELECT * FROM work_orders WHERE id = $1 FOR UPDATE',
|
||||
[input.workOrderId],
|
||||
)
|
||||
const order = orderResult.rows[0] || null
|
||||
if (!order) return { feedback: null, failureReason: 'owner_required' }
|
||||
|
||||
const shareResult = await client.query<WorkOrderShareRow>(
|
||||
`SELECT * FROM work_order_shares
|
||||
WHERE work_order_id = $1 AND worker_id = $2 AND status != 'cancelled'
|
||||
FOR UPDATE`,
|
||||
[input.workOrderId, input.workerId],
|
||||
)
|
||||
const share = shareResult.rows[0] || null
|
||||
const isOrderOwner =
|
||||
Number(order.assigned_worker_id || 0) === input.workerId ||
|
||||
Number(order.last_assigned_worker_id || 0) === input.workerId
|
||||
if (!share && !isOrderOwner) return { feedback: null, failureReason: 'owner_required' }
|
||||
|
||||
const result = await client.query<{ id: number }>(
|
||||
`INSERT INTO worker_order_feedbacks (
|
||||
work_order_id, work_order_share_id, worker_id, content, status, created_at, updated_at
|
||||
) VALUES ($1, $2, $3, $4, 'pending', $5, $5)
|
||||
RETURNING id`,
|
||||
[input.workOrderId, share?.id || null, input.workerId, input.content, input.now],
|
||||
)
|
||||
const feedbackId = Number(result.rows[0]?.id || 0)
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'worker',
|
||||
actorId: String(input.workerId),
|
||||
eventType: 'worker_feedback_submitted',
|
||||
fromStatus: order.status,
|
||||
toStatus: order.status,
|
||||
payloadJson: JSON.stringify({
|
||||
feedbackId,
|
||||
shareId: share?.id || null,
|
||||
content: input.content,
|
||||
}),
|
||||
now: input.now,
|
||||
})
|
||||
return {
|
||||
feedback: await getWorkerOrderFeedbackByIdWithClient(client, feedbackId),
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function listWorkerOrderFeedbacks(
|
||||
workerId: number | string,
|
||||
): Promise<WorkerOrderFeedbackRow[]> {
|
||||
const result = await query<WorkerOrderFeedbackRow>(
|
||||
`${WORKER_ORDER_FEEDBACK_SELECT}
|
||||
WHERE wf.worker_id = $1
|
||||
ORDER BY wf.id DESC
|
||||
LIMIT 100`,
|
||||
[Number(workerId)],
|
||||
)
|
||||
return result.rows
|
||||
}
|
||||
|
||||
export async function listAdminWorkerOrderFeedbacks(
|
||||
status = 'pending',
|
||||
): Promise<WorkerOrderFeedbackRow[]> {
|
||||
const result = await query<WorkerOrderFeedbackRow>(
|
||||
`${WORKER_ORDER_FEEDBACK_SELECT}
|
||||
WHERE ($1 = '' OR wf.status = $1)
|
||||
ORDER BY wf.id DESC
|
||||
LIMIT 200`,
|
||||
[status],
|
||||
)
|
||||
return result.rows
|
||||
}
|
||||
|
||||
export async function resolveWorkerOrderFeedback(input: {
|
||||
feedbackId: number
|
||||
reply: string
|
||||
actorName: string
|
||||
now: string
|
||||
}): Promise<{
|
||||
feedback: WorkerOrderFeedbackRow | null
|
||||
order: WorkOrderRow | null
|
||||
failureReason: 'not_found' | 'not_pending' | null
|
||||
}> {
|
||||
return withTransaction(async (client) => {
|
||||
const feedbackResult = await client.query<WorkerOrderFeedbackRow>(
|
||||
'SELECT * FROM worker_order_feedbacks WHERE id = $1 FOR UPDATE',
|
||||
[input.feedbackId],
|
||||
)
|
||||
const feedback = feedbackResult.rows[0] || null
|
||||
if (!feedback) return { feedback: null, order: null, failureReason: 'not_found' }
|
||||
if (feedback.status !== 'pending') {
|
||||
return {
|
||||
feedback: await getWorkerOrderFeedbackByIdWithClient(client, feedback.id),
|
||||
order: null,
|
||||
failureReason: 'not_pending',
|
||||
}
|
||||
}
|
||||
const orderResult = await client.query<WorkOrderRow>(
|
||||
'SELECT * FROM work_orders WHERE id = $1 FOR UPDATE',
|
||||
[feedback.work_order_id],
|
||||
)
|
||||
const order = orderResult.rows[0] || null
|
||||
if (!order) return { feedback: null, order: null, failureReason: 'not_found' }
|
||||
await client.query(
|
||||
`UPDATE worker_order_feedbacks
|
||||
SET status = 'resolved', reply = $1, handled_by = $2, handled_at = $3, updated_at = $3
|
||||
WHERE id = $4`,
|
||||
[input.reply, input.actorName, input.now, feedback.id],
|
||||
)
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: Number(order.id),
|
||||
actorType: 'admin',
|
||||
actorId: input.actorName,
|
||||
eventType: 'worker_feedback_resolved',
|
||||
fromStatus: order.status,
|
||||
toStatus: order.status,
|
||||
payloadJson: JSON.stringify({ feedbackId: feedback.id, reply: input.reply }),
|
||||
now: input.now,
|
||||
})
|
||||
return {
|
||||
feedback: await getWorkerOrderFeedbackByIdWithClient(client, feedback.id),
|
||||
order: await getWorkOrderByIdWithClient(client, order.id),
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** 打手提交撤单申请;待审核期间订单与押金均保持原状。 */
|
||||
export async function createWorkerCancelRequest(input: {
|
||||
workOrderId: number
|
||||
@@ -3782,6 +3933,17 @@ async function getWorkerCancelRequestByIdWithClient(
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
async function getWorkerOrderFeedbackByIdWithClient(
|
||||
client: PoolClient,
|
||||
feedbackId: number | string,
|
||||
): Promise<WorkerOrderFeedbackRow | null> {
|
||||
const result = await client.query<WorkerOrderFeedbackRow>(
|
||||
`${WORKER_ORDER_FEEDBACK_SELECT} WHERE wf.id = $1 LIMIT 1`,
|
||||
[Number(feedbackId)],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
}
|
||||
|
||||
async function countWorkerAcceptedOrdersWithClient(
|
||||
client: PoolClient,
|
||||
workerId: number,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
listAdminWorkerUsers,
|
||||
listAdminWorkOrders,
|
||||
listAdminWorkerCancelRequests,
|
||||
listAdminWorkerOrderFeedbacks,
|
||||
markAdminWorkOrderProblem,
|
||||
pinAdminWorkOrder,
|
||||
publishAdminWorkOrder,
|
||||
@@ -39,6 +40,7 @@ import {
|
||||
reprocessAdminKuaishouSendCodeWorkOrders,
|
||||
reviewAdminWorkerFinanceRequest,
|
||||
reviewAdminWorkerCancelRequest,
|
||||
resolveAdminWorkerOrderFeedback,
|
||||
resolveAdminProblemWorkOrder,
|
||||
reviewAdminWorkerUser,
|
||||
resetAdminWorkerPassword,
|
||||
@@ -495,6 +497,34 @@ router.get(
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/worker-platform/feedbacks',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
createJsonHandler((req) => listAdminWorkerOrderFeedbacks(req.query), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取问题反馈失败',
|
||||
scope: '[admin/worker-platform/feedbacks]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/feedbacks/:feedbackId/resolve',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
resolveAdminWorkerOrderFeedback(
|
||||
String(req.params.feedbackId || ''),
|
||||
req.body || {},
|
||||
req.adminSession?.username || '',
|
||||
),
|
||||
{
|
||||
successMessage: '问题反馈已处理',
|
||||
errorMessage: '处理问题反馈失败',
|
||||
scope: '[admin/worker-platform/feedbacks/:feedbackId/resolve]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/cancel-requests/:requestId/review',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
|
||||
@@ -38,7 +38,9 @@ import {
|
||||
supplementWorkerAcceptedOrderEvidence,
|
||||
submitWorkerOrderAcceptance,
|
||||
getWorkerCancelRequestSummary,
|
||||
getWorkerOrderFeedbackSummary,
|
||||
submitWorkerCancelRequest,
|
||||
submitWorkerOrderFeedback,
|
||||
} from '../services/worker-platform/index.js'
|
||||
import { buildNotFoundPayload, createRouteHandler } from '../utils/http.js'
|
||||
import {
|
||||
@@ -452,6 +454,34 @@ router.get(
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/orders/feedbacks',
|
||||
requireActiveWorker,
|
||||
createRouteHandler((req) => getWorkerOrderFeedbackSummary(getRequiredWorkerSession(req)), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取问题反馈失败',
|
||||
scope: '[worker/orders/feedbacks]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/orders/:workOrderId/feedbacks',
|
||||
requireActiveWorker,
|
||||
createRouteHandler(
|
||||
(req) =>
|
||||
submitWorkerOrderFeedback(
|
||||
String(req.params.workOrderId || ''),
|
||||
req.body || {},
|
||||
getRequiredWorkerSession(req),
|
||||
),
|
||||
{
|
||||
successMessage: '问题反馈已提交,等待客服处理',
|
||||
errorMessage: '提交问题反馈失败',
|
||||
scope: '[worker/orders/:workOrderId/feedbacks]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/orders/:workOrderId/cancel-requests',
|
||||
requireActiveWorker,
|
||||
|
||||
@@ -122,6 +122,32 @@ export async function createWorkerCancelRequestAdminNotification(input: {
|
||||
})
|
||||
}
|
||||
|
||||
export async function createWorkerOrderFeedbackAdminNotification(input: {
|
||||
feedbackId: number
|
||||
workOrderId: number
|
||||
workOrderNo: string
|
||||
productName: string
|
||||
workerName: string
|
||||
content: string
|
||||
}) {
|
||||
return createConfiguredWorkerPlatformNotification('feedback_submitted', {
|
||||
notificationType: 'worker_order_feedback_submitted',
|
||||
priority: 'high',
|
||||
entityType: 'worker_order_feedback',
|
||||
entityId: input.feedbackId,
|
||||
dedupeKey: `worker_order_feedback:${input.feedbackId}`,
|
||||
title: '有打手问题反馈待处理',
|
||||
body: [
|
||||
`订单:${input.workOrderNo || input.workOrderId}`,
|
||||
`商品:${input.productName || '-'}`,
|
||||
`打手:${input.workerName || '-'}`,
|
||||
`反馈:${input.content || '-'}`,
|
||||
].join(';'),
|
||||
actionPath: `${ADMIN_WORKER_PLATFORM_PATH}?tab=feedbacks&feedbackId=${input.feedbackId}`,
|
||||
now: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
|
||||
export async function remindWorkerAcceptanceAdminNotification(workOrderId: number) {
|
||||
const now = new Date().toISOString()
|
||||
const eventConfig = getWorkerPlatformNotificationConfig().events.acceptance_reminded
|
||||
|
||||
@@ -51,6 +51,8 @@ import {
|
||||
reviewWorkerFinanceRequest,
|
||||
reopenCancelledWorkOrder,
|
||||
listAdminWorkerCancelRequests as listWorkerCancelRequestsForAdmin,
|
||||
listAdminWorkerOrderFeedbacks as listWorkerOrderFeedbacksForAdmin,
|
||||
resolveWorkerOrderFeedback,
|
||||
reviewWorkerCancelRequest,
|
||||
updateWorkOrder,
|
||||
updateWorkOrderBasic,
|
||||
@@ -111,6 +113,7 @@ import {
|
||||
mapWorkerLevel,
|
||||
mapWorkerUser,
|
||||
mapWorkerCancelRequest,
|
||||
mapWorkerOrderFeedback,
|
||||
normalizeAdminFinanceReviewStatus,
|
||||
normalizeAmountFen,
|
||||
normalizeBoolean,
|
||||
@@ -1366,6 +1369,66 @@ export async function listAdminWorkerCancelRequests(query: JsonObject = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function listAdminWorkerOrderFeedbacks(query: JsonObject = {}) {
|
||||
const status = String(query.status || 'pending').trim()
|
||||
const normalizedStatus = ['pending', 'resolved'].includes(status) ? status : 'pending'
|
||||
return {
|
||||
items: (await listWorkerOrderFeedbacksForAdmin(normalizedStatus)).map(mapWorkerOrderFeedback),
|
||||
}
|
||||
}
|
||||
|
||||
export async function resolveAdminWorkerOrderFeedback(
|
||||
feedbackId: number | string,
|
||||
payload: JsonObject = {},
|
||||
actorName = '',
|
||||
) {
|
||||
const reply = String(payload.reply || '').trim()
|
||||
if (!reply) {
|
||||
throw createHttpError('请填写处理回复', {
|
||||
statusCode: 400,
|
||||
errorCode: 'worker_order_feedback_reply_required',
|
||||
})
|
||||
}
|
||||
const result = await resolveWorkerOrderFeedback({
|
||||
feedbackId: Number(feedbackId),
|
||||
reply,
|
||||
actorName,
|
||||
now: nowIso(),
|
||||
})
|
||||
if (result.failureReason === 'not_found') {
|
||||
throw createHttpError('问题反馈不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'worker_order_feedback_not_found',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'not_pending') {
|
||||
throw createHttpError('该问题反馈已处理,请刷新后重试', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_order_feedback_not_pending',
|
||||
})
|
||||
}
|
||||
if (!result.feedback || !result.order) {
|
||||
throw createHttpError('问题反馈处理失败', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_order_feedback_resolve_failed',
|
||||
})
|
||||
}
|
||||
await resolveAdminNotificationEntity(
|
||||
'worker_order_feedback',
|
||||
Number(result.feedback.id),
|
||||
'resolved',
|
||||
)
|
||||
const workerId = Number(result.feedback.worker_id)
|
||||
publishWorkOrderRealtimeChange({
|
||||
workOrderId: Number(result.order.id),
|
||||
workerIds: workerId > 0 ? [workerId] : [],
|
||||
})
|
||||
return {
|
||||
feedback: mapWorkerOrderFeedback(result.feedback),
|
||||
order: mapWorkOrderAdmin(result.order),
|
||||
}
|
||||
}
|
||||
|
||||
export async function reviewAdminWorkerCancelRequest(
|
||||
requestId: number | string,
|
||||
payload: JsonObject = {},
|
||||
|
||||
@@ -292,6 +292,41 @@ export function mapWorkerCancelRequest(request: {
|
||||
}
|
||||
}
|
||||
|
||||
export function mapWorkerOrderFeedback(feedback: {
|
||||
id: number
|
||||
work_order_id: number
|
||||
work_order_share_id: number | null
|
||||
worker_id: number
|
||||
content: string
|
||||
status: string
|
||||
reply: string
|
||||
handled_by: string
|
||||
created_at: string
|
||||
handled_at: string | null
|
||||
work_order_no?: string
|
||||
product_name?: string
|
||||
worker_username?: string
|
||||
worker_display_name?: string
|
||||
share_quantity?: number | null
|
||||
}) {
|
||||
return {
|
||||
feedbackId: Number(feedback.id),
|
||||
workOrderId: Number(feedback.work_order_id),
|
||||
shareId: feedback.work_order_share_id ? Number(feedback.work_order_share_id) : null,
|
||||
workerId: Number(feedback.worker_id),
|
||||
workOrderNo: String(feedback.work_order_no || ''),
|
||||
productName: String(feedback.product_name || ''),
|
||||
workerName: String(feedback.worker_display_name || feedback.worker_username || ''),
|
||||
shareQuantity: feedback.share_quantity ? Number(feedback.share_quantity) : null,
|
||||
content: String(feedback.content || ''),
|
||||
status: String(feedback.status || ''),
|
||||
reply: String(feedback.reply || ''),
|
||||
handledBy: String(feedback.handled_by || ''),
|
||||
createdAt: feedback.created_at,
|
||||
handledAt: feedback.handled_at,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapWalletLedger(ledger: WorkerWalletLedgerRow | null | undefined) {
|
||||
if (!ledger) return null
|
||||
return {
|
||||
|
||||
+2
@@ -9,6 +9,7 @@ export const WORKER_PLATFORM_NOTIFICATION_EVENT_KEYS = [
|
||||
'acceptance_reminded',
|
||||
'material_required',
|
||||
'cancel_requested',
|
||||
'feedback_submitted',
|
||||
] as const
|
||||
|
||||
export type WorkerPlatformNotificationEventKey =
|
||||
@@ -53,6 +54,7 @@ export function createDefaultWorkerPlatformNotificationConfig(): WorkerPlatformN
|
||||
externalPushEnabled: false,
|
||||
},
|
||||
cancel_requested: createEnabledEventConfig(),
|
||||
feedback_submitted: createEnabledEventConfig(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
acceptWorkOrderShareAndSettle,
|
||||
countWorkerAcceptedOrders,
|
||||
createWorkerCancelRequest,
|
||||
createWorkerOrderFeedback,
|
||||
countWorkerActiveOrders,
|
||||
countWorkerTimeoutEvents,
|
||||
countWorkerWithdrawRequestsOnDay,
|
||||
@@ -44,6 +45,7 @@ import {
|
||||
listWorkerFinanceRequests,
|
||||
listWorkerCompletionLeaderboard,
|
||||
listWorkerCancelRequests,
|
||||
listWorkerOrderFeedbacks,
|
||||
listWorkerSharesByWorker,
|
||||
listWorkerWalletLedgers,
|
||||
listWorkerWorkOrderNotes,
|
||||
@@ -86,6 +88,7 @@ import { refreshUploadedFileUrls } from '../file-storage/file-storage-service.js
|
||||
import {
|
||||
createWorkerAcceptanceAdminNotification,
|
||||
createWorkerCancelRequestAdminNotification,
|
||||
createWorkerOrderFeedbackAdminNotification,
|
||||
createWorkerRechargeAdminNotification,
|
||||
createWorkerWithdrawAdminNotification,
|
||||
getWorkerAcceptanceReminder,
|
||||
@@ -116,6 +119,7 @@ import {
|
||||
mapWorkOrderEvents,
|
||||
mapWorkOrderShare,
|
||||
mapWorkerCancelRequest,
|
||||
mapWorkerOrderFeedback,
|
||||
mapWorkerUser,
|
||||
normalizeAmountFen,
|
||||
assertWithdrawChannelAllowedForAmount,
|
||||
@@ -1752,6 +1756,58 @@ export async function getWorkerCancelRequestSummary(session: WorkerSession) {
|
||||
return { risk, items: requests.map(mapWorkerCancelRequest) }
|
||||
}
|
||||
|
||||
export async function getWorkerOrderFeedbackSummary(session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
return { items: (await listWorkerOrderFeedbacks(session.workerId)).map(mapWorkerOrderFeedback) }
|
||||
}
|
||||
|
||||
export async function submitWorkerOrderFeedback(
|
||||
workOrderId: number | string,
|
||||
payload: JsonObject = {},
|
||||
session: WorkerSession,
|
||||
) {
|
||||
requireActiveWorkerSession(session)
|
||||
const content = String(payload.content || '').trim()
|
||||
if (!content) {
|
||||
throw createHttpError('请填写问题反馈内容', {
|
||||
statusCode: 400,
|
||||
errorCode: 'worker_order_feedback_content_required',
|
||||
})
|
||||
}
|
||||
if (content.length > 500) {
|
||||
throw createHttpError('问题反馈不能超过 500 个字符', {
|
||||
statusCode: 400,
|
||||
errorCode: 'worker_order_feedback_content_too_long',
|
||||
})
|
||||
}
|
||||
const result = await createWorkerOrderFeedback({
|
||||
workOrderId: Number(workOrderId),
|
||||
workerId: Number(session.workerId),
|
||||
content,
|
||||
now: nowIso(),
|
||||
})
|
||||
if (!result.feedback) {
|
||||
throw createHttpError('只能反馈自己的订单问题', {
|
||||
statusCode: 403,
|
||||
errorCode: 'worker_order_feedback_owner_required',
|
||||
})
|
||||
}
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
await createWorkerOrderFeedbackAdminNotification({
|
||||
feedbackId: Number(result.feedback.id),
|
||||
workOrderId: Number(result.feedback.work_order_id),
|
||||
workOrderNo: result.feedback.work_order_no || '',
|
||||
productName: result.feedback.product_name || '',
|
||||
workerName: worker.display_name || worker.username,
|
||||
content,
|
||||
})
|
||||
publishWorkOrderRealtimeChange({
|
||||
workOrderId: Number(result.feedback.work_order_id),
|
||||
workerIds: [Number(session.workerId)],
|
||||
})
|
||||
return { feedback: mapWorkerOrderFeedback(result.feedback) }
|
||||
}
|
||||
|
||||
export async function submitWorkerCancelRequest(
|
||||
workOrderId: number | string,
|
||||
payload: JsonObject = {},
|
||||
|
||||
Reference in New Issue
Block a user