forked from yoone/API
feat(订单): 重构订单履约功能并完善相关实体和服务
refactor(订单服务): 优化订单同步逻辑,增加履约信息处理 feat(实体): 新增订单履约实体并更新关联关系 fix(适配器): 修正Shopyy和WooCommerce履约数据映射 docs(dto): 更新订单和履约相关DTO定义 style: 格式化代码并修正拼写错误
This commit is contained in:
parent
99bd7009cc
commit
934085fd64
|
|
@ -15,7 +15,9 @@ 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 {
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +243,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
price: String(p.price ?? ''),
|
price: String(p.price ?? ''),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
// 货币符号
|
||||||
const currencySymbols: Record<string, string> = {
|
const currencySymbols: Record<string, string> = {
|
||||||
'EUR': '€',
|
'EUR': '€',
|
||||||
'USD': '$',
|
'USD': '$',
|
||||||
|
|
@ -244,13 +258,17 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
'SGD': 'S$'
|
'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 {
|
||||||
// 处理多地址结构
|
// 处理多地址结构
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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[]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 = [];
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue