后端迁移后台管理任务读辅助
This commit is contained in:
+102
-43
@@ -1,5 +1,3 @@
|
|||||||
// @ts-check
|
|
||||||
|
|
||||||
import { getTaskById } from '../../repositories/task-repo.js'
|
import { getTaskById } from '../../repositories/task-repo.js'
|
||||||
import { listTaskInventoryBindingSummariesByTaskIds } from '../../repositories/task-inventory-binding-repo.js'
|
import { listTaskInventoryBindingSummariesByTaskIds } from '../../repositories/task-inventory-binding-repo.js'
|
||||||
import { createHttpError } from '../../utils/http.js'
|
import { createHttpError } from '../../utils/http.js'
|
||||||
@@ -11,13 +9,60 @@ import {
|
|||||||
parseTaskContext,
|
parseTaskContext,
|
||||||
} from './admin-read-shared-helpers.js'
|
} from './admin-read-shared-helpers.js'
|
||||||
|
|
||||||
/** @typedef {import('../../types/admin-read-models.js').AdminTaskBindingSummary} AdminTaskBindingSummary */
|
import type {
|
||||||
/** @typedef {import('../../types/admin-read-models.js').AdminTaskListItem} AdminTaskListItem */
|
AdminAgisoAutoDeliveryStatus,
|
||||||
/** @typedef {import('../../types/repository-rows.js').TaskEventRow} TaskEventRow */
|
AdminTaskBindingSummary,
|
||||||
/** @typedef {import('../../types/repository-rows.js').TaskInventoryBindingRow} TaskInventoryBindingRow */
|
AdminTaskListItem,
|
||||||
/** @typedef {import('../../types/repository-rows.js').TaskRow} TaskRow */
|
} from '../../types/admin-read-models.js'
|
||||||
|
import type { AdminTaskActionPayload } from '../../types/admin-write-models.js'
|
||||||
|
import type {
|
||||||
|
OrderRow,
|
||||||
|
TaskEventRow,
|
||||||
|
TaskInventoryBindingRow,
|
||||||
|
TaskInventoryBindingSummaryRow,
|
||||||
|
TaskRow,
|
||||||
|
} from '../../types/repository-rows.js'
|
||||||
|
import type { AdminViewerContext } from './admin-read-shared-helpers.js'
|
||||||
|
|
||||||
export function mapAdminTaskSummary(task, bindingSummary = createEmptyTaskBindingSummary()) {
|
type JsonRecord = Record<string, any>
|
||||||
|
|
||||||
|
type TaskBindingState = {
|
||||||
|
systemBindingStatus: string
|
||||||
|
userBindingStatus: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskInventoryBindingLike = TaskInventoryBindingRow & {
|
||||||
|
inventory_item_status?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderBindingSummary = {
|
||||||
|
totalTaskCount: number
|
||||||
|
systemBoundTaskCount: number
|
||||||
|
completedBindingTaskCount: number
|
||||||
|
totalBindingCount: number
|
||||||
|
reservedBindingCount: number
|
||||||
|
consumedBindingCount: number
|
||||||
|
releasedBindingCount: number
|
||||||
|
systemBindingStatus: string
|
||||||
|
userBindingStatus: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderAgisoAutoDeliverySummary = AdminAgisoAutoDeliveryStatus & {
|
||||||
|
sourceTaskId: number | null
|
||||||
|
sourceTaskNo: string
|
||||||
|
totalTaskCount: number
|
||||||
|
deliveredTaskCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderAgisoAutoDeliveryCandidate = AdminAgisoAutoDeliveryStatus & {
|
||||||
|
sourceTaskId: number | null
|
||||||
|
sourceTaskNo: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapAdminTaskSummary(
|
||||||
|
task: TaskRow,
|
||||||
|
bindingSummary: AdminTaskBindingSummary = createEmptyTaskBindingSummary(),
|
||||||
|
): JsonRecord {
|
||||||
const binding = buildTaskBindingState(task)
|
const binding = buildTaskBindingState(task)
|
||||||
const taskContext = parseTaskContext(task)
|
const taskContext = parseTaskContext(task)
|
||||||
|
|
||||||
@@ -42,7 +87,7 @@ export function mapAdminTaskSummary(task, bindingSummary = createEmptyTaskBindin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapTaskActionPayload(task) {
|
export function mapTaskActionPayload(task: any): AdminTaskActionPayload {
|
||||||
const inventoryItemId = getTaskPrimaryInventoryItemId(task)
|
const inventoryItemId = getTaskPrimaryInventoryItemId(task)
|
||||||
const primaryClaimTokenId = getTaskPrimaryClaimTokenId(task)
|
const primaryClaimTokenId = getTaskPrimaryClaimTokenId(task)
|
||||||
|
|
||||||
@@ -60,7 +105,7 @@ export function mapTaskActionPayload(task) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRequiredTask(taskId) {
|
export async function getRequiredTask(taskId: number | string): Promise<TaskRow> {
|
||||||
const task = await getTaskById(Number(taskId))
|
const task = await getTaskById(Number(taskId))
|
||||||
|
|
||||||
if (!task) {
|
if (!task) {
|
||||||
@@ -73,11 +118,11 @@ export async function getRequiredTask(taskId) {
|
|||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function mapAdminTaskListItem(
|
||||||
* @param {TaskRow} task
|
task: TaskRow,
|
||||||
* @returns {AdminTaskListItem}
|
bindingSummary: AdminTaskBindingSummary = createEmptyTaskBindingSummary(),
|
||||||
*/
|
viewerContext: AdminViewerContext = createAdminViewerContext(),
|
||||||
export function mapAdminTaskListItem(task, bindingSummary = createEmptyTaskBindingSummary(), viewerContext = createAdminViewerContext()) {
|
): AdminTaskListItem {
|
||||||
const binding = buildTaskBindingState(task)
|
const binding = buildTaskBindingState(task)
|
||||||
const taskContext = parseTaskContext(task)
|
const taskContext = parseTaskContext(task)
|
||||||
|
|
||||||
@@ -114,8 +159,7 @@ export function mapAdminTaskListItem(task, bindingSummary = createEmptyTaskBindi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {TaskEventRow} event */
|
export function mapAdminTaskEvent(event: TaskEventRow): JsonRecord {
|
||||||
export function mapAdminTaskEvent(event) {
|
|
||||||
const payload = safeParseJson(event.payload_json)
|
const payload = safeParseJson(event.payload_json)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -126,8 +170,7 @@ export function mapAdminTaskEvent(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {AdminTaskBindingSummary} */
|
export function createEmptyTaskBindingSummary(): AdminTaskBindingSummary {
|
||||||
export function createEmptyTaskBindingSummary() {
|
|
||||||
return {
|
return {
|
||||||
totalBindingCount: 0,
|
totalBindingCount: 0,
|
||||||
reservedBindingCount: 0,
|
reservedBindingCount: 0,
|
||||||
@@ -137,11 +180,11 @@ export function createEmptyTaskBindingSummary() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTaskBindingSummaryMap(taskIds = []) {
|
export async function getTaskBindingSummaryMap(taskIds: Array<number | string> = []): Promise<Map<number, AdminTaskBindingSummary>> {
|
||||||
const rows = await listTaskInventoryBindingSummariesByTaskIds(taskIds)
|
const rows = await listTaskInventoryBindingSummariesByTaskIds(taskIds)
|
||||||
const output = new Map()
|
const output = new Map<number, AdminTaskBindingSummary>()
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows as TaskInventoryBindingSummaryRow[]) {
|
||||||
output.set(Number(row.task_id), {
|
output.set(Number(row.task_id), {
|
||||||
totalBindingCount: Number(row.total_binding_count || 0),
|
totalBindingCount: Number(row.total_binding_count || 0),
|
||||||
reservedBindingCount: Number(row.reserved_binding_count || 0),
|
reservedBindingCount: Number(row.reserved_binding_count || 0),
|
||||||
@@ -156,11 +199,16 @@ export async function getTaskBindingSummaryMap(taskIds = []) {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTaskBindingSummary(summaryMap, taskId) {
|
export function getTaskBindingSummary(
|
||||||
|
summaryMap: Map<number, AdminTaskBindingSummary>,
|
||||||
|
taskId: number | string,
|
||||||
|
): AdminTaskBindingSummary {
|
||||||
return summaryMap.get(Number(taskId)) || createEmptyTaskBindingSummary()
|
return summaryMap.get(Number(taskId)) || createEmptyTaskBindingSummary()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createTaskBindingSummaryFromBindings(bindings = []) {
|
export function createTaskBindingSummaryFromBindings(
|
||||||
|
bindings: Array<Partial<TaskInventoryBindingRow>> | null | undefined = [],
|
||||||
|
): AdminTaskBindingSummary {
|
||||||
const normalizedBindings = Array.isArray(bindings) ? bindings : []
|
const normalizedBindings = Array.isArray(bindings) ? bindings : []
|
||||||
const roleKeys = Array.from(new Set(normalizedBindings
|
const roleKeys = Array.from(new Set(normalizedBindings
|
||||||
.map((binding) => String(binding?.role_key || '').trim())
|
.map((binding) => String(binding?.role_key || '').trim())
|
||||||
@@ -175,7 +223,11 @@ export function createTaskBindingSummaryFromBindings(bindings = []) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapAdminTaskInventoryBinding(binding, task, viewerContext = createAdminViewerContext()) {
|
export function mapAdminTaskInventoryBinding(
|
||||||
|
binding: TaskInventoryBindingLike,
|
||||||
|
task: TaskRow,
|
||||||
|
viewerContext: AdminViewerContext = createAdminViewerContext(),
|
||||||
|
): JsonRecord {
|
||||||
const metadata = safeParseJson(binding.metadata_json)
|
const metadata = safeParseJson(binding.metadata_json)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -200,7 +252,10 @@ export function mapAdminTaskInventoryBinding(binding, task, viewerContext = crea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildOrderBindingSummary(tasks, taskBindingSummaryMap = new Map()) {
|
export function buildOrderBindingSummary(
|
||||||
|
tasks: TaskRow[] | null | undefined,
|
||||||
|
taskBindingSummaryMap: Map<number, AdminTaskBindingSummary> = new Map(),
|
||||||
|
): OrderBindingSummary {
|
||||||
const normalizedTasks = Array.isArray(tasks) ? tasks : []
|
const normalizedTasks = Array.isArray(tasks) ? tasks : []
|
||||||
const totalTaskCount = normalizedTasks.length
|
const totalTaskCount = normalizedTasks.length
|
||||||
const taskBindings = normalizedTasks.map((task) => buildTaskBindingState(task))
|
const taskBindings = normalizedTasks.map((task) => buildTaskBindingState(task))
|
||||||
@@ -260,7 +315,10 @@ export function buildOrderBindingSummary(tasks, taskBindingSummaryMap = new Map(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildOrderAgisoAutoDeliverySummary(order, tasks = []) {
|
export function buildOrderAgisoAutoDeliverySummary(
|
||||||
|
order: Partial<OrderRow> | null | undefined,
|
||||||
|
tasks: TaskRow[] = [],
|
||||||
|
): OrderAgisoAutoDeliverySummary | null {
|
||||||
if (String(order?.provider || '').trim() !== 'agiso' || String(order?.platform || '').trim() !== 'xianyu') {
|
if (String(order?.provider || '').trim() !== 'agiso' || String(order?.platform || '').trim() !== 'xianyu') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -268,7 +326,7 @@ export function buildOrderAgisoAutoDeliverySummary(order, tasks = []) {
|
|||||||
const normalizedTasks = Array.isArray(tasks) ? tasks.filter(Boolean) : []
|
const normalizedTasks = Array.isArray(tasks) ? tasks.filter(Boolean) : []
|
||||||
const totalTaskCount = normalizedTasks.length
|
const totalTaskCount = normalizedTasks.length
|
||||||
const deliveredTaskCount = normalizedTasks.filter((task) => String(task?.delivery_status || '').trim() === 'delivered').length
|
const deliveredTaskCount = normalizedTasks.filter((task) => String(task?.delivery_status || '').trim() === 'delivered').length
|
||||||
const latest = normalizedTasks.reduce((best, task) => {
|
const latest = normalizedTasks.reduce<OrderAgisoAutoDeliveryCandidate | null>((best, task) => {
|
||||||
const autoDelivery = mapAgisoAutoDeliveryContext(parseTaskContext(task).agisoAutoDelivery)
|
const autoDelivery = mapAgisoAutoDeliveryContext(parseTaskContext(task).agisoAutoDelivery)
|
||||||
|
|
||||||
if (!autoDelivery) {
|
if (!autoDelivery) {
|
||||||
@@ -314,7 +372,7 @@ export function buildOrderAgisoAutoDeliverySummary(order, tasks = []) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function canReleaseTaskInventoryBinding(task, binding) {
|
function canReleaseTaskInventoryBinding(task: TaskRow | null | undefined, binding: TaskInventoryBindingLike | null | undefined): boolean {
|
||||||
if (!task || !binding) {
|
if (!task || !binding) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -326,7 +384,7 @@ function canReleaseTaskInventoryBinding(task, binding) {
|
|||||||
return !['redeemed', 'expired'].includes(String(task.task_status || '').trim())
|
return !['redeemed', 'expired'].includes(String(task.task_status || '').trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTaskBindingState(task) {
|
function buildTaskBindingState(task: TaskRow | null | undefined): TaskBindingState {
|
||||||
const normalizedStatus = String(task?.task_status || '').trim()
|
const normalizedStatus = String(task?.task_status || '').trim()
|
||||||
|
|
||||||
if (!normalizedStatus) {
|
if (!normalizedStatus) {
|
||||||
@@ -407,32 +465,33 @@ function buildTaskBindingState(task) {
|
|||||||
return { systemBindingStatus: 'pending_binding', userBindingStatus: 'not_started' }
|
return { systemBindingStatus: 'pending_binding', userBindingStatus: 'not_started' }
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTaskSystemBound(task) {
|
function isTaskSystemBound(task: TaskRow | null | undefined): boolean {
|
||||||
return Boolean(task && (getTaskPrimaryInventoryItemId(task) || getTaskPrimaryClaimTokenId(task)))
|
return Boolean(task && (getTaskPrimaryInventoryItemId(task) || getTaskPrimaryClaimTokenId(task)))
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapAgisoAutoDeliveryContext(value) {
|
function mapAgisoAutoDeliveryContext(value: unknown): AdminAgisoAutoDeliveryStatus | null {
|
||||||
if (!value || typeof value !== 'object') {
|
if (!value || typeof value !== 'object') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const record = value as JsonRecord
|
||||||
return {
|
return {
|
||||||
status: String(value.status || '').trim(),
|
status: String(record.status || '').trim(),
|
||||||
trigger: String(value.trigger || '').trim(),
|
trigger: String(record.trigger || '').trim(),
|
||||||
reason: String(value.reason || '').trim(),
|
reason: String(record.reason || '').trim(),
|
||||||
platformOrderId: String(value.platformOrderId || '').trim(),
|
platformOrderId: String(record.platformOrderId || '').trim(),
|
||||||
responseStatus: Number(value.responseStatus || 0),
|
responseStatus: Number(record.responseStatus || 0),
|
||||||
errorMessage: String(value.errorMessage || '').trim(),
|
errorMessage: String(record.errorMessage || '').trim(),
|
||||||
requestId: String(value.requestId || '').trim(),
|
requestId: String(record.requestId || '').trim(),
|
||||||
updatedAt: value.updatedAt || null,
|
updatedAt: record.updatedAt || null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeRecord(value) {
|
function normalizeRecord(value: unknown): JsonRecord {
|
||||||
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function maskCode(value) {
|
function maskCode(value: unknown): string {
|
||||||
const text = String(value || '').trim()
|
const text = String(value || '').trim()
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return ''
|
return ''
|
||||||
@@ -443,6 +502,6 @@ function maskCode(value) {
|
|||||||
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
return `${text.slice(0, 4)}****${text.slice(-4)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTaskRetryCount(task) {
|
function getTaskRetryCount(task: TaskRow | null | undefined): number {
|
||||||
return Number(task?.attempt_count || 0)
|
return Number(task?.attempt_count || 0)
|
||||||
}
|
}
|
||||||
@@ -752,6 +752,14 @@
|
|||||||
- `npm run typecheck`
|
- `npm run typecheck`
|
||||||
- `npm run build`
|
- `npm run build`
|
||||||
- `npm test` 共 139 个用例通过
|
- `npm test` 共 139 个用例通过
|
||||||
|
189. 后台 Admin 任务读侧映射 helper 迁移到 `.ts`:
|
||||||
|
- `src/services/admin/admin-task-read-helpers.ts`
|
||||||
|
190. Admin 任务摘要 / 列表项 / 操作 payload、任务事件、任务库存绑定、任务绑定汇总 map、订单绑定状态汇总与 Agiso 自动发货订单摘要已进入 TS 编译链路;任务 row、绑定 row、事件 row、viewer context、绑定 summary、自动发货 summary 与动态 metadata 补齐类型
|
||||||
|
191. Docker 内验证通过:
|
||||||
|
- `src/services/admin/*.test.js` 共 8 个用例通过
|
||||||
|
- `npm run typecheck`
|
||||||
|
- `npm run build`
|
||||||
|
- `npm test` 共 139 个用例通过
|
||||||
|
|
||||||
## 下一步建议
|
## 下一步建议
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user