feat:寄卖增加对接客服,db脚本迁移修复
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
-- 115_work_order_share_cancel_reason_comment.sql
|
||||
-- 111_work_order_share_cancel_reason_comment.sql
|
||||
-- 「取消部分」把被撤销的份额标记为 admin_partial,校准 cancelled_reason 的枚举注释。
|
||||
-- 纯注释变更,无结构/数据改动;重复执行无副作用。
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
-- 112_consignment_contact_supporter.sql
|
||||
-- 原临时编号 116;108–115 已收敛为 108–111,本补丁顺延为 112。
|
||||
-- 寄卖「对接客服」独立名单:发布寄卖时必选,后台按人统计提成与快速联系。
|
||||
-- 该名单不复用 admin_users,避免后台账号变动影响历史寄卖归属。
|
||||
-- 全部语句幂等,可重复执行。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS consignment_contact_supporters (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
phone TEXT NOT NULL DEFAULT '',
|
||||
wechat TEXT NOT NULL DEFAULT '',
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 打手端下拉只取启用项,按 sort_order、id 排序,正好走这条索引
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_contact_supporters_order
|
||||
ON consignment_contact_supporters (enabled, sort_order, id);
|
||||
|
||||
ALTER TABLE consignment_submissions
|
||||
ADD COLUMN IF NOT EXISTS contact_supporter_id BIGINT
|
||||
REFERENCES consignment_contact_supporters(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE consignment_inventories
|
||||
ADD COLUMN IF NOT EXISTS contact_supporter_id BIGINT
|
||||
REFERENCES consignment_contact_supporters(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_submissions_contact_supporter
|
||||
ON consignment_submissions (contact_supporter_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_contact_supporter
|
||||
ON consignment_inventories (contact_supporter_id);
|
||||
|
||||
-- 首次部署给一个可用默认项:打手端「必填 + 默认选第一个」不会因名单为空而无法发布。
|
||||
-- 后台可直接改名/补充联系方式,不会重复插入。
|
||||
INSERT INTO consignment_contact_supporters (name, note, sort_order)
|
||||
SELECT '默认客服', '系统初始化,请在「配置 - 对接客服」中修改', 0
|
||||
WHERE NOT EXISTS (SELECT 1 FROM consignment_contact_supporters);
|
||||
@@ -0,0 +1,190 @@
|
||||
import { query } from '../../db/client.js'
|
||||
|
||||
/**
|
||||
* 寄卖「对接客服」名单。
|
||||
*
|
||||
* 独立于后台账号表:客服离职或后台账号改角色,都不应影响历史寄卖归属;
|
||||
* 提报单 / 现货只保存名单 id,展示时 join 出当时选择的客服资料。
|
||||
*/
|
||||
export type ConsignmentContactRow = {
|
||||
id: string | number
|
||||
name: string
|
||||
phone: string
|
||||
wechat: string
|
||||
note: string
|
||||
enabled: boolean
|
||||
sort_order: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export async function listConsignmentContacts(filters: {
|
||||
keyword?: string | undefined
|
||||
enabled?: boolean | undefined
|
||||
}): Promise<ConsignmentContactRow[]> {
|
||||
const conditions: string[] = []
|
||||
const values: unknown[] = []
|
||||
|
||||
if (filters.enabled !== undefined) {
|
||||
values.push(filters.enabled)
|
||||
conditions.push(`enabled = $${values.length}`)
|
||||
}
|
||||
if (filters.keyword) {
|
||||
values.push(`%${filters.keyword.trim()}%`)
|
||||
conditions.push(
|
||||
`(name ILIKE $${values.length} OR phone ILIKE $${values.length} OR wechat ILIKE $${values.length} OR note ILIKE $${values.length})`,
|
||||
)
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
||||
const res = await query<ConsignmentContactRow>(
|
||||
`SELECT * FROM consignment_contact_supporters ${whereClause} ORDER BY sort_order ASC, id ASC`,
|
||||
values,
|
||||
)
|
||||
return res.rows
|
||||
}
|
||||
|
||||
/** 打手端发布下拉:只返回启用中的客服。 */
|
||||
export async function listEnabledConsignmentContacts(): Promise<ConsignmentContactRow[]> {
|
||||
return listConsignmentContacts({ enabled: true })
|
||||
}
|
||||
|
||||
export async function getConsignmentContactById(
|
||||
id: number | string,
|
||||
): Promise<ConsignmentContactRow | null> {
|
||||
const res = await query<ConsignmentContactRow>(
|
||||
'SELECT * FROM consignment_contact_supporters WHERE id = $1 LIMIT 1',
|
||||
[id],
|
||||
)
|
||||
return res.rows[0] || null
|
||||
}
|
||||
|
||||
/** 重名检查:名单用于归属统计,同名会让运营无法判断到底是谁。 */
|
||||
export async function findConsignmentContactByName(
|
||||
name: string,
|
||||
excludeId?: number | string,
|
||||
): Promise<ConsignmentContactRow | null> {
|
||||
const res = await query<ConsignmentContactRow>(
|
||||
`SELECT * FROM consignment_contact_supporters
|
||||
WHERE name = $1 ${excludeId !== undefined ? 'AND id <> $2' : ''}
|
||||
ORDER BY id ASC
|
||||
LIMIT 1`,
|
||||
excludeId !== undefined ? [name, excludeId] : [name],
|
||||
)
|
||||
return res.rows[0] || null
|
||||
}
|
||||
|
||||
export async function createConsignmentContact(input: {
|
||||
name: string
|
||||
phone?: string | undefined
|
||||
wechat?: string | undefined
|
||||
note?: string | undefined
|
||||
enabled?: boolean | undefined
|
||||
sortOrder?: number | undefined
|
||||
now: string
|
||||
}): Promise<ConsignmentContactRow> {
|
||||
const res = await query<ConsignmentContactRow>(
|
||||
`INSERT INTO consignment_contact_supporters (
|
||||
name, phone, wechat, note, enabled, sort_order, created_at, updated_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $7)
|
||||
RETURNING *`,
|
||||
[
|
||||
input.name,
|
||||
input.phone || '',
|
||||
input.wechat || '',
|
||||
input.note || '',
|
||||
input.enabled !== false,
|
||||
input.sortOrder || 0,
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
return res.rows[0]!
|
||||
}
|
||||
|
||||
export async function updateConsignmentContact(
|
||||
id: number | string,
|
||||
input: {
|
||||
name?: string | undefined
|
||||
phone?: string | undefined
|
||||
wechat?: string | undefined
|
||||
note?: string | undefined
|
||||
sortOrder?: number | undefined
|
||||
now: string
|
||||
},
|
||||
): Promise<ConsignmentContactRow | null> {
|
||||
const updates: string[] = []
|
||||
const values: unknown[] = []
|
||||
|
||||
if (input.name !== undefined) {
|
||||
values.push(input.name)
|
||||
updates.push(`name = $${values.length}`)
|
||||
}
|
||||
if (input.phone !== undefined) {
|
||||
values.push(input.phone)
|
||||
updates.push(`phone = $${values.length}`)
|
||||
}
|
||||
if (input.wechat !== undefined) {
|
||||
values.push(input.wechat)
|
||||
updates.push(`wechat = $${values.length}`)
|
||||
}
|
||||
if (input.note !== undefined) {
|
||||
values.push(input.note)
|
||||
updates.push(`note = $${values.length}`)
|
||||
}
|
||||
if (input.sortOrder !== undefined) {
|
||||
values.push(input.sortOrder)
|
||||
updates.push(`sort_order = $${values.length}`)
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return getConsignmentContactById(id)
|
||||
}
|
||||
|
||||
values.push(input.now)
|
||||
updates.push(`updated_at = $${values.length}`)
|
||||
values.push(id)
|
||||
|
||||
const res = await query<ConsignmentContactRow>(
|
||||
`UPDATE consignment_contact_supporters
|
||||
SET ${updates.join(', ')}
|
||||
WHERE id = $${values.length}
|
||||
RETURNING *`,
|
||||
values,
|
||||
)
|
||||
return res.rows[0] || null
|
||||
}
|
||||
|
||||
export async function setConsignmentContactEnabled(
|
||||
id: number | string,
|
||||
enabled: boolean,
|
||||
now: string,
|
||||
): Promise<ConsignmentContactRow | null> {
|
||||
const res = await query<ConsignmentContactRow>(
|
||||
`UPDATE consignment_contact_supporters
|
||||
SET enabled = $1, updated_at = $2
|
||||
WHERE id = $3
|
||||
RETURNING *`,
|
||||
[enabled, now, id],
|
||||
)
|
||||
return res.rows[0] || null
|
||||
}
|
||||
|
||||
/** 删除前统计引用:有历史寄卖时禁止硬删,避免历史单据的对接客服变成空白。 */
|
||||
export async function countConsignmentContactReferences(id: number | string): Promise<number> {
|
||||
const res = await query<{ total: number }>(
|
||||
`SELECT (
|
||||
(SELECT COUNT(*) FROM consignment_submissions WHERE contact_supporter_id = $1) +
|
||||
(SELECT COUNT(*) FROM consignment_inventories WHERE contact_supporter_id = $1)
|
||||
)::integer AS total`,
|
||||
[id],
|
||||
)
|
||||
return Number(res.rows[0]?.total || 0)
|
||||
}
|
||||
|
||||
export async function deleteConsignmentContact(id: number | string): Promise<boolean> {
|
||||
const res = await query<{ id: string | number }>(
|
||||
'DELETE FROM consignment_contact_supporters WHERE id = $1 RETURNING id',
|
||||
[id],
|
||||
)
|
||||
return res.rows.length > 0
|
||||
}
|
||||
@@ -50,6 +50,11 @@ export type ConsignmentInventoryRow = {
|
||||
worker_phone?: string
|
||||
assigned_worker_id?: string | number | null
|
||||
deposit_amount_fen?: number
|
||||
/** 对接客服(发布/审核入库时选定,用于提成归属与联系) */
|
||||
contact_supporter_id?: string | number | null
|
||||
contact_supporter_name?: string
|
||||
contact_supporter_phone?: string
|
||||
contact_supporter_wechat?: string
|
||||
inbound_at: string
|
||||
consumed_at: string | null
|
||||
created_at: string
|
||||
@@ -79,6 +84,11 @@ export type ConsignmentSubmissionRow = {
|
||||
worker_name?: string
|
||||
worker_phone?: string
|
||||
deposit_amount_fen?: number
|
||||
/** 对接客服(发布时选定,用于提成归属与联系) */
|
||||
contact_supporter_id?: string | number | null
|
||||
contact_supporter_name?: string
|
||||
contact_supporter_phone?: string
|
||||
contact_supporter_wechat?: string
|
||||
status: 'pending_review' | 'approved' | 'rejected'
|
||||
review_note: string
|
||||
reviewer_name: string
|
||||
@@ -175,13 +185,13 @@ export async function listConsignmentInventories(filters: {
|
||||
if (filters.keyword) {
|
||||
values.push(`%${filters.keyword.trim()}%`)
|
||||
conditions.push(
|
||||
`(ci.item_name ILIKE $${values.length} OR ci.inventory_no ILIKE $${values.length} OR ci.bound_platform_order_id ILIKE $${values.length} OR ci.seller_contact ILIKE $${values.length} OR ci.seller_game_id ILIKE $${values.length} OR ci.seller_payment_info ILIKE $${values.length} OR ci.remark ILIKE $${values.length} OR cs.submission_no ILIKE $${values.length})`,
|
||||
`(ci.item_name ILIKE $${values.length} OR ci.inventory_no ILIKE $${values.length} OR ci.bound_platform_order_id ILIKE $${values.length} OR ci.seller_contact ILIKE $${values.length} OR ci.seller_game_id ILIKE $${values.length} OR ci.seller_payment_info ILIKE $${values.length} OR ci.remark ILIKE $${values.length} OR cs.submission_no ILIKE $${values.length} OR ccs.name ILIKE $${values.length})`,
|
||||
)
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
||||
// 计数也要带上提报单 join:关键字现在包含 cs.submission_no(卖家端主标识就是它)
|
||||
const countSql = `SELECT COUNT(*)::integer AS total FROM consignment_inventories ci LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id ${whereClause}`
|
||||
// 计数也要带上提报单 / 对接客服 join:关键字现在包含 cs.submission_no 与 ccs.name
|
||||
const countSql = `SELECT COUNT(*)::integer AS total FROM consignment_inventories ci LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id ${whereClause}`
|
||||
const countRes = await query<{ total: number }>(countSql, values)
|
||||
const total = countRes.rows[0]?.total || 0
|
||||
|
||||
@@ -202,10 +212,14 @@ export async function listConsignmentInventories(filters: {
|
||||
wo.gift_phase AS gift_phase,
|
||||
wo.friend_added_at AS gift_friend_added_at,
|
||||
wo.material_json AS bound_work_order_material_json,
|
||||
wo.requirement_json AS bound_work_order_requirement_json
|
||||
wo.requirement_json AS bound_work_order_requirement_json,
|
||||
ccs.name AS contact_supporter_name,
|
||||
ccs.phone AS contact_supporter_phone,
|
||||
ccs.wechat AS contact_supporter_wechat
|
||||
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
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id
|
||||
LEFT JOIN work_orders wo ON wo.id = ci.bound_work_order_id
|
||||
${whereClause}
|
||||
ORDER BY ci.id DESC
|
||||
@@ -321,10 +335,14 @@ export async function getConsignmentInventoryById(
|
||||
wo.gift_phase AS gift_phase,
|
||||
wo.friend_added_at AS gift_friend_added_at,
|
||||
wo.material_json AS bound_work_order_material_json,
|
||||
wo.requirement_json AS bound_work_order_requirement_json
|
||||
wo.requirement_json AS bound_work_order_requirement_json,
|
||||
ccs.name AS contact_supporter_name,
|
||||
ccs.phone AS contact_supporter_phone,
|
||||
ccs.wechat AS contact_supporter_wechat
|
||||
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
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id
|
||||
LEFT JOIN work_orders wo ON wo.id = ci.bound_work_order_id
|
||||
WHERE ci.id = $1
|
||||
LIMIT 1
|
||||
@@ -344,10 +362,14 @@ export async function listConsignmentInventoriesByIds(
|
||||
ci.*,
|
||||
wc.name AS category_name,
|
||||
cs.submission_no AS source_submission_no,
|
||||
cs.screenshots_json AS submission_screenshots_json
|
||||
cs.screenshots_json AS submission_screenshots_json,
|
||||
ccs.name AS contact_supporter_name,
|
||||
ccs.phone AS contact_supporter_phone,
|
||||
ccs.wechat AS contact_supporter_wechat
|
||||
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
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id
|
||||
WHERE ci.id = ANY($1::bigint[])
|
||||
`
|
||||
const res = await query<ConsignmentInventoryRow>(sql, [numericIds])
|
||||
@@ -379,6 +401,7 @@ export async function updateConsignmentInventory(
|
||||
workerName?: string | undefined
|
||||
workerPhone?: string | undefined
|
||||
depositAmountFen?: number | undefined
|
||||
contactSupporterId?: number | null | undefined
|
||||
now: string
|
||||
},
|
||||
): Promise<ConsignmentInventoryRow | null> {
|
||||
@@ -473,6 +496,10 @@ export async function updateConsignmentInventory(
|
||||
values.push(input.depositAmountFen)
|
||||
updates.push(`deposit_amount_fen = $${values.length}`)
|
||||
}
|
||||
if (input.contactSupporterId !== undefined) {
|
||||
values.push(input.contactSupporterId)
|
||||
updates.push(`contact_supporter_id = $${values.length}`)
|
||||
}
|
||||
|
||||
if (updates.length === 0) return getConsignmentInventoryById(id)
|
||||
|
||||
@@ -484,10 +511,13 @@ export async function updateConsignmentInventory(
|
||||
UPDATE consignment_inventories
|
||||
SET ${updates.join(', ')}
|
||||
WHERE id = $${values.length}
|
||||
RETURNING *
|
||||
RETURNING id
|
||||
`
|
||||
const res = await query<ConsignmentInventoryRow>(sql, values)
|
||||
return res.rows[0] || null
|
||||
const res = await query<{ id: string | number }>(sql, values)
|
||||
const updatedId = res.rows[0]?.id
|
||||
// 重新走带 join 的详情查询:RETURNING * 拿不到对接客服姓名,
|
||||
// 编辑保存后的即时回显会变成空白(列表刷新前)。
|
||||
return updatedId === undefined ? null : getConsignmentInventoryById(updatedId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -880,9 +910,13 @@ export async function listActiveConsignmentInventoriesForMatching(options?: {
|
||||
const sql = `
|
||||
SELECT
|
||||
ci.*,
|
||||
cs.submission_no AS source_submission_no
|
||||
cs.submission_no AS source_submission_no,
|
||||
ccs.name AS contact_supporter_name,
|
||||
ccs.phone AS contact_supporter_phone,
|
||||
ccs.wechat AS contact_supporter_wechat
|
||||
FROM consignment_inventories ci
|
||||
LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id
|
||||
WHERE ${conditions.join(' AND ')}
|
||||
ORDER BY ci.id ASC
|
||||
`
|
||||
@@ -913,12 +947,12 @@ export async function listConsignmentSubmissions(filters: {
|
||||
if (filters.keyword) {
|
||||
values.push(`%${filters.keyword.trim()}%`)
|
||||
conditions.push(
|
||||
`(cs.item_name ILIKE $${values.length} OR cs.submission_no ILIKE $${values.length} OR cs.contact_info ILIKE $${values.length} OR cs.seller_game_id ILIKE $${values.length} OR cs.seller_phone ILIKE $${values.length})`,
|
||||
`(cs.item_name ILIKE $${values.length} OR cs.submission_no ILIKE $${values.length} OR cs.contact_info ILIKE $${values.length} OR cs.seller_game_id ILIKE $${values.length} OR cs.seller_phone ILIKE $${values.length} OR ccs.name ILIKE $${values.length})`,
|
||||
)
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
|
||||
const countSql = `SELECT COUNT(*)::integer AS total FROM consignment_submissions cs ${whereClause}`
|
||||
const countSql = `SELECT COUNT(*)::integer AS total FROM consignment_submissions cs LEFT JOIN consignment_contact_supporters ccs ON ccs.id = cs.contact_supporter_id ${whereClause}`
|
||||
const countRes = await query<{ total: number }>(countSql, values)
|
||||
const total = countRes.rows[0]?.total || 0
|
||||
|
||||
@@ -934,9 +968,13 @@ export async function listConsignmentSubmissions(filters: {
|
||||
wc.name AS category_name,
|
||||
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,
|
||||
ccs.name AS contact_supporter_name,
|
||||
ccs.phone AS contact_supporter_phone,
|
||||
ccs.wechat AS contact_supporter_wechat
|
||||
FROM consignment_submissions cs
|
||||
LEFT JOIN work_categories wc ON wc.id = cs.category_id
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = cs.contact_supporter_id
|
||||
LEFT JOIN consignment_inventories rinv ON (rinv.id = cs.result_inventory_id OR rinv.source_submission_id = cs.id)
|
||||
LEFT JOIN work_orders wo ON wo.id = rinv.bound_work_order_id
|
||||
${whereClause}
|
||||
@@ -1152,9 +1190,13 @@ export async function listConsignmentSubmissionsByIds(
|
||||
wc.name AS category_name,
|
||||
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,
|
||||
ccs.name AS contact_supporter_name,
|
||||
ccs.phone AS contact_supporter_phone,
|
||||
ccs.wechat AS contact_supporter_wechat
|
||||
FROM consignment_submissions cs
|
||||
LEFT JOIN work_categories wc ON wc.id = cs.category_id
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = cs.contact_supporter_id
|
||||
LEFT JOIN consignment_inventories rinv
|
||||
ON (rinv.id = cs.result_inventory_id OR rinv.source_submission_id = cs.id)
|
||||
LEFT JOIN work_orders wo ON wo.id = rinv.bound_work_order_id
|
||||
@@ -1179,10 +1221,14 @@ export async function getConsignmentInventoryBySubmissionId(
|
||||
SELECT
|
||||
ci.*,
|
||||
wc.name AS category_name,
|
||||
cs.submission_no AS source_submission_no
|
||||
cs.submission_no AS source_submission_no,
|
||||
ccs.name AS contact_supporter_name,
|
||||
ccs.phone AS contact_supporter_phone,
|
||||
ccs.wechat AS contact_supporter_wechat
|
||||
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
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id
|
||||
WHERE ci.source_submission_id = $1
|
||||
ORDER BY ci.id DESC
|
||||
LIMIT 1
|
||||
@@ -1201,9 +1247,13 @@ export async function getConsignmentSubmissionById(
|
||||
wc.name AS category_name,
|
||||
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,
|
||||
ccs.name AS contact_supporter_name,
|
||||
ccs.phone AS contact_supporter_phone,
|
||||
ccs.wechat AS contact_supporter_wechat
|
||||
FROM consignment_submissions cs
|
||||
LEFT JOIN work_categories wc ON wc.id = cs.category_id
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = cs.contact_supporter_id
|
||||
LEFT JOIN consignment_inventories rinv ON (rinv.id = cs.result_inventory_id OR rinv.source_submission_id = cs.id)
|
||||
LEFT JOIN work_orders wo ON wo.id = rinv.bound_work_order_id
|
||||
WHERE cs.id = $1
|
||||
@@ -1234,6 +1284,7 @@ export async function createConsignmentSubmission(input: {
|
||||
workerName?: string
|
||||
workerPhone?: string
|
||||
depositAmountFen?: number
|
||||
contactSupporterId?: number | null
|
||||
now: string
|
||||
}): Promise<ConsignmentSubmissionRow> {
|
||||
return withTransaction((client) => createConsignmentSubmissionWithClient(client, input))
|
||||
@@ -1262,6 +1313,7 @@ export async function createConsignmentSubmissionWithClient(
|
||||
workerName?: string
|
||||
workerPhone?: string
|
||||
depositAmountFen?: number
|
||||
contactSupporterId?: number | null
|
||||
now: string
|
||||
},
|
||||
): Promise<ConsignmentSubmissionRow> {
|
||||
@@ -1272,14 +1324,14 @@ export async function createConsignmentSubmissionWithClient(
|
||||
seller_game_id, seller_payment_info, seller_phone, contact_info,
|
||||
content_json, screenshots_json, asking_price_fen,
|
||||
worker_id, worker_name, worker_phone, deposit_amount_fen,
|
||||
status, created_at, updated_at
|
||||
contact_supporter_id, status, created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4,
|
||||
$5, $6, $7, $8, $9,
|
||||
$10, $11, $12, $13,
|
||||
$14::jsonb, $15::jsonb, $16,
|
||||
$17, $18, $19, $20,
|
||||
'pending_review', $21, $21
|
||||
$21, 'pending_review', $22, $22
|
||||
)
|
||||
RETURNING *
|
||||
`
|
||||
@@ -1304,6 +1356,7 @@ export async function createConsignmentSubmissionWithClient(
|
||||
input.workerName || '',
|
||||
input.workerPhone || '',
|
||||
input.depositAmountFen || 0,
|
||||
input.contactSupporterId || null,
|
||||
input.now,
|
||||
])
|
||||
return res.rows[0]!
|
||||
@@ -1371,6 +1424,7 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
workerName?: string | undefined
|
||||
workerPhone?: string | undefined
|
||||
depositAmountFen?: number | undefined
|
||||
contactSupporterId?: number | null | undefined
|
||||
now: string
|
||||
}): Promise<{ submission: ConsignmentSubmissionRow; inventory: ConsignmentInventoryRow }> {
|
||||
return withTransaction(async (client: PoolClient) => {
|
||||
@@ -1391,6 +1445,10 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
input.depositAmountFen !== undefined
|
||||
? input.depositAmountFen
|
||||
: submission.deposit_amount_fen || 0
|
||||
const contactSupporterId =
|
||||
input.contactSupporterId !== undefined
|
||||
? input.contactSupporterId
|
||||
: (submission.contact_supporter_id ?? null)
|
||||
|
||||
const invSql = `
|
||||
INSERT INTO consignment_inventories (
|
||||
@@ -1400,7 +1458,7 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
seller_phone, credentials_json, screenshots_json, remark,
|
||||
allow_matching, status, allocation_status, source_type,
|
||||
source_submission_id, worker_id, worker_name, worker_phone, deposit_amount_fen,
|
||||
inbound_at, created_at, updated_at
|
||||
contact_supporter_id, inbound_at, created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$7, $8, $9, $10, $11,
|
||||
@@ -1408,7 +1466,7 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
$16, $17::jsonb, $18::jsonb, $19,
|
||||
TRUE, 'in_stock', '待安排', 'worker_portal',
|
||||
$20, $21, $22, $23, $24,
|
||||
$25, $25, $25
|
||||
$25, $26, $26, $26
|
||||
)
|
||||
RETURNING *
|
||||
`
|
||||
@@ -1437,6 +1495,7 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
workerName,
|
||||
workerPhone,
|
||||
depositAmountFen,
|
||||
contactSupporterId,
|
||||
input.now,
|
||||
]
|
||||
const invRes = await client.query<ConsignmentInventoryRow>(invSql, invValues)
|
||||
|
||||
@@ -26,3 +26,4 @@ export * from './after-sales-repo.js'
|
||||
export * from './product-match-repo.js'
|
||||
export * from './sms-code-repo.js'
|
||||
export * from './consignment-repo.js'
|
||||
export * from './consignment-contact-repo.js'
|
||||
|
||||
@@ -13,6 +13,13 @@ import {
|
||||
toggleAdminConsignmentMatching,
|
||||
updateAdminConsignmentInventory,
|
||||
} from '../../../services/worker-platform/consignment-admin-service.js'
|
||||
import {
|
||||
createAdminConsignmentContact,
|
||||
deleteAdminConsignmentContact,
|
||||
fetchAdminConsignmentContacts,
|
||||
setAdminConsignmentContactEnabled,
|
||||
updateAdminConsignmentContact,
|
||||
} from '../../../services/worker-platform/consignment-contact-service.js'
|
||||
import {
|
||||
assignWorkOrderWithConsignmentWorker,
|
||||
checkWorkOrderInventoryMatches,
|
||||
@@ -54,6 +61,64 @@ router.get(
|
||||
}),
|
||||
)
|
||||
|
||||
// ---------------- 对接客服名单 ----------------
|
||||
|
||||
router.get(
|
||||
'/worker-platform/consignment/contacts',
|
||||
requireAdminPermission('consignment.view'),
|
||||
createJsonHandler((req) => fetchAdminConsignmentContacts(req.query), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取对接客服名单失败',
|
||||
scope: '[admin/consignment/contacts]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/consignment/contacts',
|
||||
requireAdminPermission('consignment.manage'),
|
||||
createJsonHandler((req) => createAdminConsignmentContact(req.body || {}), {
|
||||
successMessage: '对接客服已新增',
|
||||
errorMessage: '新增对接客服失败',
|
||||
scope: '[admin/consignment/contacts/create]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.put(
|
||||
'/worker-platform/consignment/contacts/:id',
|
||||
requireAdminPermission('consignment.manage'),
|
||||
createJsonHandler(
|
||||
(req) => updateAdminConsignmentContact(String(req.params.id || ''), req.body || {}),
|
||||
{
|
||||
successMessage: '对接客服已更新',
|
||||
errorMessage: '更新对接客服失败',
|
||||
scope: '[admin/consignment/contacts/update]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.patch(
|
||||
'/worker-platform/consignment/contacts/:id/enabled',
|
||||
requireAdminPermission('consignment.manage'),
|
||||
createJsonHandler(
|
||||
(req) => setAdminConsignmentContactEnabled(String(req.params.id || ''), req.body?.enabled),
|
||||
{
|
||||
successMessage: '对接客服状态已更新',
|
||||
errorMessage: '更新对接客服状态失败',
|
||||
scope: '[admin/consignment/contacts/enabled]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/worker-platform/consignment/contacts/:id',
|
||||
requireAdminPermission('consignment.manage'),
|
||||
createJsonHandler((req) => deleteAdminConsignmentContact(String(req.params.id || '')), {
|
||||
successMessage: '对接客服已删除',
|
||||
errorMessage: '删除对接客服失败',
|
||||
scope: '[admin/consignment/contacts/delete]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/worker-platform/consignment/inventories/:id',
|
||||
requireAdminPermission('consignment.view'),
|
||||
|
||||
@@ -7,11 +7,25 @@ import {
|
||||
publishWorkerConsignment,
|
||||
submitWorkerConsignmentGiftEvidence,
|
||||
} from '../../services/worker-platform/consignment-worker-service.js'
|
||||
import { fetchWorkerConsignmentContacts } from '../../services/worker-platform/consignment-contact-service.js'
|
||||
import { sendRouteError } from '../../utils/http.js'
|
||||
import { getRequiredWorkerSession, requireActiveWorker } from './session.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
/**
|
||||
* GET /api/v1/worker/consignment/contacts
|
||||
* 打手发布寄卖时的「对接客服」下拉:只返回启用中的名单
|
||||
*/
|
||||
router.get('/consignment/contacts', async (req, res) => {
|
||||
try {
|
||||
const result = await fetchWorkerConsignmentContacts()
|
||||
res.json({ ok: true, data: result })
|
||||
} catch (error) {
|
||||
sendRouteError(res, error, '获取对接客服失败', '[worker/consignment/contacts]')
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* POST /api/v1/worker/consignment/publish
|
||||
* 打手在接单平台直接提报苏尔南手枪寄卖
|
||||
|
||||
@@ -226,6 +226,10 @@ export function mapConsignmentInventory(row: ConsignmentInventoryRow) {
|
||||
workerPhone: row.worker_phone || '',
|
||||
assignedWorkerId: row.assigned_worker_id ? Number(row.assigned_worker_id) : null,
|
||||
depositAmountFen: Number(row.deposit_amount_fen || 0),
|
||||
contactSupporterId: row.contact_supporter_id ? Number(row.contact_supporter_id) : null,
|
||||
contactSupporterName: row.contact_supporter_name || '',
|
||||
contactSupporterPhone: row.contact_supporter_phone || '',
|
||||
contactSupporterWechat: row.contact_supporter_wechat || '',
|
||||
inboundAt: row.inbound_at || row.created_at,
|
||||
consumedAt: row.consumed_at,
|
||||
createdAt: row.created_at,
|
||||
@@ -266,6 +270,10 @@ export function mapConsignmentSubmission(row: ConsignmentSubmissionRow) {
|
||||
workerName: row.worker_name || '',
|
||||
workerPhone: row.worker_phone || '',
|
||||
depositAmountFen: Number(row.deposit_amount_fen || 0),
|
||||
contactSupporterId: row.contact_supporter_id ? Number(row.contact_supporter_id) : null,
|
||||
contactSupporterName: row.contact_supporter_name || '',
|
||||
contactSupporterPhone: row.contact_supporter_phone || '',
|
||||
contactSupporterWechat: row.contact_supporter_wechat || '',
|
||||
status: row.status,
|
||||
reviewNote: row.review_note || '',
|
||||
reviewerName: row.reviewer_name || '',
|
||||
@@ -407,6 +415,12 @@ export async function updateAdminConsignmentInventory(
|
||||
workerPhone: payload.workerPhone !== undefined ? String(payload.workerPhone).trim() : undefined,
|
||||
depositAmountFen:
|
||||
payload.depositAmountFen !== undefined ? Number(payload.depositAmountFen) : undefined,
|
||||
contactSupporterId:
|
||||
payload.contactSupporterId !== undefined
|
||||
? payload.contactSupporterId
|
||||
? Number(payload.contactSupporterId)
|
||||
: null
|
||||
: undefined,
|
||||
now,
|
||||
})
|
||||
|
||||
@@ -655,12 +669,25 @@ export async function approveAdminConsignmentSubmission(
|
||||
workerName,
|
||||
workerPhone,
|
||||
depositAmountFen,
|
||||
contactSupporterId:
|
||||
payload.contactSupporterId !== undefined
|
||||
? payload.contactSupporterId
|
||||
? Number(payload.contactSupporterId)
|
||||
: null
|
||||
: undefined,
|
||||
now,
|
||||
})
|
||||
|
||||
// 入库 INSERT 的 RETURNING * 不带 join 出来的对接客服姓名,这里按 id 重读一次再出参,
|
||||
// 避免审核成功后前端立即刷新时「对接客服」短暂显示为空。
|
||||
const [joinedInventory, joinedSubmission] = await Promise.all([
|
||||
getConsignmentInventoryById(res.inventory.id),
|
||||
getConsignmentSubmissionById(id),
|
||||
])
|
||||
|
||||
return {
|
||||
submission: mapConsignmentSubmission(res.submission),
|
||||
inventory: mapConsignmentInventory(res.inventory),
|
||||
submission: mapConsignmentSubmission(joinedSubmission || res.submission),
|
||||
inventory: mapConsignmentInventory(joinedInventory || res.inventory),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import {
|
||||
countConsignmentContactReferences,
|
||||
createConsignmentContact,
|
||||
deleteConsignmentContact,
|
||||
findConsignmentContactByName,
|
||||
getConsignmentContactById,
|
||||
listConsignmentContacts,
|
||||
listEnabledConsignmentContacts,
|
||||
setConsignmentContactEnabled,
|
||||
updateConsignmentContact,
|
||||
type ConsignmentContactRow,
|
||||
} from '../../repositories/worker-platform/consignment-contact-repo.js'
|
||||
|
||||
export function mapConsignmentContact(row: ConsignmentContactRow) {
|
||||
return {
|
||||
id: Number(row.id),
|
||||
name: row.name,
|
||||
phone: row.phone || '',
|
||||
wechat: row.wechat || '',
|
||||
note: row.note || '',
|
||||
enabled: Boolean(row.enabled),
|
||||
sortOrder: Number(row.sort_order || 0),
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeText(value: unknown): string {
|
||||
return String(value ?? '').trim()
|
||||
}
|
||||
|
||||
function normalizeSortOrder(value: unknown): number {
|
||||
const num = Number(value)
|
||||
return Number.isFinite(num) ? Math.round(num) : 0
|
||||
}
|
||||
|
||||
function requireContactName(value: unknown): string {
|
||||
const name = normalizeText(value)
|
||||
if (!name) {
|
||||
throw createHttpError('请填写对接客服姓名', {
|
||||
statusCode: 400,
|
||||
errorCode: 'consignment_contact_name_required',
|
||||
})
|
||||
}
|
||||
if (name.length > 60) {
|
||||
throw createHttpError('对接客服姓名不能超过 60 个字符', {
|
||||
statusCode: 400,
|
||||
errorCode: 'consignment_contact_name_too_long',
|
||||
})
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
async function assertContactNameAvailable(name: string, excludeId?: number | string) {
|
||||
const duplicated = await findConsignmentContactByName(name, excludeId)
|
||||
if (duplicated) {
|
||||
throw createHttpError(`已存在同名对接客服「${name}」,请换一个名字`, {
|
||||
statusCode: 409,
|
||||
errorCode: 'consignment_contact_name_duplicated',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 管理端:完整名单(含禁用),用于配置页。 */
|
||||
export async function fetchAdminConsignmentContacts(query: JsonObject = {}) {
|
||||
const keyword = normalizeText(query.keyword)
|
||||
const enabledRaw = normalizeText(query.enabled)
|
||||
const enabled = enabledRaw === 'true' ? true : enabledRaw === 'false' ? false : undefined
|
||||
const rows = await listConsignmentContacts({
|
||||
keyword: keyword || undefined,
|
||||
enabled,
|
||||
})
|
||||
return {
|
||||
items: rows.map(mapConsignmentContact),
|
||||
total: rows.length,
|
||||
}
|
||||
}
|
||||
|
||||
/** 打手端:发布下拉只取启用项。 */
|
||||
export async function fetchWorkerConsignmentContacts() {
|
||||
const rows = await listEnabledConsignmentContacts()
|
||||
return {
|
||||
items: rows.map(mapConsignmentContact),
|
||||
}
|
||||
}
|
||||
|
||||
export async function createAdminConsignmentContact(payload: JsonObject) {
|
||||
const name = requireContactName(payload.name)
|
||||
await assertContactNameAvailable(name)
|
||||
const row = await createConsignmentContact({
|
||||
name,
|
||||
phone: normalizeText(payload.phone),
|
||||
wechat: normalizeText(payload.wechat),
|
||||
note: normalizeText(payload.note),
|
||||
enabled: payload.enabled !== false,
|
||||
sortOrder: normalizeSortOrder(payload.sortOrder),
|
||||
now: nowIso(),
|
||||
})
|
||||
return mapConsignmentContact(row)
|
||||
}
|
||||
|
||||
export async function updateAdminConsignmentContact(id: number | string, payload: JsonObject) {
|
||||
const current = await getConsignmentContactById(id)
|
||||
if (!current) {
|
||||
throw createHttpError('对接客服不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'consignment_contact_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
const name = payload.name !== undefined ? requireContactName(payload.name) : undefined
|
||||
if (name !== undefined) {
|
||||
await assertContactNameAvailable(name, id)
|
||||
}
|
||||
|
||||
const updated = await updateConsignmentContact(id, {
|
||||
name,
|
||||
phone: payload.phone !== undefined ? normalizeText(payload.phone) : undefined,
|
||||
wechat: payload.wechat !== undefined ? normalizeText(payload.wechat) : undefined,
|
||||
note: payload.note !== undefined ? normalizeText(payload.note) : undefined,
|
||||
sortOrder: payload.sortOrder !== undefined ? normalizeSortOrder(payload.sortOrder) : undefined,
|
||||
now: nowIso(),
|
||||
})
|
||||
if (!updated) {
|
||||
throw createHttpError('对接客服不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'consignment_contact_not_found',
|
||||
})
|
||||
}
|
||||
return mapConsignmentContact(updated)
|
||||
}
|
||||
|
||||
export async function setAdminConsignmentContactEnabled(
|
||||
id: number | string,
|
||||
enabledValue: unknown,
|
||||
) {
|
||||
if (typeof enabledValue !== 'boolean') {
|
||||
throw createHttpError('启用状态必须是布尔值', {
|
||||
statusCode: 400,
|
||||
errorCode: 'consignment_contact_enabled_invalid',
|
||||
})
|
||||
}
|
||||
const enabled = enabledValue
|
||||
const current = await getConsignmentContactById(id)
|
||||
if (!current) {
|
||||
throw createHttpError('对接客服不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'consignment_contact_not_found',
|
||||
})
|
||||
}
|
||||
if (!enabled && current.enabled) {
|
||||
const enabledRows = await listEnabledConsignmentContacts()
|
||||
if (enabledRows.length <= 1) {
|
||||
throw createHttpError('至少保留一个启用中的对接客服,否则打手无法发布寄卖', {
|
||||
statusCode: 409,
|
||||
errorCode: 'consignment_contact_last_enabled',
|
||||
})
|
||||
}
|
||||
}
|
||||
const updated = await setConsignmentContactEnabled(id, enabled, nowIso())
|
||||
if (!updated) {
|
||||
throw createHttpError('对接客服不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'consignment_contact_not_found',
|
||||
})
|
||||
}
|
||||
return mapConsignmentContact(updated)
|
||||
}
|
||||
|
||||
export async function deleteAdminConsignmentContact(id: number | string) {
|
||||
const current = await getConsignmentContactById(id)
|
||||
if (!current) {
|
||||
throw createHttpError('对接客服不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'consignment_contact_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
const referenceCount = await countConsignmentContactReferences(id)
|
||||
if (referenceCount > 0) {
|
||||
throw createHttpError(
|
||||
`该对接客服已被 ${referenceCount} 条寄卖记录使用,不能删除;请改为「禁用」,历史记录仍会保留。`,
|
||||
{ statusCode: 409, errorCode: 'consignment_contact_in_use' },
|
||||
)
|
||||
}
|
||||
|
||||
if (current.enabled) {
|
||||
const enabledRows = await listEnabledConsignmentContacts()
|
||||
if (enabledRows.length <= 1) {
|
||||
throw createHttpError('至少保留一个启用中的对接客服,请先新增并启用其他客服', {
|
||||
statusCode: 409,
|
||||
errorCode: 'consignment_contact_last_enabled',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const deleted = await deleteConsignmentContact(id)
|
||||
if (!deleted) {
|
||||
throw createHttpError('对接客服不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'consignment_contact_not_found',
|
||||
})
|
||||
}
|
||||
return { deleted: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布寄卖时校验「对接客服」:必填 + 必须存在于启用名单。
|
||||
* 返回整行,调用方取 id 落库即可。
|
||||
*/
|
||||
export async function resolveEnabledConsignmentContact(
|
||||
contactSupporterId: unknown,
|
||||
): Promise<ConsignmentContactRow> {
|
||||
const id = Number(contactSupporterId)
|
||||
if (!Number.isFinite(id) || id <= 0) {
|
||||
throw createHttpError('请选择对接客服', {
|
||||
statusCode: 400,
|
||||
errorCode: 'consignment_contact_required',
|
||||
})
|
||||
}
|
||||
const row = await getConsignmentContactById(id)
|
||||
if (!row || !row.enabled) {
|
||||
throw createHttpError('对接客服不存在或已停用,请刷新后重新选择', {
|
||||
statusCode: 400,
|
||||
errorCode: 'consignment_contact_invalid',
|
||||
})
|
||||
}
|
||||
return row
|
||||
}
|
||||
@@ -507,6 +507,12 @@ export async function fulfillWorkOrderWithConsignment(input: {
|
||||
sellerGameId: inv.seller_game_id || '',
|
||||
sellerContact: inv.seller_contact || '',
|
||||
sellerPhone: inv.seller_phone || '',
|
||||
contactSupporterId: inventory.contact_supporter_id
|
||||
? Number(inventory.contact_supporter_id)
|
||||
: null,
|
||||
contactSupporterName: inventory.contact_supporter_name || '',
|
||||
contactSupporterPhone: inventory.contact_supporter_phone || '',
|
||||
contactSupporterWechat: inventory.contact_supporter_wechat || '',
|
||||
workerName: inv.worker_name || '',
|
||||
workerPhone: inv.worker_phone || '',
|
||||
remark: inv.remark || '',
|
||||
@@ -552,6 +558,10 @@ export async function fulfillWorkOrderWithConsignment(input: {
|
||||
priceAdjustReason: '寄卖内部现货履约按现货结算成本价更新发单金额',
|
||||
buyerName: inv.buyer_name,
|
||||
shopName,
|
||||
contactSupporterId: inventory.contact_supporter_id
|
||||
? Number(inventory.contact_supporter_id)
|
||||
: null,
|
||||
contactSupporterName: inventory.contact_supporter_name || '',
|
||||
matchType:
|
||||
specMatch?.matchType || (input.allowSpecMismatch === true ? 'conflict' : 'exact'),
|
||||
manualMatch: Boolean(matchNote) || input.allowSpecMismatch === true,
|
||||
@@ -757,6 +767,12 @@ export async function assignWorkOrderWithConsignmentWorker(input: {
|
||||
sellerGameId: claimed.seller_game_id || '',
|
||||
sellerContact: claimed.seller_contact || '',
|
||||
sellerPhone: claimed.seller_phone || '',
|
||||
contactSupporterId: inventory.contact_supporter_id
|
||||
? Number(inventory.contact_supporter_id)
|
||||
: null,
|
||||
contactSupporterName: inventory.contact_supporter_name || '',
|
||||
contactSupporterPhone: inventory.contact_supporter_phone || '',
|
||||
contactSupporterWechat: inventory.contact_supporter_wechat || '',
|
||||
workerId: Number(targetWorkerId),
|
||||
workerName: claimed.worker_name || '',
|
||||
workerPhone: claimed.worker_phone || '',
|
||||
|
||||
@@ -15,6 +15,7 @@ import { listWorkOrdersByIds } from '../../repositories/worker-platform/work-ord
|
||||
import type { WorkOrderRow } from '../../repositories/worker-platform/types.js'
|
||||
import { deriveConsignmentItemStage } from '../../domain/consignment-item-stage.js'
|
||||
import { canWorkerPublishConsignment } from '../../domain/consignment-whitelist.js'
|
||||
import { resolveEnabledConsignmentContact } from './consignment-contact-service.js'
|
||||
import { suggestConsignmentPriceFromRules } from './consignment-matching-service.js'
|
||||
import { hasInternalConsignmentFulfillment } from '../../repositories/worker-platform/work-order-gift-repo.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
@@ -43,6 +44,21 @@ function toRecord(value: unknown): Record<string, unknown> | null {
|
||||
: null
|
||||
}
|
||||
|
||||
/** 提报单 / 现货都可能带对接客服信息,统一成前端字段。 */
|
||||
function mapConsignmentContactSupporter(source: {
|
||||
contact_supporter_id?: string | number | null
|
||||
contact_supporter_name?: string
|
||||
contact_supporter_phone?: string
|
||||
contact_supporter_wechat?: string
|
||||
}) {
|
||||
return {
|
||||
contactSupporterId: source.contact_supporter_id ? Number(source.contact_supporter_id) : null,
|
||||
contactSupporterName: source.contact_supporter_name || '',
|
||||
contactSupporterPhone: source.contact_supporter_phone || '',
|
||||
contactSupporterWechat: source.contact_supporter_wechat || '',
|
||||
}
|
||||
}
|
||||
|
||||
function extractConsignmentGiftEvidenceFromWorkOrder(boundWo?: WorkOrderRow | null) {
|
||||
if (!boundWo) {
|
||||
return {
|
||||
@@ -162,6 +178,9 @@ export async function publishWorkerConsignment(workerId: number, payload: JsonOb
|
||||
})
|
||||
}
|
||||
|
||||
// 对接客服必填:发布时选定,后续按人统计提成 / 方便联系。
|
||||
const contactSupporter = await resolveEnabledConsignmentContact(payload.contactSupporterId)
|
||||
|
||||
const system = String(payload.system || '').trim()
|
||||
const serverZone = String(payload.serverZone || '').trim()
|
||||
const series = String(payload.series || '').trim()
|
||||
@@ -298,6 +317,7 @@ export async function publishWorkerConsignment(workerId: number, payload: JsonOb
|
||||
workerName: worker.display_name || worker.username,
|
||||
workerPhone: sellerPhone || worker.phone || '',
|
||||
depositAmountFen,
|
||||
contactSupporterId: Number(contactSupporter.id),
|
||||
now,
|
||||
})
|
||||
|
||||
@@ -330,6 +350,12 @@ export async function publishWorkerConsignment(workerId: number, payload: JsonOb
|
||||
workerName: submission.worker_name,
|
||||
workerPhone: submission.worker_phone,
|
||||
depositAmountFen: submission.deposit_amount_fen,
|
||||
contactSupporterId: submission.contact_supporter_id
|
||||
? Number(submission.contact_supporter_id)
|
||||
: null,
|
||||
contactSupporterName: contactSupporter.name,
|
||||
contactSupporterPhone: contactSupporter.phone || '',
|
||||
contactSupporterWechat: contactSupporter.wechat || '',
|
||||
createdAt: submission.created_at,
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -438,6 +464,8 @@ export async function listWorkerConsignments(
|
||||
const evidence = showingMatchedBuyer
|
||||
? extractConsignmentGiftEvidenceFromWorkOrder(boundWo)
|
||||
: { giftEvidenceImages: [], giftEvidenceNote: '', giftEvidenceSubmittedAt: '' }
|
||||
// 现货上的对接客服优先级更高:客服可能在审核入库后改派对接人,卖家端应看到最新归属。
|
||||
const contactSupporter = inv?.contact_supporter_id ? inv : sub
|
||||
return {
|
||||
/** 唯一锚点:提报单号贯穿发布→审核→在库→匹配→冷却→赠送→结算 */
|
||||
consignmentNo: sub.submission_no,
|
||||
@@ -452,6 +480,7 @@ export async function listWorkerConsignments(
|
||||
brillianceCount: sub.brilliance_count,
|
||||
askingPriceFen: sub.asking_price_fen,
|
||||
depositAmountFen: sub.deposit_amount_fen,
|
||||
...mapConsignmentContactSupporter(contactSupporter),
|
||||
reviewNote: sub.review_note,
|
||||
// 库里存的是入库时的签名链接(15 分钟有效),出参必须重签,否则卖家自己回头看也是裂图
|
||||
screenshotsJson: (Array.isArray(sub.screenshots_json) ? sub.screenshots_json : [])
|
||||
@@ -496,6 +525,7 @@ export async function listWorkerConsignments(
|
||||
brillianceCount: inv.brilliance_count,
|
||||
askingPriceFen: inv.cost_price_fen,
|
||||
depositAmountFen: inv.deposit_amount_fen,
|
||||
...mapConsignmentContactSupporter(inv),
|
||||
reviewNote: '',
|
||||
screenshotsJson: [],
|
||||
giftEvidenceImages: evidence.giftEvidenceImages,
|
||||
|
||||
@@ -62,3 +62,4 @@ export * from './worker-announcement-config-service.js'
|
||||
export * from './worker-hall-config-service.js'
|
||||
export * from './worker-acceptance-config-service.js'
|
||||
export * from './sync-worker-orders-service.js'
|
||||
export * from './consignment-contact-service.js'
|
||||
|
||||
Reference in New Issue
Block a user