feat(consignment): 苏尔南寄卖接入打手端押金履约
打手可直接提报寄卖,关联 worker 与履约押金后指派交枪;后台去掉旧收集链接管理,提报审核与库存/工单流程对齐接单平台。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
-- 078_consignment_worker_deposit.sql —— 寄卖库存支持关联接单平台打手账号与押金履约模式
|
||||
|
||||
ALTER TABLE consignment_inventories
|
||||
ADD COLUMN IF NOT EXISTS worker_id BIGINT REFERENCES worker_users(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS worker_name TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS worker_phone TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS deposit_amount_fen INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE consignment_submissions
|
||||
ADD COLUMN IF NOT EXISTS worker_id BIGINT REFERENCES worker_users(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS worker_name TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS worker_phone TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN IF NOT EXISTS deposit_amount_fen INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_inventories_worker_id
|
||||
ON consignment_inventories(worker_id)
|
||||
WHERE worker_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_consignment_submissions_worker_id
|
||||
ON consignment_submissions(worker_id)
|
||||
WHERE worker_id IS NOT NULL;
|
||||
|
||||
COMMENT ON COLUMN consignment_inventories.worker_id IS '关联接单平台的打手ID(用于押金冻结与履约指派)';
|
||||
COMMENT ON COLUMN consignment_inventories.deposit_amount_fen IS '该寄卖手枪建议/约定的履约押金金额(分)';
|
||||
COMMENT ON COLUMN consignment_submissions.worker_id IS '提交人或关联的接单平台打手ID';
|
||||
@@ -32,6 +32,10 @@ export type ConsignmentInventoryRow = {
|
||||
shop_name: string
|
||||
source_type: 'manual' | 'collection_link' | 'recycle'
|
||||
source_submission_id: string | number | null
|
||||
worker_id?: string | number | null
|
||||
worker_name?: string
|
||||
worker_phone?: string
|
||||
deposit_amount_fen?: number
|
||||
inbound_at: string
|
||||
consumed_at: string | null
|
||||
created_at: string
|
||||
@@ -83,6 +87,10 @@ export type ConsignmentSubmissionRow = {
|
||||
content_json: string | Record<string, unknown>
|
||||
screenshots_json: string | unknown[]
|
||||
asking_price_fen: number
|
||||
worker_id?: string | number | null
|
||||
worker_name?: string
|
||||
worker_phone?: string
|
||||
deposit_amount_fen?: number
|
||||
status: 'pending_review' | 'approved' | 'rejected'
|
||||
review_note: string
|
||||
reviewer_name: string
|
||||
@@ -121,6 +129,7 @@ export async function listConsignmentInventories(filters: {
|
||||
series?: string | undefined
|
||||
diamondLevel?: number | undefined
|
||||
allowMatching?: boolean | undefined
|
||||
workerId?: number | undefined
|
||||
keyword?: string | undefined
|
||||
page?: number | undefined
|
||||
pageSize?: number | undefined
|
||||
@@ -132,6 +141,10 @@ export async function listConsignmentInventories(filters: {
|
||||
values.push(filters.status)
|
||||
conditions.push(`ci.status = $${values.length}`)
|
||||
}
|
||||
if (filters.workerId) {
|
||||
values.push(filters.workerId)
|
||||
conditions.push(`ci.worker_id = $${values.length}`)
|
||||
}
|
||||
if (filters.buyerName) {
|
||||
values.push(filters.buyerName)
|
||||
conditions.push(`ci.buyer_name = $${values.length}`)
|
||||
@@ -329,6 +342,10 @@ export async function createConsignmentInventory(input: {
|
||||
allowMatching?: boolean
|
||||
sourceType?: 'manual' | 'collection_link' | 'recycle'
|
||||
sourceSubmissionId?: number | null
|
||||
workerId?: number | null
|
||||
workerName?: string
|
||||
workerPhone?: string
|
||||
depositAmountFen?: number
|
||||
now: string
|
||||
}): Promise<ConsignmentInventoryRow> {
|
||||
const sql = `
|
||||
@@ -338,14 +355,16 @@ export async function createConsignmentInventory(input: {
|
||||
inbound_batch, seller_contact, seller_game_id, seller_payment_info,
|
||||
seller_phone, credentials_json, screenshots_json, remark,
|
||||
allow_matching, status, allocation_status, source_type,
|
||||
source_submission_id, inbound_at, created_at, updated_at
|
||||
source_submission_id, worker_id, worker_name, worker_phone, deposit_amount_fen,
|
||||
inbound_at, created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$7, $8, $9, $10, $11,
|
||||
$12, $13, $14, $15,
|
||||
$16, $17::jsonb, $18::jsonb, $19,
|
||||
$20, 'in_stock', '待安排', $21,
|
||||
$22, $23, $23, $23
|
||||
$22, $23, $24, $25, $26,
|
||||
$27, $27, $27
|
||||
)
|
||||
RETURNING *
|
||||
`
|
||||
@@ -372,6 +391,10 @@ export async function createConsignmentInventory(input: {
|
||||
input.allowMatching !== false,
|
||||
input.sourceType || 'manual',
|
||||
input.sourceSubmissionId || null,
|
||||
input.workerId || null,
|
||||
input.workerName || '',
|
||||
input.workerPhone || '',
|
||||
input.depositAmountFen || 0,
|
||||
input.now,
|
||||
]
|
||||
const res = await query<ConsignmentInventoryRow>(sql, values)
|
||||
@@ -400,6 +423,10 @@ export async function updateConsignmentInventory(
|
||||
allowMatching?: boolean | undefined
|
||||
status?: 'in_stock' | 'consumed' | 'archived' | undefined
|
||||
allocationStatus?: string | undefined
|
||||
workerId?: number | null | undefined
|
||||
workerName?: string | undefined
|
||||
workerPhone?: string | undefined
|
||||
depositAmountFen?: number | undefined
|
||||
now: string
|
||||
},
|
||||
): Promise<ConsignmentInventoryRow | null> {
|
||||
@@ -482,6 +509,22 @@ export async function updateConsignmentInventory(
|
||||
values.push(input.allocationStatus)
|
||||
updates.push(`allocation_status = $${values.length}`)
|
||||
}
|
||||
if (input.workerId !== undefined) {
|
||||
values.push(input.workerId)
|
||||
updates.push(`worker_id = $${values.length}`)
|
||||
}
|
||||
if (input.workerName !== undefined) {
|
||||
values.push(input.workerName)
|
||||
updates.push(`worker_name = $${values.length}`)
|
||||
}
|
||||
if (input.workerPhone !== undefined) {
|
||||
values.push(input.workerPhone)
|
||||
updates.push(`worker_phone = $${values.length}`)
|
||||
}
|
||||
if (input.depositAmountFen !== undefined) {
|
||||
values.push(input.depositAmountFen)
|
||||
updates.push(`deposit_amount_fen = $${values.length}`)
|
||||
}
|
||||
|
||||
if (updates.length === 0) return getConsignmentInventoryById(id)
|
||||
|
||||
@@ -681,6 +724,7 @@ export async function updateConsignmentCollectLink(
|
||||
export async function listConsignmentSubmissions(filters: {
|
||||
status?: string | undefined
|
||||
linkId?: number | undefined
|
||||
workerId?: number | undefined
|
||||
keyword?: string | undefined
|
||||
page?: number | undefined
|
||||
pageSize?: number | undefined
|
||||
@@ -696,6 +740,10 @@ export async function listConsignmentSubmissions(filters: {
|
||||
values.push(filters.linkId)
|
||||
conditions.push(`cs.link_id = $${values.length}`)
|
||||
}
|
||||
if (filters.workerId) {
|
||||
values.push(filters.workerId)
|
||||
conditions.push(`cs.worker_id = $${values.length}`)
|
||||
}
|
||||
if (filters.keyword) {
|
||||
values.push(`%${filters.keyword.trim()}%`)
|
||||
conditions.push(
|
||||
@@ -765,6 +813,10 @@ export async function createConsignmentSubmission(input: {
|
||||
contentJson?: Record<string, unknown>
|
||||
screenshotsJson?: unknown[]
|
||||
askingPriceFen?: number
|
||||
workerId?: number | null
|
||||
workerName?: string
|
||||
workerPhone?: string
|
||||
depositAmountFen?: number
|
||||
now: string
|
||||
}): Promise<ConsignmentSubmissionRow> {
|
||||
const sql = `
|
||||
@@ -773,13 +825,15 @@ export async function createConsignmentSubmission(input: {
|
||||
system, server_zone, series, diamond_level, brilliance_count,
|
||||
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
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5,
|
||||
$6, $7, $8, $9, $10,
|
||||
$11, $12, $13, $14,
|
||||
$15::jsonb, $16::jsonb, $17,
|
||||
'pending_review', $18, $18
|
||||
$18, $19, $20, $21,
|
||||
'pending_review', $22, $22
|
||||
)
|
||||
RETURNING *
|
||||
`
|
||||
@@ -801,6 +855,10 @@ export async function createConsignmentSubmission(input: {
|
||||
toJsonString(input.contentJson || {}),
|
||||
toJsonString(input.screenshotsJson || []),
|
||||
input.askingPriceFen || 0,
|
||||
input.workerId || null,
|
||||
input.workerName || '',
|
||||
input.workerPhone || '',
|
||||
input.depositAmountFen || 0,
|
||||
input.now,
|
||||
])
|
||||
return res.rows[0]!
|
||||
@@ -852,6 +910,10 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
credentialsJson?: Record<string, unknown> | undefined
|
||||
screenshotsJson?: unknown[] | undefined
|
||||
reviewNote?: string | undefined
|
||||
workerId?: number | null | undefined
|
||||
workerName?: string | undefined
|
||||
workerPhone?: string | undefined
|
||||
depositAmountFen?: number | undefined
|
||||
now: string
|
||||
}): Promise<{ submission: ConsignmentSubmissionRow; inventory: ConsignmentInventoryRow }> {
|
||||
return withTransaction(async (client: PoolClient) => {
|
||||
@@ -863,6 +925,16 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
if (!submission) throw new Error('提报单不存在')
|
||||
if (submission.status !== 'pending_review') throw new Error('提报单已被处理')
|
||||
|
||||
const workerId = input.workerId !== undefined ? input.workerId : submission.worker_id || null
|
||||
const workerName =
|
||||
input.workerName !== undefined ? input.workerName : submission.worker_name || ''
|
||||
const workerPhone =
|
||||
input.workerPhone !== undefined ? input.workerPhone : submission.worker_phone || ''
|
||||
const depositAmountFen =
|
||||
input.depositAmountFen !== undefined
|
||||
? input.depositAmountFen
|
||||
: submission.deposit_amount_fen || 0
|
||||
|
||||
const invSql = `
|
||||
INSERT INTO consignment_inventories (
|
||||
inventory_no, game_name, category_id, item_name, system, server_zone,
|
||||
@@ -870,14 +942,16 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
inbound_batch, seller_contact, seller_game_id, seller_payment_info,
|
||||
seller_phone, credentials_json, screenshots_json, remark,
|
||||
allow_matching, status, allocation_status, source_type,
|
||||
source_submission_id, inbound_at, created_at, updated_at
|
||||
source_submission_id, worker_id, worker_name, worker_phone, deposit_amount_fen,
|
||||
inbound_at, created_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$7, $8, $9, $10, $11,
|
||||
$12, $13, $14, $15,
|
||||
$16, $17::jsonb, $18::jsonb, $19,
|
||||
TRUE, 'in_stock', '待安排', 'collection_link',
|
||||
$20, $21, $21, $21
|
||||
$20, $21, $22, $23, $24,
|
||||
$25, $25, $25
|
||||
)
|
||||
RETURNING *
|
||||
`
|
||||
@@ -902,6 +976,10 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
toJsonString(input.screenshotsJson || submission.screenshots_json || []),
|
||||
input.reviewNote || '',
|
||||
submission.id,
|
||||
workerId,
|
||||
workerName,
|
||||
workerPhone,
|
||||
depositAmountFen,
|
||||
input.now,
|
||||
]
|
||||
const invRes = await client.query<ConsignmentInventoryRow>(invSql, invValues)
|
||||
@@ -914,15 +992,23 @@ export async function approveConsignmentSubmissionWithInbound(input: {
|
||||
result_inventory_id = $1,
|
||||
reviewer_name = $2,
|
||||
review_note = $3,
|
||||
reviewed_at = $4,
|
||||
updated_at = $4
|
||||
WHERE id = $5
|
||||
worker_id = COALESCE($4, worker_id),
|
||||
worker_name = COALESCE($5, worker_name),
|
||||
worker_phone = COALESCE($6, worker_phone),
|
||||
deposit_amount_fen = COALESCE($7, deposit_amount_fen),
|
||||
reviewed_at = $8,
|
||||
updated_at = $8
|
||||
WHERE id = $9
|
||||
RETURNING *
|
||||
`
|
||||
const updateSubRes = await client.query<ConsignmentSubmissionRow>(updateSubSql, [
|
||||
inventory.id,
|
||||
input.buyerName,
|
||||
input.reviewNote || '',
|
||||
workerId,
|
||||
workerName,
|
||||
workerPhone,
|
||||
depositAmountFen,
|
||||
input.now,
|
||||
submission.id,
|
||||
])
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
updateAdminConsignmentLink,
|
||||
} from '../../../services/worker-platform/consignment-admin-service.js'
|
||||
import {
|
||||
assignWorkOrderWithConsignmentWorker,
|
||||
checkWorkOrderInventoryMatches,
|
||||
fulfillWorkOrderWithConsignment,
|
||||
revokeWorkOrderConsignmentFulfillment,
|
||||
@@ -253,6 +254,25 @@ router.post(
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/consignment/assign-worker',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
assignWorkOrderWithConsignmentWorker({
|
||||
workOrderId: String(req.body?.workOrderId || ''),
|
||||
inventoryId: String(req.body?.inventoryId || ''),
|
||||
workerId: req.body?.workerId ? Number(req.body.workerId) : null,
|
||||
actorName: req.adminSession?.username || 'admin',
|
||||
}),
|
||||
{
|
||||
successMessage: '已指派打手,押金已冻结,工单流转进行中!',
|
||||
errorMessage: '指派打手履约失败',
|
||||
scope: '[admin/consignment/assign-worker]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/consignment/revoke',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
|
||||
@@ -5,6 +5,7 @@ import workerAuthSessionRoutes from './worker/auth-session.js'
|
||||
import workerProfileRoutes from './worker/profile.js'
|
||||
import workerHallRoutes from './worker/hall.js'
|
||||
import workerOrdersRoutes from './worker/orders.js'
|
||||
import workerConsignmentRoutes from './worker/consignment.js'
|
||||
import { requireWorkerSession } from './worker/session.js'
|
||||
import { buildNotFoundPayload } from '../utils/http.js'
|
||||
|
||||
@@ -22,6 +23,8 @@ router.use(workerHallRoutes)
|
||||
|
||||
router.use(workerOrdersRoutes)
|
||||
|
||||
router.use(workerConsignmentRoutes)
|
||||
|
||||
router.use((req, res) => {
|
||||
res.status(404).json(buildNotFoundPayload(req))
|
||||
})
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Router } from 'express'
|
||||
|
||||
import {
|
||||
listWorkerConsignments,
|
||||
publishWorkerConsignment,
|
||||
} from '../../services/worker-platform/consignment-worker-service.js'
|
||||
import { sendRouteError } from '../../utils/http.js'
|
||||
import { getRequiredWorkerSession } from './session.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
/**
|
||||
* POST /api/v1/worker/consignment/publish
|
||||
* 打手在接单平台直接提报苏尔南手枪寄卖
|
||||
*/
|
||||
router.post('/consignment/publish', async (req, res) => {
|
||||
try {
|
||||
const session = getRequiredWorkerSession(req)
|
||||
const result = await publishWorkerConsignment(session.workerId, req.body || {})
|
||||
res.json({ ok: true, data: result })
|
||||
} catch (error) {
|
||||
sendRouteError(res, error, '提交寄卖失败', '[worker/consignment/publish]')
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* GET /api/v1/worker/consignment/my-items
|
||||
* 打手查看自己在接单平台寄卖的手枪列表与状态
|
||||
*/
|
||||
router.get('/consignment/my-items', async (req, res) => {
|
||||
try {
|
||||
const session = getRequiredWorkerSession(req)
|
||||
const result = await listWorkerConsignments(session.workerId)
|
||||
res.json({ ok: true, data: result })
|
||||
} catch (error) {
|
||||
sendRouteError(res, error, '获取我的寄卖列表失败', '[worker/consignment/my-items]')
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -16,6 +16,10 @@ import {
|
||||
updateConsignmentCollectLink,
|
||||
updateConsignmentInventory,
|
||||
} from '../../repositories/worker-platform/consignment-repo.js'
|
||||
import {
|
||||
getWorkerUserById,
|
||||
getWorkerUserByPhone,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { generateConsignmentInventoryNo, randomToken } from '../../utils/random.js'
|
||||
@@ -60,6 +64,10 @@ export function mapConsignmentInventory(row: ConsignmentInventoryRow) {
|
||||
boundPlatformOrderId: row.bound_platform_order_id || '',
|
||||
sourceType: row.source_type,
|
||||
sourceSubmissionId: row.source_submission_id ? Number(row.source_submission_id) : null,
|
||||
workerId: row.worker_id ? Number(row.worker_id) : null,
|
||||
workerName: row.worker_name || '',
|
||||
workerPhone: row.worker_phone || '',
|
||||
depositAmountFen: Number(row.deposit_amount_fen || 0),
|
||||
inboundAt: row.inbound_at || row.created_at,
|
||||
consumedAt: row.consumed_at,
|
||||
createdAt: row.created_at,
|
||||
@@ -126,6 +134,10 @@ export function mapConsignmentSubmission(row: ConsignmentSubmissionRow) {
|
||||
? (safeParseJson(row.screenshots_json) as unknown[])
|
||||
: [],
|
||||
askingPriceFen: Number(row.asking_price_fen || 0),
|
||||
workerId: row.worker_id ? Number(row.worker_id) : null,
|
||||
workerName: row.worker_name || '',
|
||||
workerPhone: row.worker_phone || '',
|
||||
depositAmountFen: Number(row.deposit_amount_fen || 0),
|
||||
status: row.status,
|
||||
reviewNote: row.review_note || '',
|
||||
reviewerName: row.reviewer_name || '',
|
||||
@@ -204,8 +216,8 @@ export async function createAdminConsignmentInventory(payload: JsonObject, actor
|
||||
system: String(payload.system || '').trim(),
|
||||
serverZone: String(payload.serverZone || '').trim(),
|
||||
series: String(payload.series || '').trim(),
|
||||
diamondLevel: Math.max(1, Math.min(6, Number(payload.diamondLevel || 1))),
|
||||
brillianceCount: Math.max(0, Math.min(6, Number(payload.brillianceCount || 0))),
|
||||
diamondLevel: Math.max(1, Math.min(7, Number(payload.diamondLevel || 1))),
|
||||
brillianceCount: Math.max(0, Math.min(7, Number(payload.brillianceCount || 0))),
|
||||
costPriceFen,
|
||||
buyerName: String(payload.buyerName || actorName || '').trim(),
|
||||
inboundBatch: String(payload.inboundBatch || '').trim(),
|
||||
@@ -221,6 +233,10 @@ export async function createAdminConsignmentInventory(payload: JsonObject, actor
|
||||
remark: String(payload.remark || '').trim(),
|
||||
allowMatching: payload.allowMatching !== false,
|
||||
sourceType: 'manual',
|
||||
workerId: payload.workerId ? Number(payload.workerId) : null,
|
||||
workerName: String(payload.workerName || '').trim(),
|
||||
workerPhone: String(payload.workerPhone || '').trim(),
|
||||
depositAmountFen: payload.depositAmountFen ? Number(payload.depositAmountFen) : 0,
|
||||
now,
|
||||
})
|
||||
|
||||
@@ -238,9 +254,14 @@ export async function updateAdminConsignmentInventory(
|
||||
system: payload.system !== undefined ? String(payload.system).trim() : undefined,
|
||||
serverZone: payload.serverZone !== undefined ? String(payload.serverZone).trim() : undefined,
|
||||
series: payload.series !== undefined ? String(payload.series).trim() : undefined,
|
||||
diamondLevel: payload.diamondLevel !== undefined ? Number(payload.diamondLevel) : undefined,
|
||||
diamondLevel:
|
||||
payload.diamondLevel !== undefined
|
||||
? Math.max(1, Math.min(7, Number(payload.diamondLevel)))
|
||||
: undefined,
|
||||
brillianceCount:
|
||||
payload.brillianceCount !== undefined ? Number(payload.brillianceCount) : undefined,
|
||||
payload.brillianceCount !== undefined
|
||||
? Math.max(0, Math.min(7, Number(payload.brillianceCount)))
|
||||
: undefined,
|
||||
costPriceFen:
|
||||
payload.costPriceFen !== undefined
|
||||
? Math.max(0, Math.round(Number(payload.costPriceFen)))
|
||||
@@ -265,6 +286,16 @@ export async function updateAdminConsignmentInventory(
|
||||
remark: payload.remark !== undefined ? String(payload.remark).trim() : undefined,
|
||||
allowMatching: payload.allowMatching !== undefined ? Boolean(payload.allowMatching) : undefined,
|
||||
status: payload.status !== undefined ? (payload.status as any) : undefined,
|
||||
workerId:
|
||||
payload.workerId !== undefined
|
||||
? payload.workerId
|
||||
? Number(payload.workerId)
|
||||
: null
|
||||
: undefined,
|
||||
workerName: payload.workerName !== undefined ? String(payload.workerName).trim() : undefined,
|
||||
workerPhone: payload.workerPhone !== undefined ? String(payload.workerPhone).trim() : undefined,
|
||||
depositAmountFen:
|
||||
payload.depositAmountFen !== undefined ? Number(payload.depositAmountFen) : undefined,
|
||||
now,
|
||||
})
|
||||
|
||||
@@ -428,6 +459,29 @@ export async function approveAdminConsignmentSubmission(
|
||||
Math.round(Number(payload.costPriceFen ?? submission.asking_price_fen ?? 0)),
|
||||
)
|
||||
|
||||
let workerId = payload.workerId
|
||||
? Number(payload.workerId)
|
||||
: submission.worker_id
|
||||
? Number(submission.worker_id)
|
||||
: null
|
||||
let workerName = String(payload.workerName || submission.worker_name || '').trim()
|
||||
let workerPhone = String(payload.workerPhone || submission.worker_phone || '').trim()
|
||||
|
||||
const phoneToLookup = String(payload.sellerPhone || submission.seller_phone || '').trim()
|
||||
if (!workerId && phoneToLookup) {
|
||||
const matchedWorker = await getWorkerUserByPhone(phoneToLookup)
|
||||
if (matchedWorker) {
|
||||
workerId = Number(matchedWorker.id)
|
||||
workerName = matchedWorker.display_name || matchedWorker.username || ''
|
||||
workerPhone = matchedWorker.phone || phoneToLookup
|
||||
}
|
||||
}
|
||||
|
||||
const depositAmountFen =
|
||||
payload.depositAmountFen !== undefined
|
||||
? Number(payload.depositAmountFen)
|
||||
: Number(submission.deposit_amount_fen || 0)
|
||||
|
||||
const res = await approveConsignmentSubmissionWithInbound({
|
||||
submissionId: id,
|
||||
inventoryNo,
|
||||
@@ -439,23 +493,27 @@ export async function approveAdminConsignmentSubmission(
|
||||
series: String(payload.series || submission.series).trim(),
|
||||
diamondLevel: Math.max(
|
||||
1,
|
||||
Math.min(6, Number(payload.diamondLevel ?? submission.diamond_level ?? 1)),
|
||||
Math.min(7, Number(payload.diamondLevel ?? submission.diamond_level ?? 1)),
|
||||
),
|
||||
brillianceCount: Math.max(
|
||||
0,
|
||||
Math.min(6, Number(payload.brillianceCount ?? submission.brilliance_count ?? 0)),
|
||||
Math.min(7, Number(payload.brillianceCount ?? submission.brilliance_count ?? 0)),
|
||||
),
|
||||
costPriceFen,
|
||||
sellerContact: String(payload.sellerContact || submission.contact_info).trim(),
|
||||
sellerGameId: String(payload.sellerGameId || submission.seller_game_id).trim(),
|
||||
sellerPaymentInfo: String(payload.sellerPaymentInfo || submission.seller_payment_info).trim(),
|
||||
sellerPhone: String(payload.sellerPhone || submission.seller_phone).trim(),
|
||||
sellerPhone: phoneToLookup,
|
||||
credentialsJson:
|
||||
payload.credentials && typeof payload.credentials === 'object'
|
||||
? (payload.credentials as Record<string, unknown>)
|
||||
: undefined,
|
||||
screenshotsJson: Array.isArray(payload.screenshots) ? payload.screenshots : undefined,
|
||||
reviewNote: String(payload.reviewNote || '').trim(),
|
||||
workerId,
|
||||
workerName,
|
||||
workerPhone,
|
||||
depositAmountFen,
|
||||
now,
|
||||
})
|
||||
|
||||
|
||||
@@ -5,16 +5,19 @@ import {
|
||||
type ConsignmentInventoryRow,
|
||||
getConsignmentInventoryById,
|
||||
listActiveConsignmentInventoriesForMatching,
|
||||
updateConsignmentInventory,
|
||||
} from '../../repositories/worker-platform/consignment-repo.js'
|
||||
import {
|
||||
createWorkOrderEvent,
|
||||
getWorkOrderById,
|
||||
updateWorkOrder,
|
||||
type WorkOrderRow,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { safeParseJson } from '../admin/admin-query-utils.js'
|
||||
import { publishWorkOrderRealtimeChange } from '../realtime/realtime-event-service.js'
|
||||
import { assignAdminWorkOrderToWorker } from './admin-work-order-assignment-service.js'
|
||||
import { mapWorkOrderAdmin } from './mappers.js'
|
||||
import {
|
||||
consumeIndustryVouchersBeforeWorkOrderPublish,
|
||||
@@ -246,6 +249,122 @@ export async function fulfillWorkOrderWithConsignment(input: {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选用内部寄售库存并走接单平台指派打手(冻结押金担保履约)
|
||||
*/
|
||||
export async function assignWorkOrderWithConsignmentWorker(input: {
|
||||
workOrderId: number | string
|
||||
inventoryId: number | string
|
||||
workerId?: number | string | null
|
||||
actorName: string
|
||||
}): Promise<{
|
||||
order: ReturnType<typeof mapWorkOrderAdmin>
|
||||
inventory: ReturnType<typeof mapConsignmentInventory>
|
||||
voucherConsume: WorkOrderVoucherConsumeResult
|
||||
}> {
|
||||
const workOrder = await getWorkOrderById(input.workOrderId)
|
||||
if (!workOrder) {
|
||||
throw createHttpError('工单不存在', { statusCode: 404, errorCode: 'work_order_not_found' })
|
||||
}
|
||||
|
||||
const inventory = await getConsignmentInventoryById(input.inventoryId)
|
||||
if (!inventory) {
|
||||
throw createHttpError('寄卖物品不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'consignment_inventory_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
if (inventory.status !== 'in_stock' || !inventory.allow_matching) {
|
||||
throw createHttpError('该寄卖物品不可用或已被暂停匹配', {
|
||||
statusCode: 409,
|
||||
errorCode: 'consignment_inventory_unavailable',
|
||||
})
|
||||
}
|
||||
|
||||
const targetWorkerId = input.workerId || inventory.worker_id
|
||||
if (!targetWorkerId) {
|
||||
throw createHttpError('该寄卖物品未关联打手,请在指派弹窗中选择打手账号', {
|
||||
statusCode: 400,
|
||||
errorCode: 'consignment_worker_required',
|
||||
})
|
||||
}
|
||||
|
||||
// 1. 调用接单平台统一指派流程(进行打手可用性校验、余额与押金冻结、电子凭证核销、工单状态流转到 in_progress)
|
||||
const assignResult = await assignAdminWorkOrderToWorker(
|
||||
workOrder.id,
|
||||
{ workerId: targetWorkerId },
|
||||
input.actorName,
|
||||
)
|
||||
|
||||
const now = nowIso()
|
||||
const material = safeParseJson(workOrder.material_json)
|
||||
|
||||
// 2. 将寄售手枪信息写入工单 material
|
||||
const updatedMaterial = {
|
||||
...material,
|
||||
consignment: {
|
||||
inventoryId: Number(inventory.id),
|
||||
inventoryNo: inventory.inventory_no,
|
||||
itemName: inventory.item_name,
|
||||
system: inventory.system,
|
||||
serverZone: inventory.server_zone,
|
||||
series: inventory.series,
|
||||
diamondLevel: inventory.diamond_level,
|
||||
brillianceCount: inventory.brilliance_count,
|
||||
costPriceFen: inventory.cost_price_fen,
|
||||
workerId: Number(targetWorkerId),
|
||||
workerName: inventory.worker_name || '',
|
||||
fulfillmentType: 'worker_deposit',
|
||||
assignedAt: now,
|
||||
assignedBy: input.actorName,
|
||||
},
|
||||
}
|
||||
|
||||
const rewardAmountYuan =
|
||||
Number(inventory.cost_price_fen || 0) > 0
|
||||
? Number((Number(inventory.cost_price_fen) / 100).toFixed(2))
|
||||
: Number(workOrder.reward_amount || 0)
|
||||
|
||||
await updateWorkOrder(workOrder.id, {
|
||||
material_json: updatedMaterial,
|
||||
reward_amount: rewardAmountYuan,
|
||||
updated_at: now,
|
||||
})
|
||||
|
||||
// 3. 将寄卖库存置为 consumed 已出库/已安排打手
|
||||
const updatedInventory = await updateConsignmentInventory(inventory.id, {
|
||||
status: 'consumed',
|
||||
allocationStatus: '已指派打手(押金履约中)',
|
||||
workerId: Number(targetWorkerId),
|
||||
now,
|
||||
})
|
||||
|
||||
await createWorkOrderEvent({
|
||||
workOrderId: workOrder.id,
|
||||
actorType: 'admin',
|
||||
actorId: input.actorName,
|
||||
eventType: 'consignment_worker_assigned',
|
||||
fromStatus: workOrder.status,
|
||||
toStatus: WORK_ORDER_STATUS.IN_PROGRESS,
|
||||
payloadJson: JSON.stringify({
|
||||
inventoryId: inventory.id,
|
||||
inventoryNo: inventory.inventory_no,
|
||||
workerId: targetWorkerId,
|
||||
costPriceFen: inventory.cost_price_fen,
|
||||
}),
|
||||
now,
|
||||
})
|
||||
|
||||
const refreshedOrder = await getWorkOrderById(workOrder.id)
|
||||
|
||||
return {
|
||||
order: mapWorkOrderAdmin(refreshedOrder || (assignResult.order as any)),
|
||||
inventory: mapConsignmentInventory(updatedInventory!),
|
||||
voucherConsume: assignResult.voucherConsume,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销寄售履约(用于选错重选、买家退款或物品异常)
|
||||
*/
|
||||
|
||||
@@ -61,6 +61,12 @@ test('extractPeacekeeperSpecs extracts specs accurately', () => {
|
||||
assert.equal(spec2.series, '怒嚎')
|
||||
assert.equal(spec2.diamondLevel, 2)
|
||||
assert.equal(spec2.brillianceCount, 1)
|
||||
|
||||
const spec3 = extractPeacekeeperSpecs('安卓区 血宴 7钻 7璀璨')
|
||||
assert.equal(spec3.system, '安卓')
|
||||
assert.equal(spec3.series, '血宴')
|
||||
assert.equal(spec3.diamondLevel, 7)
|
||||
assert.equal(spec3.brillianceCount, 7)
|
||||
})
|
||||
|
||||
test('matchPeacekeeperOrderWithInventory matches exact spec', () => {
|
||||
|
||||
@@ -62,16 +62,16 @@ export function extractPeacekeeperSpecs(
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 钻级识别 (1~6钻)
|
||||
// 4. 钻级识别 (1~7钻)
|
||||
let diamondLevel = extraMeta?.diamondLevel || 1
|
||||
const diamondMatch = text.match(/([1-6])\s*钻/)
|
||||
const diamondMatch = text.match(/([1-7])\s*钻/)
|
||||
if (diamondMatch?.[1]) {
|
||||
diamondLevel = Number(diamondMatch[1])
|
||||
}
|
||||
|
||||
// 5. 璀璨数量识别 (0~6璀璨)
|
||||
// 5. 璀璨数量识别 (0~7璀璨)
|
||||
let brillianceCount = 0
|
||||
const brillianceMatch = text.match(/([1-6])\s*璀璨/)
|
||||
const brillianceMatch = text.match(/([1-7])\s*璀璨/)
|
||||
if (brillianceMatch?.[1]) {
|
||||
brillianceCount = Number(brillianceMatch[1])
|
||||
} else if (text.includes('璀璨')) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
createConsignmentSubmission,
|
||||
getConsignmentCollectLinkByToken,
|
||||
} from '../../repositories/worker-platform/consignment-repo.js'
|
||||
import { getWorkerUserByPhone } from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { generateConsignmentSubmissionNo } from '../../utils/random.js'
|
||||
@@ -65,19 +66,89 @@ export async function submitPublicConsignment(token: string, payload: JsonObject
|
||||
).trim()
|
||||
const contactInfo = String(payload.contactInfo || payload.sellerContact || '').trim()
|
||||
if (!contactInfo) {
|
||||
throw createHttpError('请填写联系方式(微信号或手机号)', {
|
||||
throw createHttpError('请填写微信号或QQ号以便联系与核实', {
|
||||
statusCode: 400,
|
||||
errorCode: 'contact_info_required',
|
||||
})
|
||||
}
|
||||
|
||||
const contentPayload =
|
||||
payload.contentJson && typeof payload.contentJson === 'object'
|
||||
? (payload.contentJson as Record<string, unknown>)
|
||||
: payload.content && typeof payload.content === 'object'
|
||||
? (payload.content as Record<string, unknown>)
|
||||
: {}
|
||||
|
||||
const characterName = String(
|
||||
contentPayload.characterName || payload.characterName || '',
|
||||
).trim()
|
||||
const characterId = String(contentPayload.characterId || payload.characterId || '').trim()
|
||||
if (!characterName) {
|
||||
throw createHttpError('请填写游戏内角色名', {
|
||||
statusCode: 400,
|
||||
errorCode: 'character_name_required',
|
||||
})
|
||||
}
|
||||
if (!characterId) {
|
||||
throw createHttpError('请填写角色数字ID', {
|
||||
statusCode: 400,
|
||||
errorCode: 'character_id_required',
|
||||
})
|
||||
}
|
||||
|
||||
const screenshots = Array.isArray(payload.screenshotsJson)
|
||||
? payload.screenshotsJson
|
||||
: Array.isArray(payload.screenshots)
|
||||
? payload.screenshots
|
||||
: []
|
||||
if (screenshots.length === 0) {
|
||||
throw createHttpError('请上传游戏主页截图以供核验', {
|
||||
statusCode: 400,
|
||||
errorCode: 'screenshot_required',
|
||||
})
|
||||
}
|
||||
|
||||
const now = nowIso()
|
||||
const submissionNo = generateConsignmentSubmissionNo()
|
||||
const askingPriceFen = Math.max(
|
||||
0,
|
||||
100,
|
||||
Math.round(Number(payload.askingPriceFen || link.preset_price_fen || 0)),
|
||||
)
|
||||
|
||||
const sellerPhone = String(payload.sellerPhone || '').trim()
|
||||
if (!sellerPhone) {
|
||||
throw createHttpError('请填写联系电话', {
|
||||
statusCode: 400,
|
||||
errorCode: 'seller_phone_required',
|
||||
})
|
||||
}
|
||||
|
||||
let workerId: number | null = null
|
||||
let workerName = ''
|
||||
let workerPhone = ''
|
||||
|
||||
if (sellerPhone) {
|
||||
const matchedWorker = await getWorkerUserByPhone(sellerPhone)
|
||||
if (matchedWorker) {
|
||||
workerId = Number(matchedWorker.id)
|
||||
workerName = matchedWorker.display_name || matchedWorker.username || ''
|
||||
workerPhone = matchedWorker.phone || sellerPhone
|
||||
}
|
||||
}
|
||||
|
||||
const diamondLevel = Math.max(
|
||||
1,
|
||||
Math.min(7, Number(payload.diamondLevel || link.diamond_level || 1)),
|
||||
)
|
||||
const brillianceCount = Math.max(
|
||||
0,
|
||||
Math.min(7, Number(payload.brillianceCount ?? link.brilliance_count ?? 0)),
|
||||
)
|
||||
|
||||
const sellerGameId = String(
|
||||
payload.sellerGameId || `${characterName} (${characterId})`,
|
||||
).trim()
|
||||
|
||||
const submission = await createConsignmentSubmission({
|
||||
submissionNo,
|
||||
linkId: Number(link.id),
|
||||
@@ -87,18 +158,28 @@ export async function submitPublicConsignment(token: string, payload: JsonObject
|
||||
system: String(payload.system || link.system || '').trim(),
|
||||
serverZone: String(payload.serverZone || link.server_zone || '').trim(),
|
||||
series: String(payload.series || link.series || '').trim(),
|
||||
diamondLevel: Number(payload.diamondLevel || link.diamond_level || 1),
|
||||
brillianceCount: Number(payload.brillianceCount ?? link.brilliance_count ?? 0),
|
||||
sellerGameId: String(payload.sellerGameId || '').trim(),
|
||||
sellerPaymentInfo: String(payload.sellerPaymentInfo || '').trim(),
|
||||
sellerPhone: String(payload.sellerPhone || '').trim(),
|
||||
diamondLevel,
|
||||
brillianceCount,
|
||||
sellerGameId,
|
||||
sellerPaymentInfo: String(payload.sellerPaymentInfo || '走接单平台钱包结算').trim(),
|
||||
sellerPhone,
|
||||
contactInfo,
|
||||
contentJson:
|
||||
payload.content && typeof payload.content === 'object'
|
||||
? (payload.content as Record<string, unknown>)
|
||||
: (payload as Record<string, unknown>),
|
||||
screenshotsJson: Array.isArray(payload.screenshots) ? payload.screenshots : [],
|
||||
contentJson: {
|
||||
...contentPayload,
|
||||
characterName,
|
||||
characterId,
|
||||
sellerNote: String(contentPayload.sellerNote || payload.sellerNote || '').trim(),
|
||||
channel: 'collect_link',
|
||||
},
|
||||
screenshotsJson: screenshots,
|
||||
askingPriceFen,
|
||||
workerId,
|
||||
workerName,
|
||||
workerPhone,
|
||||
depositAmountFen: Math.max(
|
||||
100,
|
||||
Math.round(Number(payload.depositAmountFen || askingPriceFen || 10000)),
|
||||
),
|
||||
now,
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
createConsignmentSubmission,
|
||||
listConsignmentInventories,
|
||||
listConsignmentSubmissions,
|
||||
} from '../../repositories/worker-platform/consignment-repo.js'
|
||||
import { getWorkerUserById } from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { createHttpError } from '../../utils/http.js'
|
||||
import { generateConsignmentSubmissionNo } from '../../utils/random.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
|
||||
export async function publishWorkerConsignment(workerId: number, payload: JsonObject) {
|
||||
const worker = await getWorkerUserById(workerId)
|
||||
if (!worker) {
|
||||
throw createHttpError('打手账户不存在', { statusCode: 404, errorCode: 'worker_not_found' })
|
||||
}
|
||||
|
||||
const system = String(payload.system || '苹果').trim()
|
||||
const serverZone = String(payload.serverZone || '微信区').trim()
|
||||
const series = String(payload.series || '暗潮').trim()
|
||||
const diamondLevel = Math.max(1, Math.min(7, Number(payload.diamondLevel || 1)))
|
||||
const brillianceCount = Math.max(0, Math.min(7, Number(payload.brillianceCount || 0)))
|
||||
|
||||
const contactInfo = String(payload.sellerContact || payload.contactInfo || '').trim()
|
||||
if (!contactInfo) {
|
||||
throw createHttpError('请填写微信号或QQ号以便联系与核实', {
|
||||
statusCode: 400,
|
||||
errorCode: 'contact_required',
|
||||
})
|
||||
}
|
||||
|
||||
const characterName = String(payload.characterName || '').trim()
|
||||
const characterId = String(payload.characterId || '').trim()
|
||||
if (!characterName) {
|
||||
throw createHttpError('请填写游戏内角色名', {
|
||||
statusCode: 400,
|
||||
errorCode: 'character_name_required',
|
||||
})
|
||||
}
|
||||
if (!characterId) {
|
||||
throw createHttpError('请填写角色数字ID', {
|
||||
statusCode: 400,
|
||||
errorCode: 'character_id_required',
|
||||
})
|
||||
}
|
||||
|
||||
const screenshots = Array.isArray(payload.screenshotsJson) ? payload.screenshotsJson : []
|
||||
if (screenshots.length === 0) {
|
||||
throw createHttpError('请上传游戏主页截图以供核验', {
|
||||
statusCode: 400,
|
||||
errorCode: 'screenshot_required',
|
||||
})
|
||||
}
|
||||
|
||||
const askingPriceFen = Math.max(100, Math.round(Number(payload.askingPriceFen || 0)))
|
||||
if (askingPriceFen < 100) {
|
||||
throw createHttpError('寄售价格不能少于1元', {
|
||||
statusCode: 400,
|
||||
errorCode: 'invalid_asking_price',
|
||||
})
|
||||
}
|
||||
|
||||
const depositAmountFen = Math.max(
|
||||
100,
|
||||
Math.round(Number(payload.depositAmountFen || askingPriceFen || 10000)),
|
||||
)
|
||||
|
||||
const sellerPhone = String(payload.sellerPhone || worker.phone || '').trim()
|
||||
const briStr = brillianceCount > 0 ? `${brillianceCount}璀璨` : ''
|
||||
const defaultItemName = `${system}${serverZone}${series}${diamondLevel}钻${briStr} 苏尔南`
|
||||
const itemName = String(payload.itemName || defaultItemName).trim()
|
||||
|
||||
const submissionNo = generateConsignmentSubmissionNo()
|
||||
const now = nowIso()
|
||||
|
||||
const submission = await createConsignmentSubmission({
|
||||
submissionNo,
|
||||
linkId: null,
|
||||
gameName: '和平精英',
|
||||
categoryId: null,
|
||||
itemName,
|
||||
system,
|
||||
serverZone,
|
||||
series,
|
||||
diamondLevel,
|
||||
brillianceCount,
|
||||
sellerGameId: characterId ? `${characterName} (${characterId})` : characterName,
|
||||
sellerPaymentInfo: '走打手接单平台钱包',
|
||||
sellerPhone,
|
||||
contactInfo,
|
||||
contentJson: {
|
||||
characterName,
|
||||
characterId,
|
||||
sellerNote: String(payload.sellerNote || '').trim(),
|
||||
channel: 'worker_portal',
|
||||
},
|
||||
screenshotsJson: screenshots,
|
||||
askingPriceFen,
|
||||
workerId: Number(worker.id),
|
||||
workerName: worker.display_name || worker.username,
|
||||
workerPhone: sellerPhone || worker.phone || '',
|
||||
depositAmountFen,
|
||||
now,
|
||||
})
|
||||
|
||||
return {
|
||||
id: submission.id,
|
||||
submissionNo: submission.submission_no,
|
||||
itemName: submission.item_name,
|
||||
system: submission.system,
|
||||
serverZone: submission.server_zone,
|
||||
series: submission.series,
|
||||
diamondLevel: submission.diamond_level,
|
||||
brillianceCount: submission.brilliance_count,
|
||||
askingPriceFen: submission.asking_price_fen,
|
||||
status: submission.status,
|
||||
workerId: submission.worker_id,
|
||||
workerName: submission.worker_name,
|
||||
workerPhone: submission.worker_phone,
|
||||
depositAmountFen: submission.deposit_amount_fen,
|
||||
createdAt: submission.created_at,
|
||||
}
|
||||
}
|
||||
|
||||
export async function listWorkerConsignments(workerId: number) {
|
||||
const [submissionsRes, inventoriesRes] = await Promise.all([
|
||||
listConsignmentSubmissions({ workerId, pageSize: 100 }),
|
||||
listConsignmentInventories({ workerId, pageSize: 100 }),
|
||||
])
|
||||
|
||||
return {
|
||||
submissions: submissionsRes.items.map((sub) => ({
|
||||
id: sub.id,
|
||||
submissionNo: sub.submission_no,
|
||||
itemName: sub.item_name,
|
||||
system: sub.system,
|
||||
serverZone: sub.server_zone,
|
||||
series: sub.series,
|
||||
diamondLevel: sub.diamond_level,
|
||||
brillianceCount: sub.brilliance_count,
|
||||
askingPriceFen: sub.asking_price_fen,
|
||||
status: sub.status,
|
||||
reviewNote: sub.review_note,
|
||||
resultInventoryId: sub.result_inventory_id,
|
||||
screenshotsJson: sub.screenshots_json,
|
||||
contentJson: sub.content_json,
|
||||
createdAt: sub.created_at,
|
||||
})),
|
||||
inventories: inventoriesRes.items.map((inv) => ({
|
||||
id: inv.id,
|
||||
inventoryNo: inv.inventory_no,
|
||||
itemName: inv.item_name,
|
||||
system: inv.system,
|
||||
serverZone: inv.server_zone,
|
||||
series: inv.series,
|
||||
diamondLevel: inv.diamond_level,
|
||||
brillianceCount: inv.brilliance_count,
|
||||
costPriceFen: inv.cost_price_fen,
|
||||
status: inv.status,
|
||||
allocationStatus: inv.allocation_status,
|
||||
boundPlatformOrderId: inv.bound_platform_order_id,
|
||||
depositAmountFen: inv.deposit_amount_fen,
|
||||
createdAt: inv.created_at,
|
||||
})),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user