feat(consignment): 寄售匹配成功后向卖家端与客服后台透出买家加好友资料

This commit is contained in:
yml2213
2026-09-17 20:46:45 +08:00
parent eff45bb9a8
commit c5d1e2c375
9 changed files with 327 additions and 2 deletions
@@ -41,6 +41,8 @@ export type ConsignmentInventoryRow = {
gift_evidence_json?: string | Record<string, unknown> | null
gift_work_order_no?: string | null
gift_work_order_status?: string | null
bound_work_order_material_json?: string | Record<string, unknown> | null
bound_work_order_requirement_json?: string | Record<string, unknown> | null
worker_id?: string | number | null
worker_name?: string
worker_phone?: string
@@ -194,7 +196,9 @@ export async function listConsignmentInventories(filters: {
cs.screenshots_json AS submission_screenshots_json,
wo.acceptance_json AS gift_evidence_json,
wo.work_order_no AS gift_work_order_no,
wo.status AS gift_work_order_status
wo.status AS gift_work_order_status,
wo.material_json AS bound_work_order_material_json,
wo.requirement_json AS bound_work_order_requirement_json
FROM consignment_inventories ci
LEFT JOIN work_categories wc ON wc.id = ci.category_id
LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id
@@ -309,7 +313,9 @@ export async function getConsignmentInventoryById(
cs.screenshots_json AS submission_screenshots_json,
wo.acceptance_json AS gift_evidence_json,
wo.work_order_no AS gift_work_order_no,
wo.status AS gift_work_order_status
wo.status AS gift_work_order_status,
wo.material_json AS bound_work_order_material_json,
wo.requirement_json AS bound_work_order_requirement_json
FROM consignment_inventories ci
LEFT JOIN work_categories wc ON wc.id = ci.category_id
LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id
@@ -29,6 +29,7 @@ import {
releaseConsignmentPublishDepositBySubmissionWithClient,
} from '../../repositories/worker-platform/consignment-deposit-repo.js'
import { publishWorkerWalletRealtimeChange } from '../realtime/realtime-event-service.js'
import { extractConsignmentBuyerInfo } from './work-order-material-mappers.js'
function throwConsignmentDepositHttpError(error: unknown, restock: boolean): never {
const code = getConsignmentDepositErrorCode(error)
@@ -192,6 +193,10 @@ export function mapConsignmentInventory(row: ConsignmentInventoryRow) {
/** 唯一锚点:卖家端看到的「寄卖单号」就是它,客服端也用同一个号 */
sourceSubmissionNo: row.source_submission_no || '',
...mapConsignmentEvidence(row),
buyerInfo: extractConsignmentBuyerInfo({
materialJson: row.bound_work_order_material_json,
requirementJson: row.bound_work_order_requirement_json,
}),
workerId: row.worker_id ? Number(row.worker_id) : null,
workerName: row.worker_name || '',
workerPhone: row.worker_phone || '',
@@ -23,6 +23,7 @@ import { getConsignmentPublishDepositFen as getConfiguredConsignmentPublishDepos
import { confirmAdminWorkOrderFriendAdded } from './admin-work-order-gift-service.js'
import { submitWorkerOrderAcceptance } from './worker-acceptance-service.js'
import type { WorkerSession } from './worker-session-context-service.js'
import { extractConsignmentBuyerInfoFromWorkOrder } from './work-order-material-mappers.js'
export function getConsignmentPublishDepositFen() {
return getConfiguredConsignmentPublishDepositFen()
@@ -276,6 +277,9 @@ export async function listWorkerConsignments(workerId: number) {
const items = [
...submissionsRes.items.map((sub) => {
const inv = inventoryBySubmission.get(Number(sub.id))
const boundWo = inv?.bound_work_order_id
? workOrderById.get(Number(inv.bound_work_order_id))
: undefined
const stage = resolveStage(String(sub.status || ''), inv)
return {
/** 唯一锚点:提报单号贯穿发布→审核→在库→匹配→冷却→赠送→结算 */
@@ -305,10 +309,14 @@ export async function listWorkerConsignments(workerId: number) {
friendAddedAt: inv?.bound_work_order_id
? (workOrderById.get(Number(inv.bound_work_order_id))?.friend_added_at ?? null)
: null,
buyerInfo: extractConsignmentBuyerInfoFromWorkOrder(boundWo),
}
}),
...orphanInventories.map((inv) => {
// 手动入库已停用:没有提报单的货不该再出现,这里只做兜底展示(不显示内部仓库号、不给操作能力)
const boundWo = inv.bound_work_order_id
? workOrderById.get(Number(inv.bound_work_order_id))
: undefined
const stage = resolveStage('approved', inv)
return {
consignmentNo: '客服手工入库',
@@ -336,6 +344,7 @@ export async function listWorkerConsignments(workerId: number) {
friendAddedAt: inv.bound_work_order_id
? (workOrderById.get(Number(inv.bound_work_order_id))?.friend_added_at ?? null)
: null,
buyerInfo: extractConsignmentBuyerInfoFromWorkOrder(boundWo),
}
}),
].sort((a, b) => Date.parse(String(b.createdAt || '')) - Date.parse(String(a.createdAt || '')))
@@ -9,6 +9,87 @@ import {
normalizeSubmittedFields,
normalizeUploadedFiles,
} from './worker-input-normalizers.js'
import { normalizeRequirementFields } from './work-order-requirement-fields.js'
export interface ConsignmentBuyerInfo {
gameNickname: string
gameId: string
serverZone?: string
system?: string
fieldItems: Array<{ key: string; label: string; value: string }>
screenshots: string[]
}
export function extractConsignmentBuyerInfo(input: {
materialJson: unknown
requirementJson?: unknown
}): ConsignmentBuyerInfo | null {
const material = safeParseJson(input.materialJson)
const collect = safeParseJson(material.collect)
const fields = normalizeSubmittedFields(collect.fields || material.fields)
const requirementFields = normalizeRequirementFields(safeParseJson(input.requirementJson).fields)
const gameNickname = String(
fields.gameNickname || fields.roleName || fields.nickname || fields.game_nickname || '',
).trim()
const gameId = String(
fields.gameId || fields.characterId || fields.uid || fields.game_id || '',
).trim()
const serverZone = String(fields.serverZone || fields.zone || fields.server_zone || '').trim()
const system = String(fields.system || fields.os || '').trim()
const fieldItems: Array<{ key: string; label: string; value: string }> = []
const usedKeys = new Set<string>()
for (const req of requirementFields) {
usedKeys.add(req.key)
const val = String(fields[req.key] || '').trim()
if (val) {
fieldItems.push({ key: req.key, label: req.label, value: val })
}
}
for (const [k, v] of Object.entries(fields)) {
if (!usedKeys.has(k) && String(v || '').trim()) {
fieldItems.push({ key: k, label: k, value: String(v).trim() })
}
}
const rawScreenshots = Array.isArray(collect.screenshots) ? collect.screenshots : []
const screenshots = rawScreenshots
.map((item) => {
if (typeof item === 'string' && item.trim()) {
return refreshStoredFileUrl(item.trim())
}
if (item && typeof item === 'object') {
const norm = normalizeUploadedFiles([item])[0]
return norm ? refreshUploadedFileUrls(norm)?.url : (item as { url?: string }).url || null
}
return null
})
.filter((url): url is string => typeof url === 'string' && url.length > 0)
if (!gameNickname && !gameId && fieldItems.length === 0 && screenshots.length === 0) {
return null
}
return {
gameNickname,
gameId,
serverZone,
system,
fieldItems,
screenshots,
}
}
export function extractConsignmentBuyerInfoFromWorkOrder(
workOrder: WorkOrderRow | undefined | null,
): ConsignmentBuyerInfo | null {
if (!workOrder) return null
return extractConsignmentBuyerInfo({
materialJson: workOrder.material_json,
requirementJson: workOrder.requirement_json,
})
}
export function mapAcceptanceForResponse(value: unknown) {
const acceptance = safeParseJson(value)
@@ -29,3 +110,4 @@ export function resolveCollectSubmittedFieldValues(workOrder: WorkOrderRow) {
: {}
return normalizeSubmittedFields(collect.fields)
}