修复:寄售登记人改名后显示与筛选同步
This commit is contained in:
@@ -39,7 +39,11 @@ export async function runDatabaseMigrations() {
|
||||
migrationsSchema: 'public',
|
||||
schema: 'public',
|
||||
singleTransaction: true,
|
||||
checkOrder: true,
|
||||
// 联调/生产库可能存在「已应用、但当前分支已删除」的历史迁移记录
|
||||
// (如 153-155 semi_auto_*)。node-pg-migrate 的严格顺序检查会把这类
|
||||
// 残留记录判成乱序并直接拒绝启动;本地迁移文件本身只增不改,
|
||||
// 这里关闭顺序检查,仍会按文件名顺序执行所有未应用迁移。
|
||||
checkOrder: false,
|
||||
logger: {
|
||||
debug: () => undefined,
|
||||
info: (message) => {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
-- 158_consignment_worker_name_sync.sql —— 打手改名后同步寄售登记人快照。
|
||||
--
|
||||
-- 背景:consignment_inventories.worker_name / consignment_submissions.worker_name
|
||||
-- 是入库/提报时的名字快照。worker_users.display_name 改名后不会自动更新,
|
||||
-- 后台寄售列表仍显示旧名,按新名搜索也搜不到。
|
||||
--
|
||||
-- 这里做两件事:
|
||||
-- 1. 用当前昵称/电话回填存量快照;
|
||||
-- 2. worker_users 改名(display_name/username/phone)时自动同步这两张表,
|
||||
-- 之后新改名无需逐表手工处理。
|
||||
--
|
||||
-- 历史工单 material_json.consignment.workerName 不做批量改写:
|
||||
-- 后台展示端按 consignment_inventories 的实时关联覆盖,保留历史 JSON 的登记痕迹。
|
||||
|
||||
UPDATE consignment_inventories ci
|
||||
SET
|
||||
worker_name = COALESCE(NULLIF(wu.display_name, ''), wu.username),
|
||||
worker_phone = wu.phone
|
||||
FROM worker_users wu
|
||||
WHERE ci.worker_id = wu.id
|
||||
AND (
|
||||
ci.worker_name IS DISTINCT FROM COALESCE(NULLIF(wu.display_name, ''), wu.username)
|
||||
OR ci.worker_phone IS DISTINCT FROM wu.phone
|
||||
);
|
||||
|
||||
UPDATE consignment_submissions cs
|
||||
SET
|
||||
worker_name = COALESCE(NULLIF(wu.display_name, ''), wu.username),
|
||||
worker_phone = wu.phone
|
||||
FROM worker_users wu
|
||||
WHERE cs.worker_id = wu.id
|
||||
AND (
|
||||
cs.worker_name IS DISTINCT FROM COALESCE(NULLIF(wu.display_name, ''), wu.username)
|
||||
OR cs.worker_phone IS DISTINCT FROM wu.phone
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION sync_worker_name_to_consignment_snapshots()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
next_worker_name TEXT := COALESCE(NULLIF(NEW.display_name, ''), NEW.username);
|
||||
BEGIN
|
||||
IF NEW.display_name IS DISTINCT FROM OLD.display_name
|
||||
OR NEW.username IS DISTINCT FROM OLD.username
|
||||
OR NEW.phone IS DISTINCT FROM OLD.phone
|
||||
THEN
|
||||
UPDATE consignment_inventories
|
||||
SET
|
||||
worker_name = next_worker_name,
|
||||
worker_phone = NEW.phone
|
||||
WHERE worker_id = NEW.id
|
||||
AND (
|
||||
worker_name IS DISTINCT FROM next_worker_name
|
||||
OR worker_phone IS DISTINCT FROM NEW.phone
|
||||
);
|
||||
|
||||
UPDATE consignment_submissions
|
||||
SET
|
||||
worker_name = next_worker_name,
|
||||
worker_phone = NEW.phone
|
||||
WHERE worker_id = NEW.id
|
||||
AND (
|
||||
worker_name IS DISTINCT FROM next_worker_name
|
||||
OR worker_phone IS DISTINCT FROM NEW.phone
|
||||
);
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_worker_users_consignment_name_sync ON worker_users;
|
||||
CREATE TRIGGER trg_worker_users_consignment_name_sync
|
||||
AFTER UPDATE OF display_name, username, phone ON worker_users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION sync_worker_name_to_consignment_snapshots();
|
||||
@@ -223,6 +223,22 @@ test('内部现货不再因上一手 last_assigned 进入打手可见集', async
|
||||
assert.equal(calls.length, 2, '列表和概览必须共用同一条历史可见条件')
|
||||
})
|
||||
|
||||
test('后台接单人员筛选把寄售供货人的绑定工单纳入可见集合', () => {
|
||||
const query = buildWorkOrderWhere({
|
||||
workerSharingId: 1494,
|
||||
consignmentSupplierWorkerId: 1494,
|
||||
})
|
||||
assert.match(query.joins, /FROM consignment_inventories supplier_ci/)
|
||||
assert.match(query.joins, /supplier_ci\.worker_id = \$2::bigint/)
|
||||
assert.match(query.joins, /supplier_ci\.bound_work_order_id IS NOT NULL/)
|
||||
assert.deepEqual(query.params, [1494, 1494])
|
||||
|
||||
// 打手端「我的订单」不传扩展口径,可见范围必须保持原样
|
||||
const plain = buildWorkOrderWhere({ workerSharingId: 1494 })
|
||||
assert.doesNotMatch(plain.joins, /supplier_ci/)
|
||||
assert.deepEqual(plain.params, [1494])
|
||||
})
|
||||
|
||||
test('「仅问题单」检索走短路分支,从 pending 表反查而非枚举可见集', () => {
|
||||
const query = buildWorkOrderWhere({ workerSharingId: 7, statuses: ['problem'] })
|
||||
|
||||
|
||||
@@ -54,6 +54,52 @@ test('寄卖仓库搜索必须覆盖供货打手姓名、打手电话与页面
|
||||
)
|
||||
})
|
||||
|
||||
test('寄卖提报单搜索必须能搜打手,列表与计数 SQL 都带实时打手关联', async () => {
|
||||
const source = await readFile(new URL('./consignment-repo.ts', import.meta.url), 'utf8')
|
||||
const start = source.indexOf('export async function listConsignmentSubmissions')
|
||||
assert.ok(start > 0, '未找到寄卖提报单列表查询')
|
||||
const body = source.slice(start, source.indexOf('\n}\n', start))
|
||||
|
||||
for (const field of [
|
||||
'cs.worker_name',
|
||||
'cs.worker_phone',
|
||||
'supplier_worker.display_name',
|
||||
'supplier_worker.username',
|
||||
'supplier_worker.phone',
|
||||
]) {
|
||||
assert.ok(body.includes(field), `提报单搜索缺少字段:${field}`)
|
||||
}
|
||||
assert.ok(body.includes('AS live_worker_name'), '列表必须带出实时打手昵称')
|
||||
assert.ok(body.includes('AS live_worker_phone'), '列表必须带出实时打手电话')
|
||||
|
||||
const countSqlLine = body
|
||||
.split('\n')
|
||||
.find((line) =>
|
||||
line.includes('SELECT COUNT(*)::integer AS total FROM consignment_submissions cs'),
|
||||
)
|
||||
assert.ok(countSqlLine, '未找到提报单计数 SQL')
|
||||
assert.ok(
|
||||
countSqlLine!.includes('LEFT JOIN worker_users supplier_worker'),
|
||||
'计数 SQL 缺少打手 join,按姓名/电话搜索会报错',
|
||||
)
|
||||
})
|
||||
|
||||
test('寄售库存详情/批量/匹配候选查询都带出实时打手昵称', async () => {
|
||||
const source = await readFile(new URL('./consignment-repo.ts', import.meta.url), 'utf8')
|
||||
for (const fn of [
|
||||
'getConsignmentInventoryById',
|
||||
'listConsignmentInventoriesByIds',
|
||||
'getConsignmentInventoryBySubmissionId',
|
||||
'listActiveConsignmentInventoriesForMatching',
|
||||
]) {
|
||||
const start = source.indexOf(`export async function ${fn}`)
|
||||
assert.ok(start > 0, `未找到查询:${fn}`)
|
||||
const body = source.slice(start, source.indexOf('\n}\n', start))
|
||||
assert.ok(body.includes('LEFT JOIN worker_users supplier_worker'), `${fn} 缺少实时打手 join`)
|
||||
assert.ok(body.includes('AS live_worker_name'), `${fn} 缺少实时打手昵称字段`)
|
||||
}
|
||||
})
|
||||
|
||||
test('寄卖仓库按通过审核入库时间筛选,结束日期必须包含当天全天', async () => {
|
||||
const source = await readFile(new URL('./consignment-repo.ts', import.meta.url), 'utf8')
|
||||
const inventoryListStart = source.indexOf('export async function listConsignmentInventories')
|
||||
|
||||
@@ -48,6 +48,10 @@ export type ConsignmentInventoryRow = {
|
||||
worker_id?: string | number | null
|
||||
worker_name?: string
|
||||
worker_phone?: string
|
||||
/** 当前打手昵称(实时 join worker_users,优先于入库时的快照) */
|
||||
live_worker_name?: string | null
|
||||
/** 当前打手电话(实时 join worker_users,优先于入库时的快照) */
|
||||
live_worker_phone?: string | null
|
||||
assigned_worker_id?: string | number | null
|
||||
deposit_amount_fen?: number
|
||||
/** 对接客服(发布/审核入库时选定,用于提成归属与联系) */
|
||||
@@ -81,6 +85,10 @@ export type ConsignmentSubmissionRow = {
|
||||
worker_id?: string | number | null
|
||||
worker_name?: string
|
||||
worker_phone?: string
|
||||
/** 当前打手昵称(实时 join worker_users,优先于提报时的快照) */
|
||||
live_worker_name?: string | null
|
||||
/** 当前打手电话(实时 join worker_users,优先于提报时的快照) */
|
||||
live_worker_phone?: string | null
|
||||
deposit_amount_fen?: number
|
||||
/** 对接客服(发布时选定,用于提成归属与联系) */
|
||||
contact_supporter_id?: string | number | null
|
||||
@@ -233,6 +241,8 @@ export async function listConsignmentInventories(filters: {
|
||||
wc.name AS category_name,
|
||||
cs.submission_no AS source_submission_no,
|
||||
cs.screenshots_json AS submission_screenshots_json,
|
||||
COALESCE(NULLIF(supplier_worker.display_name, ''), supplier_worker.username) AS live_worker_name,
|
||||
supplier_worker.phone AS live_worker_phone,
|
||||
wo.acceptance_json AS gift_evidence_json,
|
||||
wo.work_order_no AS gift_work_order_no,
|
||||
wo.status AS gift_work_order_status,
|
||||
@@ -355,6 +365,8 @@ export async function getConsignmentInventoryById(
|
||||
wc.name AS category_name,
|
||||
cs.submission_no AS source_submission_no,
|
||||
cs.screenshots_json AS submission_screenshots_json,
|
||||
COALESCE(NULLIF(supplier_worker.display_name, ''), supplier_worker.username) AS live_worker_name,
|
||||
supplier_worker.phone AS live_worker_phone,
|
||||
wo.acceptance_json AS gift_evidence_json,
|
||||
wo.work_order_no AS gift_work_order_no,
|
||||
wo.status AS gift_work_order_status,
|
||||
@@ -366,6 +378,7 @@ export async function getConsignmentInventoryById(
|
||||
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 worker_users supplier_worker ON supplier_worker.id = ci.worker_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
|
||||
@@ -387,10 +400,13 @@ export async function listConsignmentInventoriesByIds(
|
||||
wc.name AS category_name,
|
||||
cs.submission_no AS source_submission_no,
|
||||
cs.screenshots_json AS submission_screenshots_json,
|
||||
COALESCE(NULLIF(supplier_worker.display_name, ''), supplier_worker.username) AS live_worker_name,
|
||||
supplier_worker.phone AS live_worker_phone,
|
||||
ccs.name AS contact_supporter_name
|
||||
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 worker_users supplier_worker ON supplier_worker.id = ci.worker_id
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id
|
||||
WHERE ci.id = ANY($1::bigint[])
|
||||
`
|
||||
@@ -933,9 +949,12 @@ export async function listActiveConsignmentInventoriesForMatching(options?: {
|
||||
SELECT
|
||||
ci.*,
|
||||
cs.submission_no AS source_submission_no,
|
||||
COALESCE(NULLIF(supplier_worker.display_name, ''), supplier_worker.username) AS live_worker_name,
|
||||
supplier_worker.phone AS live_worker_phone,
|
||||
ccs.name AS contact_supporter_name
|
||||
FROM consignment_inventories ci
|
||||
LEFT JOIN consignment_submissions cs ON cs.id = ci.source_submission_id
|
||||
LEFT JOIN worker_users supplier_worker ON supplier_worker.id = ci.worker_id
|
||||
LEFT JOIN consignment_contact_supporters ccs ON ccs.id = ci.contact_supporter_id
|
||||
WHERE ${conditions.join(' AND ')}
|
||||
ORDER BY ci.id ASC
|
||||
@@ -967,12 +986,22 @@ 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} OR ccs.name 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 cs.worker_name ILIKE $${values.length}
|
||||
OR cs.worker_phone ILIKE $${values.length}
|
||||
OR supplier_worker.display_name ILIKE $${values.length}
|
||||
OR supplier_worker.username ILIKE $${values.length}
|
||||
OR supplier_worker.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 LEFT JOIN consignment_contact_supporters ccs ON ccs.id = cs.contact_supporter_id ${whereClause}`
|
||||
const countSql = `SELECT COUNT(*)::integer AS total FROM consignment_submissions cs LEFT JOIN worker_users supplier_worker ON supplier_worker.id = cs.worker_id 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
|
||||
|
||||
@@ -989,9 +1018,12 @@ export async function listConsignmentSubmissions(filters: {
|
||||
wo.acceptance_json AS gift_evidence_json,
|
||||
wo.work_order_no AS gift_work_order_no,
|
||||
wo.status AS gift_work_order_status,
|
||||
COALESCE(NULLIF(supplier_worker.display_name, ''), supplier_worker.username) AS live_worker_name,
|
||||
supplier_worker.phone AS live_worker_phone,
|
||||
ccs.name AS contact_supporter_name
|
||||
FROM consignment_submissions cs
|
||||
LEFT JOIN work_categories wc ON wc.id = cs.category_id
|
||||
LEFT JOIN worker_users supplier_worker ON supplier_worker.id = cs.worker_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
|
||||
@@ -1209,9 +1241,12 @@ export async function listConsignmentSubmissionsByIds(
|
||||
wo.acceptance_json AS gift_evidence_json,
|
||||
wo.work_order_no AS gift_work_order_no,
|
||||
wo.status AS gift_work_order_status,
|
||||
COALESCE(NULLIF(supplier_worker.display_name, ''), supplier_worker.username) AS live_worker_name,
|
||||
supplier_worker.phone AS live_worker_phone,
|
||||
ccs.name AS contact_supporter_name
|
||||
FROM consignment_submissions cs
|
||||
LEFT JOIN work_categories wc ON wc.id = cs.category_id
|
||||
LEFT JOIN worker_users supplier_worker ON supplier_worker.id = cs.worker_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)
|
||||
@@ -1238,10 +1273,13 @@ export async function getConsignmentInventoryBySubmissionId(
|
||||
ci.*,
|
||||
wc.name AS category_name,
|
||||
cs.submission_no AS source_submission_no,
|
||||
COALESCE(NULLIF(supplier_worker.display_name, ''), supplier_worker.username) AS live_worker_name,
|
||||
supplier_worker.phone AS live_worker_phone,
|
||||
ccs.name AS contact_supporter_name
|
||||
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 worker_users supplier_worker ON supplier_worker.id = ci.worker_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
|
||||
@@ -1262,9 +1300,12 @@ export async function getConsignmentSubmissionById(
|
||||
wo.acceptance_json AS gift_evidence_json,
|
||||
wo.work_order_no AS gift_work_order_no,
|
||||
wo.status AS gift_work_order_status,
|
||||
COALESCE(NULLIF(supplier_worker.display_name, ''), supplier_worker.username) AS live_worker_name,
|
||||
supplier_worker.phone AS live_worker_phone,
|
||||
ccs.name AS contact_supporter_name
|
||||
FROM consignment_submissions cs
|
||||
LEFT JOIN work_categories wc ON wc.id = cs.category_id
|
||||
LEFT JOIN worker_users supplier_worker ON supplier_worker.id = cs.worker_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
|
||||
|
||||
@@ -514,6 +514,14 @@ export type ListInput = {
|
||||
/** 后台打手列表按提现黑名单筛选。 */
|
||||
isWithdrawalBlacklisted?: boolean | null | undefined
|
||||
workerSharingId?: number
|
||||
/**
|
||||
* 后台「接单人员」筛选的扩展口径:同时把该打手作为寄售供货人的工单纳入可见集合。
|
||||
*
|
||||
* 内部寄售直接履约不写 assigned_worker_id,供货关系只存在
|
||||
* consignment_inventories.worker_id;仅靠 workerSharingId 会漏掉这些单。
|
||||
* 该字段只由后台筛选使用,打手端「我的订单」不传,避免改变打手可见范围。
|
||||
*/
|
||||
consignmentSupplierWorkerId?: number
|
||||
/** 大厅候选集合的总展示上限;筛选与分页在候选集合内执行。 */
|
||||
hallCandidateLimit?: number
|
||||
hallCategoryLimits?: Record<string, number>
|
||||
|
||||
@@ -391,6 +391,7 @@ export async function listWorkOrders({
|
||||
workerLevelIds = [],
|
||||
categoryId = 0,
|
||||
workerSharingId = 0,
|
||||
consignmentSupplierWorkerId = 0,
|
||||
hallCandidateLimit = 0,
|
||||
hallCategoryLimits = {},
|
||||
visibleAfterIso = '',
|
||||
@@ -416,6 +417,7 @@ export async function listWorkOrders({
|
||||
...(resolvedWorkerIds ? { workerIds: resolvedWorkerIds } : {}),
|
||||
categoryId,
|
||||
workerSharingId,
|
||||
consignmentSupplierWorkerId,
|
||||
hallCandidateLimit,
|
||||
hallCategoryLimits,
|
||||
visibleAfterIso,
|
||||
@@ -1205,6 +1207,7 @@ export function buildWorkOrderWhere({
|
||||
workerIds,
|
||||
categoryId = 0,
|
||||
workerSharingId = 0,
|
||||
consignmentSupplierWorkerId = 0,
|
||||
hallCandidateLimit = 0,
|
||||
hallCategoryLimits = {},
|
||||
visibleAfterIso = '',
|
||||
@@ -1234,6 +1237,7 @@ export function buildWorkOrderWhere({
|
||||
)
|
||||
}
|
||||
let workerSharePlaceholder = ''
|
||||
let consignmentSupplierUnion = ''
|
||||
|
||||
// 旧的 excludeFilledSharing 写法:LEFT JOIN 一份「整张 work_order_shares 的聚合」
|
||||
// (GROUP BY work_order_id)再在 WHERE 里比较。列表/计数每次调用都要重算这份全局聚合,
|
||||
@@ -1245,6 +1249,22 @@ export function buildWorkOrderWhere({
|
||||
if (workerSharingId) {
|
||||
params.push(workerSharingId)
|
||||
workerSharePlaceholder = `$${params.length}`
|
||||
const normalizedSupplierWorkerId = Math.max(
|
||||
0,
|
||||
Math.floor(Number(consignmentSupplierWorkerId) || 0),
|
||||
)
|
||||
if (normalizedSupplierWorkerId > 0) {
|
||||
// 内部寄售直接履约不写 assigned_worker_id:供货关系只存在
|
||||
// consignment_inventories.worker_id,这里按库存回查绑定工单,补进可见集合。
|
||||
params.push(normalizedSupplierWorkerId)
|
||||
const supplierPlaceholder = `$${params.length}`
|
||||
consignmentSupplierUnion = `
|
||||
UNION
|
||||
SELECT supplier_ci.bound_work_order_id AS work_order_id
|
||||
FROM consignment_inventories supplier_ci
|
||||
WHERE supplier_ci.worker_id = ${supplierPlaceholder}::bigint
|
||||
AND supplier_ci.bound_work_order_id IS NOT NULL`
|
||||
}
|
||||
joins.push(`
|
||||
INNER JOIN (
|
||||
SELECT assigned.id AS work_order_id
|
||||
@@ -1263,7 +1283,7 @@ export function buildWorkOrderWhere({
|
||||
SELECT timeout_ev.work_order_id
|
||||
FROM work_order_events timeout_ev
|
||||
WHERE timeout_ev.event_type LIKE 'timeout_%'
|
||||
AND timeout_ev.payload_json->>'workerId' = ${workerSharePlaceholder}::text
|
||||
AND timeout_ev.payload_json->>'workerId' = ${workerSharePlaceholder}::text${consignmentSupplierUnion}
|
||||
) worker_visible ON worker_visible.work_order_id = wo.id
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT wos.status, wos.created_at
|
||||
|
||||
@@ -59,6 +59,7 @@ function buildWorkOrderStatisticsCacheKey(filters: JsonObject): string {
|
||||
filters.keyword || '',
|
||||
filters.shopNames || [],
|
||||
filters.workerSharingId || 0,
|
||||
filters.consignmentSupplierWorkerId || 0,
|
||||
filters.workerId || 0,
|
||||
filters.workerLevelIds || [],
|
||||
filters.categoryId || 0,
|
||||
@@ -149,6 +150,9 @@ export function resolveAdminWorkOrderListFilters(query: JsonObject = {}): ListIn
|
||||
keyword,
|
||||
shopNames,
|
||||
workerSharingId: workerId,
|
||||
// 「接单人员」筛选同时覆盖寄售供货人:内部寄售直接履约不写 assigned_worker_id,
|
||||
// 供货关系只存在 consignment_inventories.worker_id,必须补进可见集合。
|
||||
...(workerId > 0 ? { consignmentSupplierWorkerId: workerId } : {}),
|
||||
workerLevelIds,
|
||||
vipEvidencePending,
|
||||
...(acceptanceMode ? { acceptanceMode } : {}),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { normalizeInventoryDate } from './consignment-admin-service.js'
|
||||
import { mapConsignmentInventory, normalizeInventoryDate } from './consignment-admin-service.js'
|
||||
|
||||
test('寄售仓库入库日期筛选只接受合法的 YYYY-MM-DD', () => {
|
||||
assert.equal(normalizeInventoryDate('2026-11-05'), '2026-11-05')
|
||||
@@ -10,3 +10,61 @@ test('寄售仓库入库日期筛选只接受合法的 YYYY-MM-DD', () => {
|
||||
assert.equal(normalizeInventoryDate('2026/11/05'), undefined)
|
||||
assert.equal(normalizeInventoryDate(''), undefined)
|
||||
})
|
||||
|
||||
test('寄售库存改名后优先显示实时打手昵称,打手被删才回退快照', () => {
|
||||
const base = {
|
||||
id: 1,
|
||||
inventory_no: 'INV1',
|
||||
game_name: '和平精英',
|
||||
category_id: null,
|
||||
item_name: '测试物品',
|
||||
system: '',
|
||||
server_zone: '',
|
||||
series: '',
|
||||
diamond_level: 1,
|
||||
brilliance_count: 0,
|
||||
cost_price_fen: 0,
|
||||
buyer_name: '',
|
||||
inbound_batch: '',
|
||||
seller_contact: '',
|
||||
seller_game_id: '',
|
||||
seller_payment_info: '',
|
||||
seller_phone: '',
|
||||
credentials_json: {},
|
||||
screenshots_json: [],
|
||||
remark: '',
|
||||
allow_matching: true,
|
||||
status: 'in_stock',
|
||||
allocation_status: '待安排',
|
||||
bound_order_id: null,
|
||||
bound_work_order_id: null,
|
||||
bound_platform_order_id: '',
|
||||
shop_name: '',
|
||||
source_type: 'manual',
|
||||
source_submission_id: null,
|
||||
inbound_at: '2026-11-01T00:00:00.000Z',
|
||||
consumed_at: null,
|
||||
created_at: '2026-11-01T00:00:00.000Z',
|
||||
updated_at: '2026-11-01T00:00:00.000Z',
|
||||
worker_id: 7,
|
||||
worker_name: '旧昵称',
|
||||
worker_phone: '111',
|
||||
}
|
||||
|
||||
const renamed = mapConsignmentInventory({
|
||||
...base,
|
||||
live_worker_name: '新昵称',
|
||||
live_worker_phone: '222',
|
||||
} as never)
|
||||
assert.equal(renamed.workerName, '新昵称')
|
||||
assert.equal(renamed.workerPhone, '222')
|
||||
|
||||
// 打手被删除时 join 不到实时行,仍要能显示登记时快照
|
||||
const deleted = mapConsignmentInventory({
|
||||
...base,
|
||||
live_worker_name: null,
|
||||
live_worker_phone: null,
|
||||
} as never)
|
||||
assert.equal(deleted.workerName, '旧昵称')
|
||||
assert.equal(deleted.workerPhone, '111')
|
||||
})
|
||||
|
||||
@@ -222,8 +222,9 @@ export function mapConsignmentInventory(row: ConsignmentInventoryRow) {
|
||||
})
|
||||
: null,
|
||||
workerId: row.worker_id ? Number(row.worker_id) : null,
|
||||
workerName: row.worker_name || '',
|
||||
workerPhone: row.worker_phone || '',
|
||||
// 改名后以当前昵称为准;快照只用于打手被删除等无法实时关联的兜底。
|
||||
workerName: row.live_worker_name || row.worker_name || '',
|
||||
workerPhone: row.live_worker_phone || 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,
|
||||
@@ -265,8 +266,9 @@ export function mapConsignmentSubmission(row: ConsignmentSubmissionRow) {
|
||||
),
|
||||
askingPriceFen: Number(row.asking_price_fen || 0),
|
||||
workerId: row.worker_id ? Number(row.worker_id) : null,
|
||||
workerName: row.worker_name || '',
|
||||
workerPhone: row.worker_phone || '',
|
||||
// 改名后以当前昵称为准;快照只用于打手被删除等无法实时关联的兜底。
|
||||
workerName: row.live_worker_name || row.worker_name || '',
|
||||
workerPhone: row.live_worker_phone || 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 || '',
|
||||
|
||||
@@ -41,6 +41,7 @@ test('后台:显式单号检索时忽略状态等排他筛选,只按单号
|
||||
assert.equal(filters.vipEvidencePending, false)
|
||||
// 接单人员:目标单可能根本不在这名打手的可见集合里
|
||||
assert.equal(filters.workerSharingId, 0)
|
||||
assert.equal(filters.consignmentSupplierWorkerId, undefined)
|
||||
// 分类 / 等级 / 验收模式 / 赠送阶段 / 创建时间 / 定位工单
|
||||
assert.equal(filters.categoryId, 0)
|
||||
assert.deepEqual(filters.workerLevelIds, [])
|
||||
@@ -77,6 +78,7 @@ test('后台:普通关键词(商品名/昵称)仍按原样下推全部筛
|
||||
assert.deepEqual(filters.statuses, ['open', 'in_progress'])
|
||||
assert.equal(filters.vipEvidencePending, true)
|
||||
assert.equal(filters.workerSharingId, 7)
|
||||
assert.equal(filters.consignmentSupplierWorkerId, 7)
|
||||
assert.equal(filters.categoryId, 12)
|
||||
assert.deepEqual(filters.workerLevelIds, [1, 2])
|
||||
assert.equal(filters.acceptanceMode, 'friend_gift')
|
||||
|
||||
@@ -4,6 +4,7 @@ import test from 'node:test'
|
||||
import type { WorkOrderShareRow } from '../../repositories/worker-platform/types.js'
|
||||
import {
|
||||
isWorkerOrderVipEvidencePending,
|
||||
mapWorkOrderMaterialWithConsignmentWorker,
|
||||
pickWorkerRepresentativeShare,
|
||||
resolveWorkerOrderStatusTabs,
|
||||
stripWorkerPersonalShareFields,
|
||||
@@ -264,3 +265,36 @@ test('大厅广播快照必须剔除打手私有份额字段', () => {
|
||||
assert.deepEqual(snapshot.sharingProgress, { joinedQuantity: 2, pendingSubmissionCount: 1 })
|
||||
assert.equal(snapshot.workOrderId, 1)
|
||||
})
|
||||
|
||||
test('寄售供货人改名后,工单 material 展示使用库存实时昵称', () => {
|
||||
const material = {
|
||||
consignment: {
|
||||
inventoryId: 9,
|
||||
workerName: '旧昵称',
|
||||
workerPhone: '111',
|
||||
itemName: '测试物品',
|
||||
},
|
||||
}
|
||||
|
||||
const renamed = mapWorkOrderMaterialWithConsignmentWorker(material, {
|
||||
live_worker_name: '新昵称',
|
||||
live_worker_phone: '222',
|
||||
} as never) as { consignment: { workerName: string; workerPhone: string; itemName: string } }
|
||||
|
||||
assert.equal(renamed.consignment.workerName, '新昵称')
|
||||
assert.equal(renamed.consignment.workerPhone, '222')
|
||||
// 其它寄售字段不能被覆盖
|
||||
assert.equal(renamed.consignment.itemName, '测试物品')
|
||||
|
||||
const fallback = mapWorkOrderMaterialWithConsignmentWorker(material, null) as {
|
||||
consignment: { workerName: string }
|
||||
}
|
||||
assert.equal(fallback.consignment.workerName, '旧昵称')
|
||||
|
||||
// 历史上没有登记供货人的内部现货单,不能因为库存关联到打手就改标成「供货打手」
|
||||
const noSnapshot = mapWorkOrderMaterialWithConsignmentWorker(
|
||||
{ consignment: { inventoryId: 9, workerName: '' } },
|
||||
{ live_worker_name: '新昵称', live_worker_phone: '222' } as never,
|
||||
) as { consignment: { workerName: string } }
|
||||
assert.equal(noSnapshot.consignment.workerName, '')
|
||||
})
|
||||
|
||||
@@ -68,6 +68,40 @@ export function mapWorkOrderMaterial(value: unknown) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 工单 material.consignment 里保存的是入库/履约时的打手昵称快照。
|
||||
* 打手改名后,后台列表仍要显示当前昵称;有实时库存行时用它覆盖展示字段,
|
||||
* 历史 JSON 本身不改写(保留登记时痕迹)。
|
||||
*/
|
||||
export function mapWorkOrderMaterialWithConsignmentWorker(
|
||||
value: unknown,
|
||||
consignmentInventory?: ConsignmentInventoryRow | null,
|
||||
) {
|
||||
const material = mapWorkOrderMaterial(value) as JsonObject
|
||||
const liveWorkerName = String(
|
||||
consignmentInventory?.live_worker_name || consignmentInventory?.worker_name || '',
|
||||
).trim()
|
||||
if (!liveWorkerName) return material
|
||||
const consignment = material.consignment
|
||||
if (!consignment || typeof consignment !== 'object' || Array.isArray(consignment)) {
|
||||
return material
|
||||
}
|
||||
const consignmentRecord = consignment as JsonObject
|
||||
// 只刷新「原本就登记过供货人」的单据,避免把历史上没有供货人快照的
|
||||
// 内部现货单改标成「供货打手」(那会改变历史单的展示语义)。
|
||||
if (!String(consignmentRecord.workerName || '').trim()) return material
|
||||
return {
|
||||
...material,
|
||||
consignment: {
|
||||
...consignmentRecord,
|
||||
workerName: liveWorkerName,
|
||||
workerPhone: String(
|
||||
consignmentInventory?.live_worker_phone || consignmentInventory?.worker_phone || '',
|
||||
).trim(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/** 读取好友赠送模式的打手本人资料;主页截图访问链接需要读取时重签。 */
|
||||
export function mapWorkOrderBoosterMaterial(value: unknown) {
|
||||
const material = safeParseJson(value)
|
||||
@@ -412,7 +446,10 @@ export function mapWorkOrderAdmin(
|
||||
depositThresholdAmount: Number(
|
||||
workOrder.deposit_threshold_amount || DEFAULT_DEPOSIT_THRESHOLD_AMOUNT,
|
||||
),
|
||||
material: mapWorkOrderMaterial(workOrder.material_json),
|
||||
material: mapWorkOrderMaterialWithConsignmentWorker(
|
||||
workOrder.material_json,
|
||||
consignmentInventory,
|
||||
),
|
||||
requirement: {
|
||||
fields: resolveRequirementFields(workOrder),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user