拆分打手个人订单服务
This commit is contained in:
@@ -14,6 +14,7 @@ export * from './worker-order-timeout-service.js'
|
||||
export * from './worker-order-realtime-service.js'
|
||||
export * from './worker-collect-service.js'
|
||||
export * from './worker-acceptance-service.js'
|
||||
export * from './worker-my-order-service.js'
|
||||
export * from './worker-platform-defaults.js'
|
||||
export * from './work-order-timeout-policy.js'
|
||||
export * from './after-sales-service.js'
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
import { runtimeConfig } from '../../config/runtime.js'
|
||||
import {
|
||||
createWorkerCancelRequest,
|
||||
createWorkerOrderFeedback,
|
||||
getWorkOrderShare,
|
||||
getWorkerCancelRequestRisk,
|
||||
getWorkerOrderOverview,
|
||||
listWorkerCancelRequests,
|
||||
listWorkerOrderFeedbacks,
|
||||
listWorkerSharesByWorker,
|
||||
listWorkerWorkOrderNotes,
|
||||
listWorkerWorkOrderViews,
|
||||
listWorkOrderEventsByOrderId,
|
||||
listWorkOrders,
|
||||
markWorkerWorkOrderViewed,
|
||||
upsertWorkerWorkOrderNote,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { normalizePage, normalizePageSize } from '../admin/admin-query-utils.js'
|
||||
import { getWorkerAnnouncementConfig } from './worker-announcement-config-service.js'
|
||||
import {
|
||||
createWorkerCancelRequestAdminNotification,
|
||||
createWorkerOrderFeedbackAdminNotification,
|
||||
} from '../admin/admin-notification-service.js'
|
||||
import { publishWorkOrderRealtimeChange } from '../realtime/realtime-event-service.js'
|
||||
import {
|
||||
mapWorkOrderEvents,
|
||||
mapWorkOrderForWorker,
|
||||
mapWorkerCancelRequest,
|
||||
mapWorkerOrderFeedback,
|
||||
normalizeBoolean,
|
||||
normalizeStatuses,
|
||||
resolveWorkerPermissions,
|
||||
} from './mappers.js'
|
||||
import {
|
||||
getRequiredWorkOrder,
|
||||
getRequiredWorker,
|
||||
requireActiveWorkerSession,
|
||||
type WorkerSession,
|
||||
} from './worker-session-context-service.js'
|
||||
import { settleOverdueWorkOrders } from './worker-order-timeout-service.js'
|
||||
|
||||
export async function listWorkerMyOrders(query: JsonObject = {}, session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
const page = normalizePage(query.page)
|
||||
const pageSize = normalizePageSize(query.pageSize)
|
||||
const vipEvidencePending = normalizeBoolean(query.vipEvidencePending, false)
|
||||
const { items, total } = await listWorkOrders({
|
||||
page,
|
||||
pageSize,
|
||||
statuses: normalizeStatuses(query.status),
|
||||
keyword: String(query.keyword || '').trim(),
|
||||
workerSharingId: worker.id,
|
||||
vipEvidencePending,
|
||||
sort: 'worker_claimed_at_desc',
|
||||
})
|
||||
const permissions = resolveWorkerPermissions(worker)
|
||||
const myShares = await listWorkerSharesByWorker(worker.id)
|
||||
const myShareByOrderId = new Map(myShares.map((share) => [Number(share.work_order_id), share]))
|
||||
const workOrderIds = items.map((item) => Number(item.id))
|
||||
const [notes, views] = await Promise.all([
|
||||
listWorkerWorkOrderNotes(worker.id, workOrderIds),
|
||||
listWorkerWorkOrderViews(worker.id, workOrderIds),
|
||||
])
|
||||
const noteByOrderId = new Map(
|
||||
notes.map((item) => [Number(item.work_order_id), String(item.note || '')]),
|
||||
)
|
||||
const viewedAtByOrderId = new Map(
|
||||
views.map((item) => [Number(item.work_order_id), String(item.viewed_at || '')]),
|
||||
)
|
||||
return {
|
||||
items: items.map((item) =>
|
||||
mapWorkOrderForWorker(
|
||||
item,
|
||||
permissions,
|
||||
myShareByOrderId.get(Number(item.id)),
|
||||
noteByOrderId.get(Number(item.id)) || '',
|
||||
viewedAtByOrderId.get(Number(item.id)) || null,
|
||||
),
|
||||
),
|
||||
pagination: { page, pageSize, total },
|
||||
}
|
||||
}
|
||||
|
||||
/** 打手订单标签概览:订单数与预计或已结算金额。 */
|
||||
export async function getWorkerMyOrderOverview(session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
await settleOverdueWorkOrders({ workerId: worker.id, limit: 50 })
|
||||
const rows = await getWorkerOrderOverview(worker.id)
|
||||
const items = rows.map((row) => ({
|
||||
status: row.status,
|
||||
orderCount: row.orderCount,
|
||||
settlementAmount: row.settlementAmount,
|
||||
}))
|
||||
const vipEvidencePending = rows.reduce(
|
||||
(summary, row) => ({
|
||||
orderCount: summary.orderCount + row.vipEvidencePendingCount,
|
||||
settlementAmount: summary.settlementAmount + row.vipEvidencePendingSettlementAmount,
|
||||
}),
|
||||
{ orderCount: 0, settlementAmount: 0 },
|
||||
)
|
||||
const all = items.reduce(
|
||||
(summary, item) => ({
|
||||
orderCount: summary.orderCount + item.orderCount,
|
||||
settlementAmount: summary.settlementAmount + item.settlementAmount,
|
||||
}),
|
||||
{ orderCount: 0, settlementAmount: 0 },
|
||||
)
|
||||
return {
|
||||
items: [
|
||||
...items,
|
||||
{ status: 'vip_evidence_pending', ...vipEvidencePending },
|
||||
{ status: 'all', ...all },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorkerMyOrderEvents(workOrderId: number | string, session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
if (runtimeConfig.worker.orderTimelineVisible !== true) {
|
||||
throw createHttpError('流转记录暂未对打手开放', {
|
||||
statusCode: 403,
|
||||
errorCode: 'work_order_events_worker_disabled',
|
||||
})
|
||||
}
|
||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||
const workerId = Number(session.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_events_owner_required',
|
||||
})
|
||||
}
|
||||
const events = await listWorkOrderEventsByOrderId(workOrderId)
|
||||
return { items: await mapWorkOrderEvents(events) }
|
||||
}
|
||||
|
||||
export async function getWorkerCancelRequestSummary(session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
const [risk, requests] = await Promise.all([
|
||||
getWorkerCancelRequestRisk(session.workerId, nowIso()),
|
||||
listWorkerCancelRequests(session.workerId),
|
||||
])
|
||||
return { risk, items: requests.map(mapWorkerCancelRequest) }
|
||||
}
|
||||
|
||||
export async function getWorkerAnnouncement(session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
return getWorkerAnnouncementConfig()
|
||||
}
|
||||
|
||||
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 = {},
|
||||
session: WorkerSession,
|
||||
) {
|
||||
requireActiveWorkerSession(session)
|
||||
const reason = String(payload.reason || '').trim()
|
||||
if (!reason) {
|
||||
throw createHttpError('请填写申请取消原因', {
|
||||
statusCode: 400,
|
||||
errorCode: 'worker_cancel_request_reason_required',
|
||||
})
|
||||
}
|
||||
if (reason.length > 300) {
|
||||
throw createHttpError('申请取消原因不能超过 300 个字符', {
|
||||
statusCode: 400,
|
||||
errorCode: 'worker_cancel_request_reason_too_long',
|
||||
})
|
||||
}
|
||||
const now = nowIso()
|
||||
const result = await createWorkerCancelRequest({
|
||||
workOrderId: Number(workOrderId),
|
||||
workerId: Number(session.workerId),
|
||||
reason,
|
||||
now,
|
||||
})
|
||||
if (result.failureReason === 'daily_limit_reached') {
|
||||
throw createHttpError('今日最多申请取消 5 次,请明日再试', {
|
||||
statusCode: 429,
|
||||
errorCode: 'worker_cancel_request_daily_limit',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'request_pending') {
|
||||
throw createHttpError('该订单已有待审核的取消申请', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_cancel_request_pending',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'share_not_cancellable') {
|
||||
throw createHttpError('当前拼单份额不能申请取消', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_cancel_request_share_status_invalid',
|
||||
})
|
||||
}
|
||||
if (!result.request) {
|
||||
throw createHttpError('当前订单不能申请取消', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_cancel_request_status_invalid',
|
||||
})
|
||||
}
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
await createWorkerCancelRequestAdminNotification({
|
||||
requestId: Number(result.request.id),
|
||||
workOrderId: Number(result.request.work_order_id),
|
||||
platformOrderId: result.request.platform_order_id || '',
|
||||
productName: result.request.product_name || '',
|
||||
workerName: worker.display_name || worker.username,
|
||||
reason,
|
||||
})
|
||||
const risk = await getWorkerCancelRequestRisk(session.workerId, now)
|
||||
publishWorkOrderRealtimeChange({
|
||||
workOrderId: Number(result.request.work_order_id),
|
||||
workerIds: [Number(session.workerId)],
|
||||
})
|
||||
return { request: mapWorkerCancelRequest(result.request), risk }
|
||||
}
|
||||
|
||||
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 markWorkerOrderRead(workOrderId: number | string, session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||
const workerId = Number(session.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_read_owner_required',
|
||||
})
|
||||
}
|
||||
|
||||
const viewedAt = await markWorkerWorkOrderViewed({
|
||||
workOrderId: Number(workOrder.id),
|
||||
workerId,
|
||||
now: nowIso(),
|
||||
})
|
||||
publishWorkOrderRealtimeChange({
|
||||
workOrderId: Number(workOrder.id),
|
||||
workerIds: [workerId],
|
||||
})
|
||||
return { viewedAt, isUnread: false }
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
import { runtimeConfig } from '../../config/runtime.js'
|
||||
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
|
||||
import {
|
||||
createWorkerCancelRequest,
|
||||
createWorkerOrderFeedback,
|
||||
countWorkerActiveOrders,
|
||||
consumeWorkerSmsCode,
|
||||
createWorkOrderEvent,
|
||||
@@ -13,8 +10,6 @@ import {
|
||||
getWorkerUserByUsername,
|
||||
getWorkerUserByDisplayName,
|
||||
getWorkOrderById,
|
||||
getWorkOrderShare,
|
||||
getWorkerOrderOverview,
|
||||
grabWorkOrder,
|
||||
createWorkerSessionRecord,
|
||||
getWorkerSessionBySessionId,
|
||||
@@ -22,24 +17,12 @@ import {
|
||||
revokeWorkerSession,
|
||||
touchWorkerSession,
|
||||
joinWorkOrderShare,
|
||||
listWorkerCancelRequests,
|
||||
listWorkerOrderFeedbacks,
|
||||
listWorkerSharesByWorker,
|
||||
listWorkerWorkOrderNotes,
|
||||
listWorkerWorkOrderViews,
|
||||
listWorkOrders,
|
||||
listWorkOrderEventsByOrderId,
|
||||
getWorkerCancelRequestRisk,
|
||||
upsertWorkerWorkOrderNote,
|
||||
markWorkerWorkOrderViewed,
|
||||
type WorkOrderRow,
|
||||
type WorkerSessionRow,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { normalizePage, normalizePageSize } from '../admin/admin-query-utils.js'
|
||||
import { getWorkerAnnouncementConfig } from './worker-announcement-config-service.js'
|
||||
import { getWorkerHallConfig } from './worker-hall-config-service.js'
|
||||
import {
|
||||
getRequiredWorkOrder,
|
||||
@@ -47,10 +30,6 @@ import {
|
||||
requireActiveWorkerSession,
|
||||
type WorkerSession,
|
||||
} from './worker-session-context-service.js'
|
||||
import {
|
||||
createWorkerCancelRequestAdminNotification,
|
||||
createWorkerOrderFeedbackAdminNotification,
|
||||
} from '../admin/admin-notification-service.js'
|
||||
import {
|
||||
publishWorkerWalletRealtimeChange,
|
||||
publishWorkOrderRealtimeChange,
|
||||
@@ -58,14 +37,8 @@ import {
|
||||
|
||||
import {
|
||||
mapWorkOrderForWorker,
|
||||
mapWorkOrderEvents,
|
||||
mapWorkOrderShare,
|
||||
mapWorkerCancelRequest,
|
||||
mapWorkerOrderFeedback,
|
||||
normalizeBoolean,
|
||||
normalizeOptionalId,
|
||||
normalizePositiveInteger,
|
||||
normalizeStatuses,
|
||||
resolveFreezeDepositAmount,
|
||||
resolveWorkerPermissions,
|
||||
throwGrabWorkOrderFailure,
|
||||
@@ -99,7 +72,6 @@ import {
|
||||
} from './worker-session-auth-service.js'
|
||||
import { normalizeWorkOrderTimeoutPolicy } from './work-order-timeout-policy.js'
|
||||
import { publishWorkOrderParticipantChange } from './worker-order-realtime-service.js'
|
||||
import { settleOverdueWorkOrders } from './worker-order-timeout-service.js'
|
||||
|
||||
export {
|
||||
getRequiredWorkOrder,
|
||||
@@ -255,310 +227,18 @@ export { settleDueDepositUnfreezes } from './worker-profile-service.js'
|
||||
|
||||
export { settleOverdueWorkOrders } from './worker-order-timeout-service.js'
|
||||
|
||||
export async function listWorkerMyOrders(query: JsonObject = {}, session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
const page = normalizePage(query.page)
|
||||
const pageSize = normalizePageSize(query.pageSize)
|
||||
const vipEvidencePending = normalizeBoolean(query.vipEvidencePending, false)
|
||||
const { items, total } = await listWorkOrders({
|
||||
page,
|
||||
pageSize,
|
||||
statuses: normalizeStatuses(query.status),
|
||||
keyword: String(query.keyword || '').trim(),
|
||||
workerSharingId: worker.id,
|
||||
vipEvidencePending,
|
||||
sort: 'worker_claimed_at_desc',
|
||||
})
|
||||
const permissions = resolveWorkerPermissions(worker)
|
||||
const myShares = await listWorkerSharesByWorker(worker.id)
|
||||
const myShareByOrderId = new Map(myShares.map((share) => [Number(share.work_order_id), share]))
|
||||
const workOrderIds = items.map((item) => Number(item.id))
|
||||
const [notes, views] = await Promise.all([
|
||||
listWorkerWorkOrderNotes(worker.id, workOrderIds),
|
||||
listWorkerWorkOrderViews(worker.id, workOrderIds),
|
||||
])
|
||||
const noteByOrderId = new Map(
|
||||
notes.map((item) => [Number(item.work_order_id), String(item.note || '')]),
|
||||
)
|
||||
const viewedAtByOrderId = new Map(
|
||||
views.map((item) => [Number(item.work_order_id), String(item.viewed_at || '')]),
|
||||
)
|
||||
return {
|
||||
items: items.map((item) =>
|
||||
mapWorkOrderForWorker(
|
||||
item,
|
||||
permissions,
|
||||
myShareByOrderId.get(Number(item.id)),
|
||||
noteByOrderId.get(Number(item.id)) || '',
|
||||
viewedAtByOrderId.get(Number(item.id)) || null,
|
||||
),
|
||||
),
|
||||
pagination: { page, pageSize, total },
|
||||
}
|
||||
}
|
||||
|
||||
/** 打手订单标签概览:订单数与预计/已结算金额。 */
|
||||
export async function getWorkerMyOrderOverview(session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
await settleOverdueWorkOrders({ workerId: worker.id, limit: 50 })
|
||||
const rows = await getWorkerOrderOverview(worker.id)
|
||||
const items = rows.map((row) => ({
|
||||
status: row.status,
|
||||
orderCount: row.orderCount,
|
||||
settlementAmount: row.settlementAmount,
|
||||
}))
|
||||
const vipEvidencePending = rows.reduce(
|
||||
(summary, row) => ({
|
||||
orderCount: summary.orderCount + row.vipEvidencePendingCount,
|
||||
settlementAmount: summary.settlementAmount + row.vipEvidencePendingSettlementAmount,
|
||||
}),
|
||||
{ orderCount: 0, settlementAmount: 0 },
|
||||
)
|
||||
const all = items.reduce(
|
||||
(summary, item) => ({
|
||||
orderCount: summary.orderCount + item.orderCount,
|
||||
settlementAmount: summary.settlementAmount + item.settlementAmount,
|
||||
}),
|
||||
{ orderCount: 0, settlementAmount: 0 },
|
||||
)
|
||||
return {
|
||||
items: [
|
||||
...items,
|
||||
{ status: 'vip_evidence_pending', ...vipEvidencePending },
|
||||
{ status: 'all', ...all },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorkerMyOrderEvents(workOrderId: number | string, session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
if (runtimeConfig.worker.orderTimelineVisible !== true) {
|
||||
throw createHttpError('流转记录暂未对打手开放', {
|
||||
statusCode: 403,
|
||||
errorCode: 'work_order_events_worker_disabled',
|
||||
})
|
||||
}
|
||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||
const workerId = Number(session.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_events_owner_required',
|
||||
})
|
||||
}
|
||||
const events = await listWorkOrderEventsByOrderId(workOrderId)
|
||||
return { items: await mapWorkOrderEvents(events) }
|
||||
}
|
||||
|
||||
export async function getWorkerCancelRequestSummary(session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
const [risk, requests] = await Promise.all([
|
||||
getWorkerCancelRequestRisk(session.workerId, nowIso()),
|
||||
listWorkerCancelRequests(session.workerId),
|
||||
])
|
||||
return { risk, items: requests.map(mapWorkerCancelRequest) }
|
||||
}
|
||||
|
||||
export async function getWorkerAnnouncement(session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
return getWorkerAnnouncementConfig()
|
||||
}
|
||||
|
||||
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 = {},
|
||||
session: WorkerSession,
|
||||
) {
|
||||
requireActiveWorkerSession(session)
|
||||
const reason = String(payload.reason || '').trim()
|
||||
if (!reason) {
|
||||
throw createHttpError('请填写申请取消原因', {
|
||||
statusCode: 400,
|
||||
errorCode: 'worker_cancel_request_reason_required',
|
||||
})
|
||||
}
|
||||
if (reason.length > 300) {
|
||||
throw createHttpError('申请取消原因不能超过 300 个字符', {
|
||||
statusCode: 400,
|
||||
errorCode: 'worker_cancel_request_reason_too_long',
|
||||
})
|
||||
}
|
||||
const now = nowIso()
|
||||
const result = await createWorkerCancelRequest({
|
||||
workOrderId: Number(workOrderId),
|
||||
workerId: Number(session.workerId),
|
||||
reason,
|
||||
now,
|
||||
})
|
||||
if (result.failureReason === 'daily_limit_reached') {
|
||||
throw createHttpError('今日最多申请取消 5 次,请明日再试', {
|
||||
statusCode: 429,
|
||||
errorCode: 'worker_cancel_request_daily_limit',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'request_pending') {
|
||||
throw createHttpError('该订单已有待审核的取消申请', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_cancel_request_pending',
|
||||
})
|
||||
}
|
||||
if (result.failureReason === 'share_not_cancellable') {
|
||||
throw createHttpError('当前拼单份额不能申请取消', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_cancel_request_share_status_invalid',
|
||||
})
|
||||
}
|
||||
if (!result.request) {
|
||||
throw createHttpError('当前订单不能申请取消', {
|
||||
statusCode: 409,
|
||||
errorCode: 'worker_cancel_request_status_invalid',
|
||||
})
|
||||
}
|
||||
const worker = await getRequiredWorker(session.workerId)
|
||||
await createWorkerCancelRequestAdminNotification({
|
||||
requestId: Number(result.request.id),
|
||||
workOrderId: Number(result.request.work_order_id),
|
||||
platformOrderId: result.request.platform_order_id || '',
|
||||
productName: result.request.product_name || '',
|
||||
workerName: worker.display_name || worker.username,
|
||||
reason,
|
||||
})
|
||||
const risk = await getWorkerCancelRequestRisk(session.workerId, now)
|
||||
publishWorkOrderRealtimeChange({
|
||||
workOrderId: Number(result.request.work_order_id),
|
||||
workerIds: [Number(session.workerId)],
|
||||
})
|
||||
return { request: mapWorkerCancelRequest(result.request), risk }
|
||||
}
|
||||
|
||||
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 markWorkerOrderRead(workOrderId: number | string, session: WorkerSession) {
|
||||
requireActiveWorkerSession(session)
|
||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||
const workerId = Number(session.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_read_owner_required',
|
||||
})
|
||||
}
|
||||
|
||||
const viewedAt = await markWorkerWorkOrderViewed({
|
||||
workOrderId: Number(workOrder.id),
|
||||
workerId,
|
||||
now: nowIso(),
|
||||
})
|
||||
publishWorkOrderRealtimeChange({
|
||||
workOrderId: Number(workOrder.id),
|
||||
workerIds: [workerId],
|
||||
})
|
||||
return { viewedAt, isUnread: false }
|
||||
}
|
||||
export {
|
||||
getWorkerAnnouncement,
|
||||
getWorkerCancelRequestSummary,
|
||||
getWorkerMyOrderEvents,
|
||||
getWorkerMyOrderOverview,
|
||||
getWorkerOrderFeedbackSummary,
|
||||
listWorkerMyOrders,
|
||||
markWorkerOrderRead,
|
||||
saveWorkerOrderNote,
|
||||
submitWorkerCancelRequest,
|
||||
submitWorkerOrderFeedback,
|
||||
} from './worker-my-order-service.js'
|
||||
|
||||
export async function joinWorkerSharingOrder(
|
||||
workOrderId: number | string,
|
||||
|
||||
Reference in New Issue
Block a user