forked from yoone/API
1
0
Fork 0

feat(订单): 重构订单履约功能并完善相关实体和服务

refactor(订单服务): 优化订单同步逻辑,增加履约信息处理

feat(实体): 新增订单履约实体并更新关联关系

fix(适配器): 修正Shopyy和WooCommerce履约数据映射

docs(dto): 更新订单和履约相关DTO定义

style: 格式化代码并修正拼写错误
This commit is contained in:
tikkhun 2026-01-05 22:38:48 +08:00
parent 99bd7009cc
commit 934085fd64
16 changed files with 1078 additions and 162 deletions

View File

@ -15,9 +15,11 @@ import {
CreateWebhookDTO, CreateWebhookDTO,
UpdateWebhookDTO, UpdateWebhookDTO,
UnifiedAddressDTO, UnifiedAddressDTO,
UnifiedShippingLineDTO UnifiedShippingLineDTO,
OrderFulfillmentStatus,
FulfillmentDTO
} from '../dto/site-api.dto'; } from '../dto/site-api.dto';
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto'; import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto';
import { import {
ShopyyCustomer, ShopyyCustomer,
ShopyyOrder, ShopyyOrder,
@ -29,6 +31,16 @@ import {
OrderStatus, OrderStatus,
} from '../enums/base.enum'; } from '../enums/base.enum';
export class ShopyyAdapter implements ISiteAdapter { export class ShopyyAdapter implements ISiteAdapter {
shopyyFinancialStatusMap= {
'200': '待支付',
'210': "支付中",
'220':"部分支付",
'230':"已支付",
'240':"支付失败",
'250':"部分退款",
'260':"已退款",
'290':"已取消",
}
constructor(private site: any, private shopyyService: ShopyyService) { constructor(private site: any, private shopyyService: ShopyyService) {
this.mapCustomer = this.mapCustomer.bind(this); this.mapCustomer = this.mapCustomer.bind(this);
this.mapProduct = this.mapProduct.bind(this); this.mapProduct = this.mapProduct.bind(this);
@ -135,6 +147,8 @@ export class ShopyyAdapter implements ISiteAdapter {
shopyyOrderStatusMap = {//订单状态 100 未完成110 待处理180 已完成(确认收货); 190 取消; shopyyOrderStatusMap = {//订单状态 100 未完成110 待处理180 已完成(确认收货); 190 取消;
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending [100]: OrderStatus.PENDING, // 100 未完成 转为 pending
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing [110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
// 已发货
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed [180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled [190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
} }
@ -188,14 +202,14 @@ export class ShopyyAdapter implements ISiteAdapter {
'', '',
}; };
// 构建送货地址对象 // 构建送货地址对象
const shipping_lines: UnifiedShippingLineDTO[] = [ const shipping_lines: UnifiedShippingLineDTO[] = [
{ {
id: item.fulfillments?.[0]?.id || 0, id: item.fulfillments?.[0]?.id || 0,
method_title: item.payment_method || '', method_title: item.payment_method || '',
method_id: item.payment_method || '', method_id: item.payment_method || '',
total: Number(item.current_shipping_price).toExponential(2) || '0.00', total: Number(item.current_shipping_price).toExponential(2) || '0.00',
total_tax: '0.00', total_tax: '0.00',
taxes: [], taxes: [],
meta_data: [], meta_data: [],
}, },
@ -229,28 +243,32 @@ export class ShopyyAdapter implements ISiteAdapter {
price: String(p.price ?? ''), price: String(p.price ?? ''),
}) })
); );
// 货币符号
const currencySymbols: Record<string, string> = {
'EUR': '€',
'USD': '$',
'GBP': '£',
'JPY': '¥',
'AUD': 'A$',
'CAD': 'C$',
'CHF': 'CHF',
'CNY': '¥',
'HKD': 'HK$',
'NZD': 'NZ$',
'SGD': 'S$'
// 可以根据需要添加更多货币代码和符号
};
const currencySymbols: Record<string, string> = {
'EUR': '€',
'USD': '$',
'GBP': '£',
'JPY': '¥',
'AUD': 'A$',
'CAD': 'C$',
'CHF': 'CHF',
'CNY': '¥',
'HKD': 'HK$',
'NZD': 'NZ$',
'SGD': 'S$'
// 可以根据需要添加更多货币代码和符号
};
// 映射订单状态,如果不存在则默认 pending // 映射订单状态,如果不存在则默认 pending
const status = this.shopyyOrderStatusMap[item.status?? item.order_status] || OrderStatus.PENDING; const status = this.shopyyOrderStatusMap[item.status ?? item.order_status] || OrderStatus.PENDING;
const finalcial_status = this.shopyyFinancialStatusMap[item.financial_status]
// 发货状态
const fulfillment_status = this.shopyyFulfillmentStatusMap[item.fulfillment_status];
return { return {
id: item.id || item.order_id, id: item.id || item.order_id,
number: item.order_number || item.order_sn, number: item.order_number || item.order_sn,
status, status,
financial_status: finalcial_status,
currency: item.currency_code || item.currency, currency: item.currency_code || item.currency,
total: String(item.total_price ?? item.total_amount ?? ''), total: String(item.total_price ?? item.total_amount ?? ''),
customer_id: item.customer_id || item.user_id, customer_id: item.customer_id || item.user_id,
@ -271,7 +289,7 @@ export class ShopyyAdapter implements ISiteAdapter {
customer_ip_address: item.ip || '', customer_ip_address: item.ip || '',
device_type: item.source_device || '', device_type: item.source_device || '',
utm_source: item.utm_source || '', utm_source: item.utm_source || '',
source_type: 'shopyy', source_type: 'shopyy', // FIXME
date_paid: typeof item.pay_at === 'number' date_paid: typeof item.pay_at === 'number'
? item.pay_at === 0 ? null : new Date(item.pay_at * 1000).toISOString() ? item.pay_at === 0 ? null : new Date(item.pay_at * 1000).toISOString()
: null, : null,
@ -289,10 +307,31 @@ export class ShopyyAdapter implements ISiteAdapter {
: item.date_updated || : item.date_updated ||
item.last_modified || item.last_modified ||
(typeof item.updated_at === 'string' ? item.updated_at : ''), (typeof item.updated_at === 'string' ? item.updated_at : ''),
fulfillment_status,
fulfillments: item.fulfillments?.map?.((f) => ({
id: f.id,
tracking_number: f.tracking_number || '',
shipping_provider: f.tracking_company || '',
shipping_method: f.tracking_company || '',
date_created: typeof f.created_at === 'number'
? new Date(f.created_at * 1000).toISOString()
: f.created_at || '',
// status: f.payment_tracking_status
})) || [],
raw: item, raw: item,
}; };
} }
shopyyFulfillmentStatusMap = {
// 未发货
'300': OrderFulfillmentStatus.PENDING,
// 部分发货
'310': OrderFulfillmentStatus.PARTIALLY_FULFILLED,
// 已发货
'320': OrderFulfillmentStatus.FULFILLED,
// 已取消
'330': OrderFulfillmentStatus.CANCELLED,
// 确认发货
}
private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO { private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO {
// 处理多地址结构 // 处理多地址结构
@ -363,11 +402,11 @@ export class ShopyyAdapter implements ISiteAdapter {
'products/list', 'products/list',
params params
); );
const { items=[], total, totalPages, page, per_page } = response; const { items = [], total, totalPages, page, per_page } = response;
const finalItems = items.map((item) => ({ const finalItems = items.map((item) => ({
...item, ...item,
permalink: `${this.site.websiteUrl}/products/${item.handle}`, permalink: `${this.site.websiteUrl}/products/${item.handle}`,
})).map(this.mapProduct.bind(this)) })).map(this.mapProduct.bind(this))
return { return {
items: finalItems as UnifiedProductDTO[], items: finalItems as UnifiedProductDTO[],
total, total,
@ -427,22 +466,22 @@ export class ShopyyAdapter implements ISiteAdapter {
): Promise<any> { ): Promise<any> {
return await this.shopyyService.batchProcessProducts(this.site, data); return await this.shopyyService.batchProcessProducts(this.site, data);
} }
mapUnifiedOrderQueryToShopyyQuery(params:UnifiedSearchParamsDTO ){ mapUnifiedOrderQueryToShopyyQuery(params: UnifiedSearchParamsDTO) {
const {where={} as any, ...restParams} = params|| {} const { where = {} as any, ...restParams } = params || {}
const statusMap = { const statusMap = {
'pending': '100', // 100 未完成 'pending': '100', // 100 未完成
'processing': '110', // 110 待处理 'processing': '110', // 110 待处理
'completed': "180", // 180 已完成(确认收货) 'completed': "180", // 180 已完成(确认收货)
'cancelled': '190', // 190 取消 'cancelled': '190', // 190 取消
} }
const normalizedParams:any= { const normalizedParams: any = {
...restParams, ...restParams,
} }
if(where){ if (where) {
normalizedParams.where = { normalizedParams.where = {
...where, ...where,
} }
if(where.status){ if (where.status) {
normalizedParams.where.status = statusMap[where.status]; normalizedParams.where.status = statusMap[where.status];
} }
} }
@ -469,7 +508,7 @@ export class ShopyyAdapter implements ISiteAdapter {
} }
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> { async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
const data = await this.shopyyService.getAllOrders(this.site.id,params); const data = await this.shopyyService.getAllOrders(this.site.id, params);
return data.map(this.mapOrder.bind(this)); return data.map(this.mapOrder.bind(this));
} }
@ -543,7 +582,7 @@ export class ShopyyAdapter implements ISiteAdapter {
// 调用 ShopyyService 的取消履行方法 // 调用 ShopyyService 的取消履行方法
const cancelShipData = { const cancelShipData = {
order_id: String(orderId), order_id: String(orderId),
fullfillment_id: data.shipment_id || '' fulfillment_id: data.shipment_id || ''
}; };
const result = await this.shopyyService.cancelFulfillment(this.site, cancelShipData); const result = await this.shopyyService.cancelFulfillment(this.site, cancelShipData);
@ -574,18 +613,13 @@ export class ShopyyAdapter implements ISiteAdapter {
* @param data * @param data
* @returns * @returns
*/ */
async createOrderFulfillment(orderId: string | number, data: { async createOrderFulfillment(orderId: string | number, data: FulfillmentDTO): Promise<any> {
tracking_number: string;
tracking_provider: string;
date_shipped?: string;
status_shipped?: string;
}): Promise<any> {
// 调用 Shopyy Service 的 createFulfillment 方法 // 调用 Shopyy Service 的 createFulfillment 方法
const fulfillmentData = { const fulfillmentData = {
tracking_number: data.tracking_number, tracking_number: data.tracking_number,
carrier_code: data.tracking_provider, carrier_code: data.shipping_provider,
carrier_name: data.tracking_provider, carrier_name: data.shipping_provider,
shipping_method: data.status_shipped || 'standard' shipping_method: data.shipping_method || 'standard'
}; };
return await this.shopyyService.createFulfillment(this.site, String(orderId), fulfillmentData); return await this.shopyyService.createFulfillment(this.site, String(orderId), fulfillmentData);
@ -751,7 +785,7 @@ export class ShopyyAdapter implements ISiteAdapter {
id: item.id, id: item.id,
name: item.webhook_name || `Webhook-${item.id}`, name: item.webhook_name || `Webhook-${item.id}`,
topic: item.event_code || '', topic: item.event_code || '',
delivery_url: item.url|| '', delivery_url: item.url || '',
status: 'active', status: 'active',
}; };
} }
@ -791,7 +825,7 @@ export class ShopyyAdapter implements ISiteAdapter {
return await this.shopyyService.deleteWebhook(this.site, id); return await this.shopyyService.deleteWebhook(this.site, id);
} }
async getLinks(): Promise<Array<{title: string, url: string}>> { async getLinks(): Promise<Array<{ title: string, url: string }>> {
// ShopYY站点的管理后台链接通常基于apiUrl构建 // ShopYY站点的管理后台链接通常基于apiUrl构建
const url = this.site.websiteUrl const url = this.site.websiteUrl
// 提取基础域名,去掉可能的路径部分 // 提取基础域名,去掉可能的路径部分

View File

@ -454,12 +454,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
// 包含账单地址与收货地址以及创建与更新时间 // 包含账单地址与收货地址以及创建与更新时间
// 映射物流追踪信息,将后端格式转换为前端期望的格式 // 映射物流追踪信息,将后端格式转换为前端期望的格式
const tracking = (item.trackings || []).map((track: any) => ({ const fulfillments = (item.fulfillments || []).map((track: any) => ({
order_id: String(item.id),
tracking_provider: track.tracking_provider || '',
tracking_number: track.tracking_number || '', tracking_number: track.tracking_number || '',
date_shipped: track.date_shipped || '', shipping_provider: track.shipping_provider || '',
status_shipped: track.status_shipped || '', shipping_method: track.shipping_method || '',
status: track.status || '',
date_created: track.date_created || '',
items: track.items || [],
})); }));
return { return {
@ -496,7 +497,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
shipping_lines: item.shipping_lines, shipping_lines: item.shipping_lines,
fee_lines: item.fee_lines, fee_lines: item.fee_lines,
coupon_lines: item.coupon_lines, coupon_lines: item.coupon_lines,
tracking: tracking, fulfillments,
raw: item, raw: item,
}; };
} }
@ -687,29 +688,29 @@ export class WooCommerceAdapter implements ISiteAdapter {
await this.wpService.fetchResourcePaged<any>(this.site, 'orders', requestParams); await this.wpService.fetchResourcePaged<any>(this.site, 'orders', requestParams);
// 并行获取所有订单的履行信息 // 并行获取所有订单的履行信息
const ordersWithTracking = await Promise.all( const ordersWithFulfillments = await Promise.all(
items.map(async (order: any) => { items.map(async (order: any) => {
try { try {
// 获取订单的履行信息 // 获取订单的履行信息
const trackings = await this.getOrderFulfillments(order.id); const fulfillments = await this.getOrderFulfillments(order.id);
// 将履行信息添加到订单对象中 // 将履行信息添加到订单对象中
return { return {
...order, ...order,
trackings: trackings || [] fulfillments: fulfillments || []
}; };
} catch (error) { } catch (error) {
// 如果获取履行信息失败,仍然返回订单,只是履行信息为空数组 // 如果获取履行信息失败,仍然返回订单,只是履行信息为空数组
console.error(`获取订单 ${order.id} 的履行信息失败:`, error); console.error(`获取订单 ${order.id} 的履行信息失败:`, error);
return { return {
...order, ...order,
trackings: [] fulfillments: []
}; };
} }
}) })
); );
return { return {
items: ordersWithTracking.map(this.mapOrder), items: ordersWithFulfillments.map(this.mapOrder),
total, total,
totalPages, totalPages,
page, page,
@ -1016,21 +1017,34 @@ export class WooCommerceAdapter implements ISiteAdapter {
async createOrderFulfillment(orderId: string | number, data: { async createOrderFulfillment(orderId: string | number, data: {
tracking_number: string; tracking_number: string;
tracking_provider: string; shipping_provider: string;
date_shipped?: string; shipping_method?: string;
status_shipped?: string; status?: string;
date_created?: string;
items?: Array<{
order_item_id: number;
quantity: number;
}>;
}): Promise<any> { }): Promise<any> {
const shipmentData: any = { const shipmentData: any = {
tracking_provider: data.tracking_provider, shipping_provider: data.shipping_provider,
tracking_number: data.tracking_number, tracking_number: data.tracking_number,
}; };
if (data.date_shipped) { if (data.shipping_method) {
shipmentData.date_shipped = data.date_shipped; shipmentData.shipping_method = data.shipping_method;
} }
if (data.status_shipped) { if (data.status) {
shipmentData.status_shipped = data.status_shipped; shipmentData.status = data.status;
}
if (data.date_created) {
shipmentData.date_created = data.date_created;
}
if (data.items) {
shipmentData.items = data.items;
} }
const response = await this.wpService.createFulfillment(this.site, String(orderId), shipmentData); const response = await this.wpService.createFulfillment(this.site, String(orderId), shipmentData);
@ -1039,9 +1053,14 @@ export class WooCommerceAdapter implements ISiteAdapter {
async updateOrderFulfillment(orderId: string | number, fulfillmentId: string, data: { async updateOrderFulfillment(orderId: string | number, fulfillmentId: string, data: {
tracking_number?: string; tracking_number?: string;
tracking_provider?: string; shipping_provider?: string;
date_shipped?: string; shipping_method?: string;
status_shipped?: string; status?: string;
date_created?: string;
items?: Array<{
order_item_id: number;
quantity: number;
}>;
}): Promise<any> { }): Promise<any> {
return await this.wpService.updateFulfillment(this.site, String(orderId), fulfillmentId, data); return await this.wpService.updateFulfillment(this.site, String(orderId), fulfillmentId, data);
} }

View File

@ -16,6 +16,7 @@ import { OrderRefundItem } from '../entity/order_refund_item.entity';
import { OrderSale } from '../entity/order_sale.entity'; import { OrderSale } from '../entity/order_sale.entity';
import { OrderItemOriginal } from '../entity/order_item_original.entity'; import { OrderItemOriginal } from '../entity/order_item_original.entity';
import { OrderShipping } from '../entity/order_shipping.entity'; import { OrderShipping } from '../entity/order_shipping.entity';
import { OrderFulfillment } from '../entity/order_fulfillment.entity';
import { Service } from '../entity/service.entity'; import { Service } from '../entity/service.entity';
import { ShippingAddress } from '../entity/shipping_address.entity'; import { ShippingAddress } from '../entity/shipping_address.entity';
import { OrderNote } from '../entity/order_note.entity'; import { OrderNote } from '../entity/order_note.entity';
@ -67,6 +68,7 @@ export default {
ShipmentItem, ShipmentItem,
Shipment, Shipment,
OrderShipping, OrderShipping,
OrderFulfillment,
Service, Service,
ShippingAddress, ShippingAddress,
OrderNote, OrderNote,

View File

@ -9,6 +9,7 @@ import {
Put, Put,
Query, Query,
} from '@midwayjs/core'; } from '@midwayjs/core';
import { SyncOperationResult } from '../dto/api.dto';
import { ApiOkResponse } from '@midwayjs/swagger'; import { ApiOkResponse } from '@midwayjs/swagger';
import { import {
BooleanRes, BooleanRes,
@ -47,7 +48,7 @@ export class OrderController {
} }
@ApiOkResponse({ @ApiOkResponse({
type: BooleanRes, type: SyncOperationResult,
}) })
@Post('/syncOrder/:siteId/order/:orderId') @Post('/syncOrder/:siteId/order/:orderId')
async syncOrderById( async syncOrderById(

View File

@ -1017,7 +1017,7 @@ export class SiteApiController {
body.orders.map(order => body.orders.map(order =>
adapter.createOrderFulfillment(order.order_id, { adapter.createOrderFulfillment(order.order_id, {
tracking_number: order.tracking_number, tracking_number: order.tracking_number,
tracking_provider: order.shipping_provider, shipping_provider: order.shipping_provider,
items: order.items, items: order.items,
}).catch(error => ({ }).catch(error => ({
order_id: order.order_id, order_id: order.order_id,
@ -1081,9 +1081,10 @@ export class SiteApiController {
const adapter = await this.siteApiService.getAdapter(siteId); const adapter = await this.siteApiService.getAdapter(siteId);
const data = await adapter.createOrderFulfillment(orderId, { const data = await adapter.createOrderFulfillment(orderId, {
tracking_number: body.tracking_number, tracking_number: body.tracking_number,
tracking_provider: body.tracking_provider, shipping_provider: body.shipping_provider,
date_shipped: body.date_shipped, shipping_method: body.shipping_method,
status_shipped: body.status_shipped, status: body.status,
date_created: body.date_created,
}); });
this.logger.info(`[Site API] 创建订单履约跟踪信息成功, siteId: ${siteId}, orderId: ${orderId}`); this.logger.info(`[Site API] 创建订单履约跟踪信息成功, siteId: ${siteId}, orderId: ${orderId}`);
return successResponse(data); return successResponse(data);
@ -1107,9 +1108,10 @@ export class SiteApiController {
const adapter = await this.siteApiService.getAdapter(siteId); const adapter = await this.siteApiService.getAdapter(siteId);
const data = await adapter.updateOrderFulfillment(orderId, fulfillmentId, { const data = await adapter.updateOrderFulfillment(orderId, fulfillmentId, {
tracking_number: body.tracking_number, tracking_number: body.tracking_number,
tracking_provider: body.tracking_provider, shipping_provider: body.shipping_provider,
date_shipped: body.date_shipped, shipping_method: body.shipping_method,
status_shipped: body.status_shipped, status: body.status,
date_created: body.date_created,
}); });
this.logger.info(`[Site API] 更新订单履约跟踪信息成功, siteId: ${siteId}, orderId: ${orderId}, fulfillmentId: ${fulfillmentId}`); this.logger.info(`[Site API] 更新订单履约跟踪信息成功, siteId: ${siteId}, orderId: ${orderId}, fulfillmentId: ${fulfillmentId}`);
return successResponse(data); return successResponse(data);

View File

@ -81,7 +81,14 @@ export interface BatchOperationResult {
/** /**
* *
*/ */
export interface SyncOperationResult extends BatchOperationResult { export class SyncOperationResult implements BatchOperationResult {
total: number;
processed: number;
created?: number;
updated?: number;
deleted?: number;
skipped?: number;
errors: BatchErrorItem[];
// 同步成功数量 // 同步成功数量
synced: number; synced: number;
} }

View File

@ -187,18 +187,39 @@ export interface ShopyyOrder {
transaction_no?: string; transaction_no?: string;
}>; }>;
fulfillments?: Array<{ fulfillments?: Array<{
// 物流回传状态
payment_tracking_status?: number; payment_tracking_status?: number;
// 备注
note?: string; note?: string;
// 更新时间
updated_at?: number; updated_at?: number;
// 追踪接口编号
courier_code?: string; courier_code?: string;
// 物流公司 id
courier_id?: number; courier_id?: number;
// 创建时间
created_at?: number; created_at?: number;
tracking_number?: string;
id?: number; id?: number;
// 物流单号
tracking_number?: string;
// 物流公司名称
tracking_company?: string; tracking_company?: string;
// 物流回传结果
payment_tracking_result?: string; payment_tracking_result?: string;
// 物流回传时间
payment_tracking_at?: number; payment_tracking_at?: number;
products?: Array<{ order_product_id?: number; quantity?: number; updated_at?: number; created_at?: number; id?: number }>; // 商品
products?: Array<{
// 订单商品表 id
order_product_id?: number;
// 数量
quantity?: number;
// 更新时间
updated_at?: number;
// 创建时间
created_at?: number;
// 发货商品表 id
id?: number }>;
}>; }>;
shipping_zone_plans?: Array<{ shipping_zone_plans?: Array<{
shipping_price?: number | string; shipping_price?: number | string;
@ -425,9 +446,9 @@ export class ShopyPartFulfillmentDTO {
// https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse // https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
export class ShopyyCancelFulfillmentDTO { export class ShopyyCancelFulfillmentDTO {
order_id: string; order_id: string;
fullfillment_id: string; fulfillment_id: string;
} }
export class ShopyyBatchFulfillmentItemDTO { export class ShopyyBatchFulfillmentItemDTO {
fullfillments: ShopyPartFulfillmentDTO[] fulfillments: ShopyPartFulfillmentDTO[]
} }

View File

@ -5,6 +5,18 @@ import {
// export class UnifiedOrderWhere{ // export class UnifiedOrderWhere{
// [] // []
// } // }
export enum OrderFulfillmentStatus {
// 未发货
PENDING,
// 部分发货
PARTIALLY_FULFILLED,
// 已发货
FULFILLED,
// 已取消
CANCELLED,
// 确认发货
CONFIRMED,
}
export class UnifiedTagDTO { export class UnifiedTagDTO {
// 标签DTO用于承载统一标签数据 // 标签DTO用于承载统一标签数据
@ApiProperty({ description: '标签ID' }) @ApiProperty({ description: '标签ID' })
@ -21,23 +33,6 @@ export class UnifiedCategoryDTO {
@ApiProperty({ description: '分类名称' }) @ApiProperty({ description: '分类名称' })
name: string; name: string;
} }
// 订单跟踪号
export class UnifiedOrderTrackingDTO {
@ApiProperty({ description: '订单ID' })
order_id: string;
@ApiProperty({ description: '快递公司' })
tracking_provider: string;
@ApiProperty({ description: '运单跟踪号' })
tracking_number: string;
@ApiProperty({ description: '发货日期' })
date_shipped: string;
@ApiProperty({ description: '发货状态' })
status_shipped: string;
}
export class UnifiedImageDTO { export class UnifiedImageDTO {
// 图片DTO用于承载统一图片数据 // 图片DTO用于承载统一图片数据
@ -320,6 +315,9 @@ export class UnifiedOrderDTO {
@ApiProperty({ description: '订单状态' }) @ApiProperty({ description: '订单状态' })
status: string; status: string;
@ApiProperty({ description: '财务状态',nullable: true })
financial_status?: string;
@ApiProperty({ description: '货币' }) @ApiProperty({ description: '货币' })
currency: string; currency: string;
@ -386,9 +384,6 @@ export class UnifiedOrderDTO {
@ApiProperty({ description: '优惠券项', type: () => [UnifiedCouponLineDTO], required: false }) @ApiProperty({ description: '优惠券项', type: () => [UnifiedCouponLineDTO], required: false })
coupon_lines?: UnifiedCouponLineDTO[]; coupon_lines?: UnifiedCouponLineDTO[];
@ApiProperty({ description: '物流追踪信息', type: () => [UnifiedOrderTrackingDTO], required: false })
tracking?: UnifiedOrderTrackingDTO[];
@ApiProperty({ description: '支付时间', required: false }) @ApiProperty({ description: '支付时间', required: false })
date_paid?: string | null; date_paid?: string | null;
@ -403,6 +398,12 @@ export class UnifiedOrderDTO {
@ApiProperty({ description: '来源类型', required: false }) @ApiProperty({ description: '来源类型', required: false })
source_type?: string; source_type?: string;
@ApiProperty({ description: '订单状态', required: false })
fulfillment_status?: OrderFulfillmentStatus;
// 物流信息
@ApiProperty({ description: '物流信息', type: () => [FulfillmentDTO], required: false })
fulfillments?: FulfillmentDTO[];
} }
export class UnifiedShippingLineDTO { export class UnifiedShippingLineDTO {
@ -798,6 +799,12 @@ export class FulfillmentDTO {
@ApiProperty({ description: '发货方式', required: false }) @ApiProperty({ description: '发货方式', required: false })
shipping_method?: string; shipping_method?: string;
@ApiProperty({ description: '状态', required: false })
status?: string;
@ApiProperty({ description: '创建时间', required: false })
date_created?: string;
@ApiProperty({ description: '发货商品项', type: () => [FulfillmentItemDTO], required: false }) @ApiProperty({ description: '发货商品项', type: () => [FulfillmentItemDTO], required: false })
items?: FulfillmentItemDTO[]; items?: FulfillmentItemDTO[];
} }
@ -957,3 +964,24 @@ export class BatchFulfillmentsDTO {
@ApiProperty({ description: '批量发货订单列表', type: () => [BatchFulfillmentItemDTO] }) @ApiProperty({ description: '批量发货订单列表', type: () => [BatchFulfillmentItemDTO] })
orders: BatchFulfillmentItemDTO[]; orders: BatchFulfillmentItemDTO[];
} }
// 订单跟踪号
export class UnifiedOrderTrackingDTO {
@ApiProperty({ description: '订单ID' })
order_id: string;
@ApiProperty({ description: '物流单号' })
tracking_number: string;
@ApiProperty({ description: '物流公司' })
shipping_provider: string;
@ApiProperty({ description: '发货方式' })
shipping_method?: string;
@ApiProperty({ description: '状态' })
status?: string;
@ApiProperty({ description: '创建时间' })
date_created?: string;
}

View File

@ -370,11 +370,16 @@ export interface WooOrder {
date_modified?: string; date_modified?: string;
date_modified_gmt?: string; date_modified_gmt?: string;
// 物流追踪信息 // 物流追踪信息
trackings?: Array<{ fulfillments?: Array<{
tracking_provider?: string;
tracking_number?: string; tracking_number?: string;
date_shipped?: string; shipping_provider?: string;
status_shipped?: string; shipping_method?: string;
status?: string;
date_created?: string;
items?: Array<{
order_item_id?: number;
quantity?: number;
}>;
}>; }>;
} }
export interface WooOrderRefund { export interface WooOrderRefund {

View File

@ -0,0 +1,86 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity('order_fulfillment')
@Exclude()
export class OrderFulfillment {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@Column({ name: 'order_id', nullable: true })
@Expose()
order_id: number; // 订单 ID
@ApiProperty()
@Column({ name: 'site_id', nullable: true })
@Expose()
site_id: number; // 站点ID
@ApiProperty()
@Column({ name: 'external_order_id', nullable: true })
@Expose()
external_order_id: string; // 外部订单 ID
@ApiProperty()
@Column({ name: 'external_fulfillment_id', nullable: true })
@Expose()
external_fulfillment_id: string; // 外部履约 ID
@ApiProperty()
@Column({ name: 'tracking_number' })
@Expose()
tracking_number: string; // 物流单号
@ApiProperty()
@Column({ name: 'shipping_provider', nullable: true })
@Expose()
shipping_provider: string; // 物流公司
@ApiProperty()
@Column({ name: 'shipping_method', nullable: true })
@Expose()
shipping_method: string; // 发货方式
@ApiProperty()
@Column({ nullable: true })
@Expose()
status: string; // 状态
@ApiProperty()
@Column({ name: 'date_created', type: 'timestamp', nullable: true })
@Expose()
date_created: Date; // 创建时间
@ApiProperty()
@Column({ type: 'json', nullable: true })
@Expose()
items: any[]; // 发货商品项
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn({ name: 'created_at' })
@Expose()
created_at: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn({ name: 'updated_at' })
@Expose()
updated_at: Date;
}

View File

@ -37,7 +37,7 @@ export class Site {
@Column({ default: false }) @Column({ default: false })
isDisabled: boolean; isDisabled: boolean;
@ManyToMany(() => Area) @ManyToMany(() => Area, { cascade: true })
@JoinTable({ @JoinTable({
name: 'site_areas_area', name: 'site_areas_area',
joinColumn: { joinColumn: {

View File

@ -238,10 +238,14 @@ export interface ISiteAdapter {
*/ */
createOrderFulfillment(orderId: string | number, data: { createOrderFulfillment(orderId: string | number, data: {
tracking_number: string; tracking_number: string;
tracking_provider: string; shipping_provider: string;
date_shipped?: string; shipping_method?: string;
status_shipped?: string; status?: string;
items?: any[]; date_created?: string;
items?: Array<{
order_item_id: number;
quantity: number;
}>;
}): Promise<any>; }): Promise<any>;
/** /**
@ -249,9 +253,14 @@ export interface ISiteAdapter {
*/ */
updateOrderFulfillment(orderId: string | number, fulfillmentId: string, data: { updateOrderFulfillment(orderId: string | number, fulfillmentId: string, data: {
tracking_number?: string; tracking_number?: string;
tracking_provider?: string; shipping_provider?: string;
date_shipped?: string; shipping_method?: string;
status_shipped?: string; status?: string;
date_created?: string;
items?: Array<{
order_item_id: number;
quantity: number;
}>;
}): Promise<any>; }): Promise<any>;
/** /**

File diff suppressed because it is too large Load Diff

View File

@ -1025,7 +1025,7 @@ export class ShopyyService {
*/ */
async cancelFulfillment(site: any, data: { async cancelFulfillment(site: any, data: {
order_id: string; order_id: string;
fullfillment_id: string; fulfillment_id: string;
}): Promise<boolean> { }): Promise<boolean> {
try { try {
// ShopYY API: POST /orders/fulfillment/cancel // ShopYY API: POST /orders/fulfillment/cancel

View File

@ -5,6 +5,7 @@ import { Site } from '../entity/site.entity';
import { CreateSiteDTO, UpdateSiteDTO } from '../dto/site.dto'; import { CreateSiteDTO, UpdateSiteDTO } from '../dto/site.dto';
import { Area } from '../entity/area.entity'; import { Area } from '../entity/area.entity';
import { StockPoint } from '../entity/stock_point.entity'; import { StockPoint } from '../entity/stock_point.entity';
import * as countries from 'i18n-iso-countries';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Singleton)
@ -25,12 +26,26 @@ export class SiteService {
const newSite = new Site(); const newSite = new Site();
Object.assign(newSite, restData); Object.assign(newSite, restData);
// 如果传入了区域代码,则查询并关联 Area 实体 // 如果传入了区域代码,则查询或创建 Area 实体
if (areaCodes && areaCodes.length > 0) { if (areaCodes && areaCodes.length > 0) {
const areas = await this.areaModel.findBy({ // 先查询数据库中已存在的 Area 实体
const existingAreas = await this.areaModel.findBy({
code: In(areaCodes), code: In(areaCodes),
}); });
newSite.areas = areas; const existingCodes = new Set(existingAreas.map(area => area.code));
// 为不存在的 Area 创建新实体
const newAreas = areaCodes
.filter(code => !existingCodes.has(code))
.map(areaCode => {
const area = new Area();
area.code = areaCode;
area.name = countries.getName(areaCode, 'zh') || areaCode; // 使用 countries 获取中文名称,如果获取不到则使用 code
return area;
});
// 合并已存在和新创建的 Area 实体
newSite.areas = [...existingAreas, ...newAreas];
} else { } else {
// 如果没有传入区域,则关联一个空数组,代表"全局" // 如果没有传入区域,则关联一个空数组,代表"全局"
newSite.areas = []; newSite.areas = [];
@ -74,11 +89,25 @@ export class SiteService {
// 如果 DTO 中传入了 areas 字段(即使是空数组),也要更新关联关系 // 如果 DTO 中传入了 areas 字段(即使是空数组),也要更新关联关系
if (areaCodes !== undefined) { if (areaCodes !== undefined) {
if (areaCodes.length > 0) { if (areaCodes.length > 0) {
// 如果区域代码数组不为空,则查找并更新关联 // 先查询数据库中已存在的 Area 实体
const areas = await this.areaModel.findBy({ const existingAreas = await this.areaModel.findBy({
code: In(areaCodes), code: In(areaCodes),
}); });
siteToUpdate.areas = areas; const existingCodes = new Set(existingAreas.map(area => area.code));
// 为不存在的 Area 创建新实体
const newAreas = areaCodes
.filter(code => !existingCodes.has(code))
.map(areaCode => {
const area = new Area();
area.code = areaCode;
// name 使用 i18n-contries 获取
area.name = countries.getName(areaCode, 'zh') || areaCode; // 使用 code 作为 name 的默认值
return area;
});
// 合并已存在和新创建的 Area 实体
siteToUpdate.areas = [...existingAreas, ...newAreas];
} else { } else {
// 如果传入空数组,则清空所有关联,代表"全局" // 如果传入空数组,则清空所有关联,代表"全局"
siteToUpdate.areas = []; siteToUpdate.areas = [];
@ -98,7 +127,7 @@ export class SiteService {
} }
// 使用 save 方法保存实体及其更新后的关联关系 // 使用 save 方法保存实体及其更新后的关联关系
await this.siteModel.save(siteToUpdate); await this.siteModel.save(siteToUpdate);
return true; return true;
} }

View File

@ -719,9 +719,14 @@ export class WPService implements IPlatformService {
fulfillmentId: string, fulfillmentId: string,
data: { data: {
tracking_number?: string; tracking_number?: string;
tracking_provider?: string; shipping_provider?: string;
date_shipped?: string; shipping_method?: string;
status_shipped?: string; status?: string;
date_created?: string;
items?: Array<{
order_item_id: number;
quantity: number;
}>;
} }
): Promise<any> { ): Promise<any> {
const apiUrl = site.apiUrl; const apiUrl = site.apiUrl;
@ -732,20 +737,28 @@ export class WPService implements IPlatformService {
const fulfillmentData: any = {}; const fulfillmentData: any = {};
if (data.tracking_provider !== undefined) { if (data.shipping_provider !== undefined) {
fulfillmentData.tracking_provider = data.tracking_provider; fulfillmentData.shipping_provider = data.shipping_provider;
} }
if (data.tracking_number !== undefined) { if (data.tracking_number !== undefined) {
fulfillmentData.tracking_number = data.tracking_number; fulfillmentData.tracking_number = data.tracking_number;
} }
if (data.date_shipped !== undefined) { if (data.shipping_method !== undefined) {
fulfillmentData.date_shipped = data.date_shipped; fulfillmentData.shipping_method = data.shipping_method;
} }
if (data.status_shipped !== undefined) { if (data.status !== undefined) {
fulfillmentData.status_shipped = data.status_shipped; fulfillmentData.status = data.status;
}
if (data.date_created !== undefined) {
fulfillmentData.date_created = data.date_created;
}
if (data.items !== undefined) {
fulfillmentData.items = data.items;
} }
const config: AxiosRequestConfig = { const config: AxiosRequestConfig = {