增加发货记录查询功能-1

This commit is contained in:
yml2213
2026-05-26 15:01:02 +08:00
parent ab417ca90f
commit 604f1bbf54
16 changed files with 1032 additions and 0 deletions
@@ -28,6 +28,7 @@ import {
useCloudtentaclesSku,
} from "../../../platforms/cloudtentacles/catalog-service.js";
import { getCloudtentaclesKnapsack } from "../../../platforms/cloudtentacles/knapsack-service.js";
import { listCloudtentaclesDeliveryRecords } from "../../../platforms/cloudtentacles/record-service.js";
import {
appointCloudtentaclesVirtualNumber,
backCloudtentaclesVirtualNumber,
@@ -59,6 +60,7 @@ import { createHttpError } from "../../../../utils/http.js";
import type {
AdminCloudtentaclesCatalogQueryInput,
AdminCloudtentaclesDeliveryRecordQueryInput,
AdminCloudtentaclesFullFlowInput,
AdminCloudtentaclesSendSmsCodeInput,
AdminCloudtentaclesSkuBuyInput,
@@ -93,6 +95,26 @@ export function listAdminCloudtentaclesSources() {
};
}
export function listAdminCloudtentaclesRecordSources() {
const list = listCloudtentaclesSources();
const sessions = getAllCloudtentaclesSessionStates();
const sources = Array.isArray(list.sources) ? list.sources : [];
return {
sources: sources.map((source) => {
const key = String(source.key || "").trim();
const session = (sessions.sessions?.[key] || {}) as JsonObject;
return {
key,
label: String(source.label || source.username || key || "未命名账号").trim(),
enabled: source.enabled !== false,
hasToken: Boolean(String(session.token || "").trim()),
loggedInAt: String(session.loggedInAt || "").trim(),
};
}),
};
}
export function updateAdminCloudtentaclesSourceConfig(
payload: AdminCloudtentaclesSourceConfigInput = {}
) {
@@ -286,6 +308,57 @@ export async function getAdminCloudtentaclesKnapsack(
);
}
export async function listAdminCloudtentaclesDeliveryRecords(
payload: AdminCloudtentaclesDeliveryRecordQueryInput = {}
) {
const context = resolveAdminCloudtentaclesSessionPayload(payload);
const query = {
...context,
...pickDefined({
page: payload.page,
size: payload.size,
startDate: payload.startDate,
endDate: payload.endDate,
recordCode: payload.recordCode,
}),
};
const [records, skuList] = await Promise.all([
listCloudtentaclesDeliveryRecords(query),
listCloudtentaclesSku(context),
]);
const skuImageByName = new Map<string, {
skuId: number;
skuName: string;
skuImage: string;
}>();
for (const sku of Array.isArray(skuList.items) ? skuList.items : []) {
const name = normalizeSkuName(sku.name);
const skuImage = String(sku.image || "").trim();
if (!name || !skuImage) {
continue;
}
skuImageByName.set(name, {
skuId: Number(sku.id || 0),
skuName: String(sku.name || "").trim(),
skuImage,
});
}
const recordItems: JsonObject[] = Array.isArray(records.items) ? records.items : [];
return {
...records,
items: recordItems.map((record) => ({
...record,
...(skuImageByName.get(normalizeSkuName(record.name)) || {
skuId: 0,
skuName: "",
skuImage: "",
}),
})),
};
}
export async function listAdminCloudtentaclesVirtualNumbers(
payload: AdminCloudtentaclesVirtualNumberInput = {}
) {
@@ -373,3 +446,13 @@ export async function runAdminCloudtentaclesFullFlow(
vnKey: payload.vnKey,
});
}
function pickDefined(values: JsonObject = {}) {
return Object.fromEntries(
Object.entries(values).filter(([, value]) => typeof value !== "undefined")
);
}
function normalizeSkuName(value: unknown) {
return String(value || "").trim().replace(/\s+/g, "").toLowerCase();
}
@@ -0,0 +1,132 @@
import { createHttpError } from '../../../utils/http.js'
import { cloudtentaclesRequest } from './http-client.js'
import { resolveCloudtentaclesConfig } from './helpers.js'
type JsonObject = Record<string, any>
export type CloudtentaclesDeliveryRecordQuery = JsonObject & {
baseUrl?: string
timeoutMs?: number | string
token?: string
deviceId?: string
deviceType?: number | string
page?: number | string
size?: number | string
startDate?: string
endDate?: string
recordCode?: number | string
}
export async function listCloudtentaclesDeliveryRecords(
payload: CloudtentaclesDeliveryRecordQuery = {},
) {
const config = resolveCloudtentaclesConfig({
...pickDefined({
baseUrl: payload.baseUrl,
deviceId: payload.deviceId,
deviceType: payload.deviceType,
}),
...(
typeof payload.timeoutMs === 'undefined'
? {}
: { timeoutMs: normalizePositiveInteger(payload.timeoutMs, 5000) }
),
})
const page = normalizePositiveInteger(payload.page, 1)
const size = normalizePageSize(payload.size)
const startDate = normalizeDateInput(payload.startDate)
const endDate = normalizeDateInput(payload.endDate)
if (!String(payload.token || '').trim()) {
throw createHttpError('cloudtentacles 发货记录查询缺少 token', {
statusCode: 409,
errorCode: 'cloudtentacles_record_missing_token',
})
}
if (!startDate || !endDate) {
throw createHttpError('cloudtentacles 发货记录查询需要选择开始和结束时间', {
statusCode: 400,
errorCode: 'cloudtentacles_record_date_required',
})
}
const result = await cloudtentaclesRequest('/record/paging', {
...payload,
method: 'POST',
body: {
record_code: normalizePositiveInteger(payload.recordCode, 4),
page,
size,
start_date: startDate,
end_date: endDate,
},
businessErrorCode: 'cloudtentacles_record_query_failed',
timeoutMs: payload.timeoutMs || config.timeoutMs,
})
const data = isPlainObject(result.payload?.data) ? result.payload.data : {}
const values = Array.isArray(data.values) ? data.values : []
return {
baseUrl: config.baseUrl,
page,
size,
total: normalizeNonNegativeInteger(data.total, values.length),
items: values.map(mapCloudtentaclesDeliveryRecord),
raw: data,
}
}
function mapCloudtentaclesDeliveryRecord(raw: JsonObject) {
return {
createdAt: String(raw.c_time || '').trim(),
recordId: String(raw.record_id || '').trim(),
virtualNumberId: normalizeNonNegativeInteger(raw.virtual_number_id, 0),
userId: normalizeNonNegativeInteger(raw.user_id, 0),
count: normalizeNonNegativeInteger(raw.count, 0),
cdk: String(raw.cdk || '').trim(),
exchangeUrl: String(raw.exchange_url || '').trim(),
name: String(raw.name || '').trim(),
phone: String(raw.phone || '').trim(),
status: normalizeNonNegativeInteger(raw.status, 0),
fulfillUser: String(raw.fulfill_user || '').trim(),
operatorName: String(raw.u_name || '').trim(),
raw,
}
}
function normalizePositiveInteger(value: unknown, fallback: number) {
const parsed = Number(value)
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback
}
function normalizePageSize(value: unknown) {
const parsed = normalizePositiveInteger(value, 100)
return Math.min(Math.max(parsed, 100), 1000)
}
function normalizeNonNegativeInteger(value: unknown, fallback: number) {
const parsed = Number(value)
return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback
}
function normalizeDateInput(value: unknown) {
const raw = String(value || '').trim()
if (!raw) {
return ''
}
const date = new Date(raw)
return Number.isNaN(date.getTime()) ? raw : date.toISOString()
}
function isPlainObject(value: unknown): value is JsonObject {
return Object.prototype.toString.call(value) === '[object Object]'
}
function pickDefined(values: JsonObject = {}) {
return Object.fromEntries(
Object.entries(values).filter(([, value]) => typeof value !== 'undefined'),
)
}