新增打手问题反馈

This commit is contained in:
yml2213
2026-08-20 19:56:10 +08:00
parent e504b3a207
commit 20b1066ba3
18 changed files with 788 additions and 0 deletions
@@ -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 {
@@ -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 = {},