feat(订单): 添加获取订单总数功能
实现订单总数统计接口,包括: 1. 在ISiteAdapter接口添加countOrders方法 2. 在WooCommerce和Shopyy适配器中实现该方法 3. 添加控制器端点暴露该功能 4. 优化订单查询参数映射逻辑 refactor(Shopyy): 重构搜索参数映射逻辑 将通用的搜索参数映射逻辑提取为独立方法,提高代码复用性
This commit is contained in:
parent
402ec4ceec
commit
5e55b85107
|
|
@ -23,7 +23,9 @@ import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto';
|
|||
import {
|
||||
ShopyyCustomer,
|
||||
ShopyyOrder,
|
||||
ShopyyOrderQuery,
|
||||
ShopyyProduct,
|
||||
ShopyyProductQuery,
|
||||
ShopyyVariant,
|
||||
ShopyyWebhook,
|
||||
} from '../dto/shopyy.dto';
|
||||
|
|
@ -64,17 +66,30 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
}
|
||||
|
||||
private mapMediaSearchParams(params: UnifiedSearchParamsDTO): any {
|
||||
const { search, page, per_page } = params;
|
||||
const shopyyParams: any = {
|
||||
page: page || 1,
|
||||
limit: per_page || 10,
|
||||
};
|
||||
|
||||
if (search) {
|
||||
shopyyParams.query = search;
|
||||
return this.mapSearchParams(params)
|
||||
}
|
||||
|
||||
return shopyyParams;
|
||||
/**
|
||||
* 通用搜索参数转换方法,处理 where 和 orderBy 的转换
|
||||
* 将统一的搜索参数转换为 ShopYY API 所需的参数格式
|
||||
*/
|
||||
private mapSearchParams(params: UnifiedSearchParamsDTO): any {
|
||||
// 处理分页参数
|
||||
const page = Number(params.page || 1);
|
||||
const limit = Number(params.per_page ?? 20);
|
||||
|
||||
// 处理 where 条件
|
||||
const query: any = {
|
||||
...(params.where || {}),
|
||||
page,
|
||||
limit,
|
||||
}
|
||||
if(params.orderBy){
|
||||
const [field, dir] = Object.entries(params.orderBy)[0];
|
||||
query.order_by = dir === 'desc' ? 'desc' : 'asc';
|
||||
query.order_field = field
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
private mapProduct(item: ShopyyProduct & { permalink?: string }): UnifiedProductDTO {
|
||||
|
|
@ -393,14 +408,19 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
raw: item,
|
||||
};
|
||||
}
|
||||
mapProductQuery(query: UnifiedSearchParamsDTO): ShopyyProductQuery {
|
||||
return this.mapSearchParams(query)
|
||||
}
|
||||
|
||||
async getProducts(
|
||||
params: UnifiedSearchParamsDTO
|
||||
): Promise<UnifiedPaginationDTO<UnifiedProductDTO>> {
|
||||
// 转换搜索参数
|
||||
const requestParams = this.mapProductQuery(params);
|
||||
const response = await this.shopyyService.fetchResourcePaged<ShopyyProduct>(
|
||||
this.site,
|
||||
'products/list',
|
||||
params
|
||||
requestParams
|
||||
);
|
||||
const { items = [], total, totalPages, page, per_page } = response;
|
||||
const finalItems = items.map((item) => ({
|
||||
|
|
@ -431,7 +451,6 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
const res = await this.shopyyService.createProduct(this.site, data);
|
||||
return this.mapProduct(res);
|
||||
}
|
||||
|
||||
async updateProduct(id: string | number, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
||||
// Shopyy update returns boolean?
|
||||
// shopyyService.updateProduct returns boolean.
|
||||
|
|
@ -466,34 +485,45 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
): Promise<any> {
|
||||
return await this.shopyyService.batchProcessProducts(this.site, data);
|
||||
}
|
||||
mapUnifiedOrderQueryToShopyyQuery(params: UnifiedSearchParamsDTO) {
|
||||
const { where = {} as any, ...restParams } = params || {}
|
||||
/**
|
||||
* 将统一的订单查询参数转换为 ShopYY 订单查询参数
|
||||
* 包含状态映射等特殊处理
|
||||
*/
|
||||
private mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial<ShopyyOrderQuery> {
|
||||
// 首先使用通用参数转换
|
||||
const baseParams = this.mapSearchParams(params);
|
||||
|
||||
// 订单状态映射
|
||||
const statusMap = {
|
||||
'pending': '100', // 100 未完成
|
||||
'processing': '110', // 110 待处理
|
||||
'completed': "180", // 180 已完成(确认收货)
|
||||
'cancelled': '190', // 190 取消
|
||||
}
|
||||
const normalizedParams: any = {
|
||||
...restParams,
|
||||
}
|
||||
if (where) {
|
||||
normalizedParams.where = {
|
||||
...where,
|
||||
}
|
||||
if (where.status) {
|
||||
normalizedParams.where.status = statusMap[where.status];
|
||||
};
|
||||
|
||||
// 如果有状态参数,进行特殊映射
|
||||
if (baseParams.status) {
|
||||
const unifiedStatus = baseParams.status
|
||||
if (statusMap[unifiedStatus]) {
|
||||
baseParams.status = statusMap[unifiedStatus];
|
||||
}
|
||||
}
|
||||
return normalizedParams
|
||||
|
||||
// 处理ID参数
|
||||
if (baseParams.id) {
|
||||
baseParams.ids = baseParams.id;
|
||||
delete baseParams.id;
|
||||
}
|
||||
|
||||
return baseParams;
|
||||
}
|
||||
|
||||
async getOrders(
|
||||
params: UnifiedSearchParamsDTO
|
||||
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
|
||||
const normalizedParams = this.mapUnifiedOrderQueryToShopyyQuery(params);
|
||||
const { items, total, totalPages, page, per_page } =
|
||||
await this.shopyyService.fetchResourcePaged<any>(
|
||||
// 转换订单查询参数
|
||||
const normalizedParams = this.mapOrderSearchParams(params);
|
||||
const { items, total, totalPages, page, per_page } = await this.shopyyService.fetchResourcePaged<any>(
|
||||
this.site,
|
||||
'orders',
|
||||
normalizedParams
|
||||
|
|
@ -507,6 +537,17 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
};
|
||||
}
|
||||
|
||||
async countOrders(where: Record<string,any>): Promise<number> {
|
||||
// 使用最小分页只获取总数
|
||||
const searchParams = {
|
||||
where,
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
}
|
||||
const data = await this.getOrders(searchParams);
|
||||
return data.total || 0;
|
||||
}
|
||||
|
||||
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
||||
const data = await this.shopyyService.getAllOrders(this.site.id, params);
|
||||
return data.map(this.mapOrder.bind(this));
|
||||
|
|
|
|||
|
|
@ -718,6 +718,18 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
};
|
||||
}
|
||||
|
||||
async countOrders(where: Record<string,any>): Promise<number> {
|
||||
// 使用最小分页只获取总数
|
||||
const searchParams: UnifiedSearchParamsDTO = {
|
||||
where,
|
||||
page: 1,
|
||||
per_page: 1,
|
||||
};
|
||||
const requestParams = this.mapOrderSearchParams(searchParams);
|
||||
const { total } = await this.wpService.fetchResourcePaged<any>(this.site, 'orders', requestParams);
|
||||
return total || 0;
|
||||
}
|
||||
|
||||
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
|
||||
// 获取单个订单详情
|
||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||
|
|
@ -1294,4 +1306,3 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
import { Controller, Get, Inject, Query, Post, Del, Param, Files, Fields, Body } from '@midwayjs/core';
|
||||
import { WPService } from '../service/wp.service';
|
||||
import { successResponse, errorResponse } from '../utils/response.util';
|
||||
|
||||
@Controller('/media')
|
||||
export class MediaController {
|
||||
@Inject()
|
||||
wpService: WPService;
|
||||
|
||||
@Get('/list')
|
||||
async list(
|
||||
@Query('siteId') siteId: number,
|
||||
@Query('page') page: number = 1,
|
||||
@Query('pageSize') pageSize: number = 20
|
||||
) {
|
||||
try {
|
||||
if (!siteId) {
|
||||
return errorResponse('siteId is required');
|
||||
}
|
||||
const result = await this.wpService.getMedia(siteId, page, pageSize);
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/upload')
|
||||
async upload(@Fields() fields, @Files() files) {
|
||||
try {
|
||||
const siteId = fields.siteId;
|
||||
if (!siteId) {
|
||||
return errorResponse('siteId is required');
|
||||
}
|
||||
if (!files || files.length === 0) {
|
||||
return errorResponse('file is required');
|
||||
}
|
||||
const file = files[0];
|
||||
const result = await this.wpService.createMedia(siteId, file);
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/update/:id')
|
||||
async update(@Param('id') id: number, @Body() body) {
|
||||
try {
|
||||
const siteId = body.siteId;
|
||||
if (!siteId) {
|
||||
return errorResponse('siteId is required');
|
||||
}
|
||||
// 过滤出需要更新的字段
|
||||
const { title, caption, description, alt_text } = body;
|
||||
const data: any = {};
|
||||
if (title !== undefined) data.title = title;
|
||||
if (caption !== undefined) data.caption = caption;
|
||||
if (description !== undefined) data.description = description;
|
||||
if (alt_text !== undefined) data.alt_text = alt_text;
|
||||
|
||||
const result = await this.wpService.updateMedia(siteId, id, data);
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Del('/:id')
|
||||
async delete(@Param('id') id: number, @Query('siteId') siteId: number, @Query('force') force: boolean = true) {
|
||||
try {
|
||||
if (!siteId) {
|
||||
return errorResponse('siteId is required');
|
||||
}
|
||||
const result = await this.wpService.deleteMedia(siteId, id, force);
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -672,6 +672,26 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/orders/count')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async countOrders(
|
||||
@Param('siteId') siteId: number,
|
||||
@Query() query: any
|
||||
) {
|
||||
this.logger.info(`[Site API] 获取订单总数开始, siteId: ${siteId}`);
|
||||
try {
|
||||
|
||||
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const total = await adapter.countOrders(query);
|
||||
this.logger.info(`[Site API] 获取订单总数成功, siteId: ${siteId}, total: ${total}`);
|
||||
return successResponse({ total });
|
||||
} catch (error) {
|
||||
this.logger.error(`[Site API] 获取订单总数失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/customers/:customerId/orders')
|
||||
@ApiOkResponse({ type: UnifiedOrderPaginationDTO })
|
||||
async getCustomerOrders(
|
||||
|
|
@ -1050,13 +1070,13 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/orders/:orderId/trackings')
|
||||
@Get('/:siteId/orders/:orderId/fulfillments')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async getOrderTrackings(
|
||||
async getOrderFulfillments(
|
||||
@Param('siteId') siteId: number,
|
||||
@Param('orderId') orderId: string
|
||||
) {
|
||||
this.logger.info(`[Site API] 获取订单物流跟踪信息开始, siteId: ${siteId}, orderId: ${orderId}`);
|
||||
this.logger.info(`[Site API] 获取订单履约信息开始, siteId: ${siteId}, orderId: ${orderId}`);
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const data = await adapter.getOrderFulfillments(orderId);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ export interface ShopyyTag {
|
|||
id?: number;
|
||||
name?: string;
|
||||
}
|
||||
export interface ShopyyProductQuery{
|
||||
page: string;
|
||||
limit: string;
|
||||
}
|
||||
// 产品类型
|
||||
export interface ShopyyProduct {
|
||||
// 产品主键
|
||||
|
|
@ -83,6 +87,42 @@ export interface ShopyyVariant {
|
|||
position?: number | string;
|
||||
sku_code?: string;
|
||||
}
|
||||
//
|
||||
// 订单查询参数类型
|
||||
export interface ShopyyOrderQuery {
|
||||
// 订单ID集合 多个ID用','联接 例:1,2,3
|
||||
ids?: string;
|
||||
// 订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||
status?: string;
|
||||
// 物流状态 300 未发货;310 部分发货;320 已发货;330(确认收货)
|
||||
fulfillment_status?: string;
|
||||
// 支付状态 200 待支付;210 支付中;220 部分支付;230 已支付;240 支付失败;250 部分退款;260 已退款 ;290 已取消;
|
||||
financial_status?: string;
|
||||
// 支付时间 下限值
|
||||
pay_at_min?: string;
|
||||
// 支付时间 上限值
|
||||
pay_at_max?: string;
|
||||
// 创建开始时间
|
||||
created_at_min?: number;
|
||||
// 创建结束时间
|
||||
created_at_max?: number;
|
||||
// 更新时间开始
|
||||
updated_at_min?: string;
|
||||
// 更新时间结束
|
||||
updated_at_max?: string;
|
||||
// 起始ID
|
||||
since_id?: string;
|
||||
// 页码
|
||||
page?: string;
|
||||
// 每页条数
|
||||
limit?: string;
|
||||
// 排序字段(默认id) id=订单ID updated_at=最后更新时间 pay_at=支付时间
|
||||
order_field?: string;
|
||||
// 排序方式(默认desc) desc=降序 asc=升序
|
||||
order_by?: string;
|
||||
// 订单列表类型
|
||||
group?: string;
|
||||
}
|
||||
|
||||
// 订单类型
|
||||
export interface ShopyyOrder {
|
||||
|
|
|
|||
|
|
@ -65,9 +65,6 @@ export class Product {
|
|||
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||
promotionPrice: number;
|
||||
|
||||
|
||||
|
||||
|
||||
// 分类关联
|
||||
@ManyToOne(() => Category, category => category.products)
|
||||
@JoinColumn({ name: 'categoryId' })
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ export interface ISiteAdapter {
|
|||
*/
|
||||
getOrders(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>>;
|
||||
|
||||
/**
|
||||
* 获取订单总数
|
||||
*/
|
||||
countOrders(params: Record<string,any>): Promise<number>;
|
||||
|
||||
/**
|
||||
* 获取所有订单
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
|
||||
*/
|
||||
import { ILogger, Inject, Provide } from '@midwayjs/core';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import * as fs from 'fs';
|
||||
|
|
@ -180,41 +183,19 @@ export class ShopyyService {
|
|||
* 通用分页获取资源
|
||||
*/
|
||||
public async fetchResourcePaged<T>(site: any, endpoint: string, params: Record<string, any> = {}) {
|
||||
const page = Number(params.page || 1);
|
||||
const limit = Number(params.per_page ?? 20);
|
||||
const where = params.where && typeof params.where === 'object' ? params.where : {};
|
||||
let orderby: string | undefined = params.orderby;
|
||||
let order: 'asc' | 'desc' | undefined = params.orderDir as any;
|
||||
if (!orderby && params.order && typeof params.order === 'object') {
|
||||
const entries = Object.entries(params.order as Record<string, any>);
|
||||
if (entries.length > 0) {
|
||||
const [field, dir] = entries[0];
|
||||
orderby = field;
|
||||
order = String(dir).toLowerCase() === 'desc' ? 'desc' : 'asc';
|
||||
const response = await this.request(site, endpoint, 'GET', null, params);
|
||||
return this.mapPageResponse<T>(response,params);
|
||||
}
|
||||
}
|
||||
// 映射统一入参到平台入参
|
||||
const requestParams = {
|
||||
...where,
|
||||
...(params.search ? { search: params.search } : {}),
|
||||
...(params.status ? { status: params.status } : {}),
|
||||
...(orderby ? { orderby } : {}),
|
||||
...(order ? { order } : {}),
|
||||
page,
|
||||
limit
|
||||
};
|
||||
this.logger.debug('ShopYY API请求分页参数:'+ JSON.stringify(requestParams));
|
||||
const response = await this.request(site, endpoint, 'GET', null, requestParams);
|
||||
mapPageResponse<T>(response:any,query: Record<string, any>){
|
||||
if (response?.code !== 0) {
|
||||
throw new Error(response?.msg)
|
||||
}
|
||||
|
||||
return {
|
||||
items: (response.data.list || []) as T[],
|
||||
total: response.data?.paginate?.total || 0,
|
||||
totalPages: response.data?.paginate?.pageTotal || 0,
|
||||
page: response.data?.paginate?.current || requestParams.page,
|
||||
per_page: response.data?.paginate?.pagesize || requestParams.limit,
|
||||
page: response.data?.paginate?.current || query.page,
|
||||
per_page: response.data?.paginate?.pagesize || query.limit,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue