903 lines
31 KiB
TypeScript
903 lines
31 KiB
TypeScript
import { ISiteAdapter } from '../interface/site-adapter.interface';
|
||
import { ShopyyService } from '../service/shopyy.service';
|
||
import {
|
||
UnifiedCustomerDTO,
|
||
UnifiedMediaDTO,
|
||
UnifiedOrderDTO,
|
||
UnifiedOrderLineItemDTO,
|
||
UnifiedProductDTO,
|
||
UnifiedProductVariationDTO,
|
||
UnifiedSubscriptionDTO,
|
||
UnifiedReviewPaginationDTO,
|
||
UnifiedReviewDTO,
|
||
UnifiedWebhookDTO,
|
||
UnifiedWebhookPaginationDTO,
|
||
CreateWebhookDTO,
|
||
UpdateWebhookDTO,
|
||
UnifiedAddressDTO,
|
||
UnifiedShippingLineDTO,
|
||
OrderFulfillmentStatus,
|
||
FulfillmentDTO
|
||
} from '../dto/site-api.dto';
|
||
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, } from '../dto/api.dto';
|
||
import {
|
||
ShopyyCustomer,
|
||
ShopyyOrder,
|
||
ShopyyProduct,
|
||
ShopyyVariant,
|
||
ShopyyWebhook,
|
||
} from '../dto/shopyy.dto';
|
||
import {
|
||
OrderStatus,
|
||
} from '../enums/base.enum';
|
||
export class ShopyyAdapter implements ISiteAdapter {
|
||
shopyyFinancialStatusMap= {
|
||
'200': '待支付',
|
||
'210': "支付中",
|
||
'220':"部分支付",
|
||
'230':"已支付",
|
||
'240':"支付失败",
|
||
'250':"部分退款",
|
||
'260':"已退款",
|
||
'290':"已取消",
|
||
}
|
||
constructor(private site: any, private shopyyService: ShopyyService) {
|
||
this.mapCustomer = this.mapCustomer.bind(this);
|
||
this.mapProduct = this.mapProduct.bind(this);
|
||
this.mapVariation = this.mapVariation.bind(this);
|
||
this.mapOrder = this.mapOrder.bind(this);
|
||
this.mapMedia = this.mapMedia.bind(this);
|
||
// this.mapSubscription = this.mapSubscription.bind(this);
|
||
}
|
||
|
||
private mapMedia(item: any): UnifiedMediaDTO {
|
||
// 映射媒体项目
|
||
return {
|
||
id: item.id,
|
||
date_created: item.created_at,
|
||
date_modified: item.updated_at,
|
||
source_url: item.src,
|
||
title: item.alt || '',
|
||
media_type: '', // Shopyy API未提供,暂时留空
|
||
mime_type: '', // Shopyy API未提供,暂时留空
|
||
};
|
||
}
|
||
|
||
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 shopyyParams;
|
||
}
|
||
|
||
private mapProduct(item: ShopyyProduct & { permalink?: string }): UnifiedProductDTO {
|
||
// 映射产品状态
|
||
function mapProductStatus(status: number) {
|
||
return status === 1 ? 'publish' : 'draft';
|
||
}
|
||
return {
|
||
id: item.id,
|
||
name: item.name || item.title,
|
||
type: String(item.product_type ?? ''),
|
||
status: mapProductStatus(item.status),
|
||
sku: item.variant?.sku || '',
|
||
regular_price: String(item.variant?.price ?? ''),
|
||
sale_price: String(item.special_price ?? ''),
|
||
price: String(item.price ?? ''),
|
||
stock_status: item.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
||
stock_quantity: item.inventory_quantity,
|
||
images: (item.images || []).map((img: any) => ({
|
||
id: img.id || 0,
|
||
src: img.src,
|
||
name: '',
|
||
alt: img.alt || '',
|
||
// 排序
|
||
position: img.position || '',
|
||
})),
|
||
attributes: (item.options || []).map(option => ({
|
||
id: option.id || 0,
|
||
name: option.option_name || '',
|
||
options: (option.values || []).map(value => value.option_value || ''),
|
||
})),
|
||
tags: (item.tags || []).map((t: any) => ({
|
||
id: t.id || 0,
|
||
name: t.name || '',
|
||
})),
|
||
// shopyy叫做专辑
|
||
categories: item.collections.map((c: any) => ({
|
||
id: c.id || 0,
|
||
name: c.title || '',
|
||
})),
|
||
variations: item.variants?.map(this.mapVariation.bind(this)) || [],
|
||
permalink: item.permalink,
|
||
date_created:
|
||
typeof item.created_at === 'number'
|
||
? new Date(item.created_at * 1000).toISOString()
|
||
: String(item.created_at ?? ''),
|
||
date_modified:
|
||
typeof item.updated_at === 'number'
|
||
? new Date(item.updated_at * 1000).toISOString()
|
||
: String(item.updated_at ?? ''),
|
||
raw: item,
|
||
};
|
||
}
|
||
|
||
private mapVariation(variant: ShopyyVariant): UnifiedProductVariationDTO {
|
||
// 映射变体
|
||
return {
|
||
id: variant.id,
|
||
name: variant.sku || '',
|
||
sku: variant.sku || '',
|
||
regular_price: String(variant.price ?? ''),
|
||
sale_price: String(variant.special_price ?? ''),
|
||
price: String(variant.price ?? ''),
|
||
stock_status:
|
||
variant.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
||
stock_quantity: variant.inventory_quantity,
|
||
};
|
||
}
|
||
|
||
shopyyOrderStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending
|
||
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
|
||
// 已发货
|
||
|
||
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
|
||
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
|
||
}
|
||
private mapOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
||
// 提取账单和送货地址 如果不存在则为空对象
|
||
const billing = (item as any).billing_address || {};
|
||
const shipping = (item as any).shipping_address || {};
|
||
|
||
// 构建账单地址对象
|
||
const billingObj: UnifiedAddressDTO = {
|
||
first_name: billing.first_name || item.firstname || '',
|
||
last_name: billing.last_name || item.lastname || '',
|
||
fullname: billing.name || `${item.firstname} ${item.lastname}`.trim(),
|
||
company: billing.company || '',
|
||
email: item.customer_email || item.email || '',
|
||
phone: billing.phone || (item as any).telephone || '',
|
||
address_1: billing.address1 || item.payment_address || '',
|
||
address_2: billing.address2 || '',
|
||
city: billing.city || item.payment_city || '',
|
||
state: billing.province || item.payment_zone || '',
|
||
postcode: billing.zip || item.payment_postcode || '',
|
||
method_title: item.payment_method || '',
|
||
country:
|
||
billing.country_name ||
|
||
billing.country_code ||
|
||
item.payment_country ||
|
||
'',
|
||
};
|
||
|
||
// 构建送货地址对象
|
||
const shippingObj: UnifiedAddressDTO = {
|
||
first_name: shipping.first_name || item.firstname || '',
|
||
last_name: shipping.last_name || item.lastname || '',
|
||
fullname: shipping.name || '',
|
||
company: shipping.company || '',
|
||
address_1:
|
||
shipping.address1 ||
|
||
(typeof item.shipping_address === 'string'
|
||
? item.shipping_address
|
||
: '') ||
|
||
'',
|
||
address_2: shipping.address2 || '',
|
||
city: shipping.city || item.shipping_city || '',
|
||
state: shipping.province || item.shipping_zone || '',
|
||
postcode: shipping.zip || item.shipping_postcode || '',
|
||
method_title: item.payment_method || '',
|
||
country:
|
||
shipping.country_name ||
|
||
shipping.country_code ||
|
||
item.shipping_country ||
|
||
'',
|
||
};
|
||
|
||
// 构建送货地址对象
|
||
const shipping_lines: UnifiedShippingLineDTO[] = [
|
||
{
|
||
id: item.fulfillments?.[0]?.id || 0,
|
||
method_title: item.payment_method || '',
|
||
method_id: item.payment_method || '',
|
||
total: Number(item.current_shipping_price).toExponential(2) || '0.00',
|
||
total_tax: '0.00',
|
||
taxes: [],
|
||
meta_data: [],
|
||
},
|
||
];
|
||
|
||
// 格式化地址为字符串
|
||
const formatAddress = (addr: UnifiedAddressDTO) => {
|
||
return [
|
||
addr.fullname,
|
||
addr.company,
|
||
addr.address_1,
|
||
addr.address_2,
|
||
addr.city,
|
||
addr.state,
|
||
addr.postcode,
|
||
addr.country,
|
||
addr.phone,
|
||
]
|
||
.filter(Boolean)
|
||
.join(', ');
|
||
};
|
||
|
||
const lineItems: UnifiedOrderLineItemDTO[] = (item.products || []).map(
|
||
(p: any) => ({
|
||
id: p.id,
|
||
name: p.product_title || p.name,
|
||
product_id: p.product_id,
|
||
quantity: p.quantity,
|
||
total: String(p.price ?? ''),
|
||
sku: p.sku || p.sku_code || '',
|
||
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$'
|
||
// 可以根据需要添加更多货币代码和符号
|
||
};
|
||
|
||
// 映射订单状态,如果不存在则默认 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 {
|
||
id: item.id || item.order_id,
|
||
number: item.order_number || item.order_sn,
|
||
status,
|
||
financial_status: finalcial_status,
|
||
currency: item.currency_code || item.currency,
|
||
total: String(item.total_price ?? item.total_amount ?? ''),
|
||
customer_id: item.customer_id || item.user_id,
|
||
customer_name:
|
||
item.customer_name || `${item.firstname} ${item.lastname}`.trim(),
|
||
email: item.customer_email || item.email,
|
||
customer_email: item.customer_email || item.email,
|
||
line_items: lineItems,
|
||
sales: lineItems, // 兼容前端
|
||
billing: billingObj,
|
||
shipping: shippingObj,
|
||
billing_full_address: formatAddress(billingObj),
|
||
shipping_full_address: formatAddress(shippingObj),
|
||
payment_method: item.payment_method,
|
||
shipping_lines: shipping_lines || [],
|
||
fee_lines: item.fee_lines || [],
|
||
coupon_lines: item.coupon_lines || [],
|
||
customer_ip_address: item.ip || '',
|
||
device_type: item.source_device || '',
|
||
utm_source: item.utm_source || '',
|
||
source_type: 'shopyy', // FIXME
|
||
date_paid: typeof item.pay_at === 'number'
|
||
? item.pay_at === 0 ? null : new Date(item.pay_at * 1000).toISOString()
|
||
: null,
|
||
|
||
refunds: [],
|
||
currency_symbol: (currencySymbols[item.currency] || '$') || '',
|
||
date_created:
|
||
typeof item.created_at === 'number'
|
||
? new Date(item.created_at * 1000).toISOString()
|
||
: item.date_added ||
|
||
(typeof item.created_at === 'string' ? item.created_at : ''),
|
||
date_modified:
|
||
typeof item.updated_at === 'number'
|
||
? new Date(item.updated_at * 1000).toISOString()
|
||
: item.date_updated ||
|
||
item.last_modified ||
|
||
(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,
|
||
};
|
||
}
|
||
shopyyFulfillmentStatusMap = {
|
||
// 未发货
|
||
'300': OrderFulfillmentStatus.PENDING,
|
||
// 部分发货
|
||
'310': OrderFulfillmentStatus.PARTIALLY_FULFILLED,
|
||
// 已发货
|
||
'320': OrderFulfillmentStatus.FULFILLED,
|
||
// 已取消
|
||
'330': OrderFulfillmentStatus.CANCELLED,
|
||
// 确认发货
|
||
}
|
||
|
||
private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO {
|
||
// 处理多地址结构
|
||
const addresses = item.addresses || [];
|
||
const defaultAddress = item.default_address || (addresses.length > 0 ? addresses[0] : {});
|
||
|
||
// 尝试从地址列表中获取billing和shipping
|
||
// 如果没有明确区分,默认使用默认地址或第一个地址
|
||
const billingAddress = defaultAddress;
|
||
const shippingAddress = defaultAddress;
|
||
|
||
const billing = {
|
||
first_name: billingAddress.first_name || item.first_name || '',
|
||
last_name: billingAddress.last_name || item.last_name || '',
|
||
fullname: billingAddress.name || `${billingAddress.first_name || item.first_name || ''} ${billingAddress.last_name || item.last_name || ''}`.trim(),
|
||
company: billingAddress.company || '',
|
||
email: item.email || '',
|
||
phone: billingAddress.phone || item.contact || '',
|
||
address_1: billingAddress.address1 || '',
|
||
address_2: billingAddress.address2 || '',
|
||
city: billingAddress.city || '',
|
||
state: billingAddress.province || '',
|
||
postcode: billingAddress.zip || '',
|
||
country: billingAddress.country_name || billingAddress.country_code || item.country?.country_name || ''
|
||
};
|
||
|
||
const shipping = {
|
||
first_name: shippingAddress.first_name || item.first_name || '',
|
||
last_name: shippingAddress.last_name || item.last_name || '',
|
||
fullname: shippingAddress.name || `${shippingAddress.first_name || item.first_name || ''} ${shippingAddress.last_name || item.last_name || ''}`.trim(),
|
||
company: shippingAddress.company || '',
|
||
address_1: shippingAddress.address1 || '',
|
||
address_2: shippingAddress.address2 || '',
|
||
city: shippingAddress.city || '',
|
||
state: shippingAddress.province || '',
|
||
postcode: shippingAddress.zip || '',
|
||
country: shippingAddress.country_name || shippingAddress.country_code || item.country?.country_name || ''
|
||
};
|
||
|
||
return {
|
||
id: item.id || item.customer_id,
|
||
orders: Number(item.orders_count ?? item.order_count ?? item.orders ?? 0),
|
||
total_spend: Number(item.total_spent ?? item.total_spend_amount ?? item.total_spend_money ?? 0),
|
||
first_name: item.first_name || item.firstname || '',
|
||
last_name: item.last_name || item.lastname || '',
|
||
fullname: item.fullname || item.customer_name || `${item.first_name || item.firstname || ''} ${item.last_name || item.lastname || ''}`.trim(),
|
||
email: item.email || item.customer_email || '',
|
||
phone: item.contact || billing.phone || item.phone || '',
|
||
billing,
|
||
shipping,
|
||
date_created:
|
||
typeof item.created_at === 'number'
|
||
? new Date(item.created_at * 1000).toISOString()
|
||
: (typeof item.created_at === 'string' ? item.created_at : item.date_added || ''),
|
||
date_modified:
|
||
typeof item.updated_at === 'number'
|
||
? new Date(item.updated_at * 1000).toISOString()
|
||
: (typeof item.updated_at === 'string' ? item.updated_at : item.date_updated || ''),
|
||
raw: item,
|
||
};
|
||
}
|
||
|
||
async getProducts(
|
||
params: UnifiedSearchParamsDTO
|
||
): Promise<UnifiedPaginationDTO<UnifiedProductDTO>> {
|
||
const response = await this.shopyyService.fetchResourcePaged<ShopyyProduct>(
|
||
this.site,
|
||
'products/list',
|
||
params
|
||
);
|
||
const { items = [], total, totalPages, page, per_page } = response;
|
||
const finalItems = items.map((item) => ({
|
||
...item,
|
||
permalink: `${this.site.websiteUrl}/products/${item.handle}`,
|
||
})).map(this.mapProduct.bind(this))
|
||
return {
|
||
items: finalItems as UnifiedProductDTO[],
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page,
|
||
};
|
||
}
|
||
|
||
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
||
// Shopyy getAllProducts 暂未实现
|
||
throw new Error('Shopyy getAllProducts 暂未实现');
|
||
}
|
||
|
||
async getProduct(id: string | number): Promise<UnifiedProductDTO> {
|
||
// 使用ShopyyService获取单个产品
|
||
const product = await this.shopyyService.getProduct(this.site, id);
|
||
return this.mapProduct(product);
|
||
}
|
||
|
||
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||
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.
|
||
// So I can't return the updated product.
|
||
// I have to fetch it again or return empty/input.
|
||
// Since getProduct is missing, I'll return input data as UnifiedProductDTO (mock).
|
||
await this.shopyyService.updateProduct(this.site, String(id), data);
|
||
return true;
|
||
}
|
||
|
||
async updateVariation(productId: string | number, variationId: string | number, data: any): Promise<any> {
|
||
await this.shopyyService.updateVariation(this.site, String(productId), String(variationId), data);
|
||
return { ...data, id: variationId };
|
||
}
|
||
|
||
async getOrderNotes(orderId: string | number): Promise<any[]> {
|
||
return await this.shopyyService.getOrderNotes(this.site, orderId);
|
||
}
|
||
|
||
async createOrderNote(orderId: string | number, data: any): Promise<any> {
|
||
return await this.shopyyService.createOrderNote(this.site, orderId, data);
|
||
}
|
||
|
||
async deleteProduct(id: string | number): Promise<boolean> {
|
||
// Use batch delete
|
||
await this.shopyyService.batchProcessProducts(this.site, { delete: [id] });
|
||
return true;
|
||
}
|
||
|
||
async batchProcessProducts(
|
||
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||
): Promise<any> {
|
||
return await this.shopyyService.batchProcessProducts(this.site, data);
|
||
}
|
||
mapUnifiedOrderQueryToShopyyQuery(params: UnifiedSearchParamsDTO) {
|
||
const { where = {} as any, ...restParams } = 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];
|
||
}
|
||
}
|
||
return normalizedParams
|
||
}
|
||
|
||
async getOrders(
|
||
params: UnifiedSearchParamsDTO
|
||
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
|
||
const normalizedParams = this.mapUnifiedOrderQueryToShopyyQuery(params);
|
||
const { items, total, totalPages, page, per_page } =
|
||
await this.shopyyService.fetchResourcePaged<any>(
|
||
this.site,
|
||
'orders',
|
||
normalizedParams
|
||
);
|
||
return {
|
||
items: items.map(this.mapOrder.bind(this)),
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page,
|
||
};
|
||
}
|
||
|
||
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
||
const data = await this.shopyyService.getAllOrders(this.site.id, params);
|
||
return data.map(this.mapOrder.bind(this));
|
||
}
|
||
|
||
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
|
||
const data = await this.shopyyService.getOrder(this.site.id, String(id));
|
||
return this.mapOrder(data);
|
||
}
|
||
|
||
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
|
||
const createdOrder = await this.shopyyService.createOrder(this.site, data);
|
||
return this.mapOrder(createdOrder);
|
||
}
|
||
|
||
async updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
||
return await this.shopyyService.updateOrder(this.site, String(id), data);
|
||
}
|
||
|
||
async deleteOrder(id: string | number): Promise<boolean> {
|
||
return await this.shopyyService.deleteOrder(this.site, id);
|
||
}
|
||
|
||
async fulfillOrder(orderId: string | number, data: {
|
||
tracking_number?: string;
|
||
shipping_provider?: string;
|
||
shipping_method?: string;
|
||
items?: Array<{
|
||
order_item_id: number;
|
||
quantity: number;
|
||
}>;
|
||
}): Promise<any> {
|
||
// 订单履行(发货)
|
||
try {
|
||
// 判断是否为部分发货(包含 items)
|
||
if (data.items && data.items.length > 0) {
|
||
// 部分发货
|
||
const partShipData = {
|
||
order_number: String(orderId),
|
||
note: data.shipping_method || '',
|
||
tracking_company: data.shipping_provider || '',
|
||
tracking_number: data.tracking_number || '',
|
||
courier_code: '1', // 默认快递公司代码
|
||
products: data.items.map(item => ({
|
||
quantity: item.quantity,
|
||
order_product_id: String(item.order_item_id)
|
||
}))
|
||
};
|
||
return await this.shopyyService.partFulfillOrder(this.site, partShipData);
|
||
} else {
|
||
// 批量发货(完整发货)
|
||
const batchShipData = {
|
||
order_number: String(orderId),
|
||
tracking_company: data.shipping_provider || '',
|
||
tracking_number: data.tracking_number || '',
|
||
courier_code: 1, // 默认快递公司代码
|
||
note: data.shipping_method || '',
|
||
mode: null // 新增模式
|
||
};
|
||
return await this.shopyyService.batchFulfillOrders(this.site, batchShipData);
|
||
}
|
||
} catch (error) {
|
||
throw new Error(`履行失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
async cancelFulfillment(orderId: string | number, data: {
|
||
reason?: string;
|
||
shipment_id?: string;
|
||
}): Promise<any> {
|
||
// 取消订单履行
|
||
try {
|
||
// 调用 ShopyyService 的取消履行方法
|
||
const cancelShipData = {
|
||
order_id: String(orderId),
|
||
fulfillment_id: data.shipment_id || ''
|
||
};
|
||
const result = await this.shopyyService.cancelFulfillment(this.site, cancelShipData);
|
||
|
||
return {
|
||
success: result,
|
||
order_id: orderId,
|
||
shipment_id: data.shipment_id,
|
||
reason: data.reason,
|
||
cancelled_at: new Date().toISOString()
|
||
};
|
||
} catch (error) {
|
||
throw new Error(`取消履行失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取订单履行信息
|
||
* @param orderId 订单ID
|
||
* @returns 履行信息列表
|
||
*/
|
||
async getOrderFulfillments(orderId: string | number): Promise<any[]> {
|
||
return await this.shopyyService.getFulfillments(this.site, String(orderId));
|
||
}
|
||
|
||
/**
|
||
* 创建订单履行信息
|
||
* @param orderId 订单ID
|
||
* @param data 履行数据
|
||
* @returns 创建结果
|
||
*/
|
||
async createOrderFulfillment(orderId: string | number, data: FulfillmentDTO): Promise<any> {
|
||
// 调用 Shopyy Service 的 createFulfillment 方法
|
||
const fulfillmentData = {
|
||
tracking_number: data.tracking_number,
|
||
carrier_code: data.shipping_provider,
|
||
carrier_name: data.shipping_provider,
|
||
shipping_method: data.shipping_method || 'standard'
|
||
};
|
||
|
||
return await this.shopyyService.createFulfillment(this.site, String(orderId), fulfillmentData);
|
||
}
|
||
|
||
/**
|
||
* 更新订单履行信息
|
||
* @param orderId 订单ID
|
||
* @param fulfillmentId 履行ID
|
||
* @param data 更新数据
|
||
* @returns 更新结果
|
||
*/
|
||
async updateOrderFulfillment(orderId: string | number, fulfillmentId: string, data: {
|
||
tracking_number?: string;
|
||
tracking_provider?: string;
|
||
date_shipped?: string;
|
||
status_shipped?: string;
|
||
}): Promise<any> {
|
||
return await this.shopyyService.updateFulfillment(this.site, String(orderId), fulfillmentId, data);
|
||
}
|
||
|
||
/**
|
||
* 删除订单履行信息
|
||
* @param orderId 订单ID
|
||
* @param fulfillmentId 履行ID
|
||
* @returns 删除结果
|
||
*/
|
||
async deleteOrderFulfillment(orderId: string | number, fulfillmentId: string): Promise<boolean> {
|
||
return await this.shopyyService.deleteFulfillment(this.site, String(orderId), fulfillmentId);
|
||
}
|
||
|
||
async getSubscriptions(
|
||
params: UnifiedSearchParamsDTO
|
||
): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>> {
|
||
throw new Error('Shopyy does not support subscriptions.');
|
||
}
|
||
|
||
async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]> {
|
||
// Shopyy getAllSubscriptions 暂未实现
|
||
throw new Error('Shopyy getAllSubscriptions 暂未实现');
|
||
}
|
||
|
||
async getMedia(
|
||
params: UnifiedSearchParamsDTO
|
||
): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> {
|
||
const requestParams = this.mapMediaSearchParams(params);
|
||
const { items, total, totalPages, page, per_page } = await this.shopyyService.fetchResourcePaged<any>(
|
||
this.site,
|
||
'media', // Shopyy的媒体API端点可能需要调整
|
||
requestParams
|
||
);
|
||
return {
|
||
items: items.map(this.mapMedia.bind(this)),
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page,
|
||
};
|
||
}
|
||
|
||
async getAllMedia(params?: UnifiedSearchParamsDTO): Promise<UnifiedMediaDTO[]> {
|
||
// Shopyy getAllMedia 暂未实现
|
||
throw new Error('Shopyy getAllMedia 暂未实现');
|
||
}
|
||
|
||
async createMedia(file: any): Promise<UnifiedMediaDTO> {
|
||
const createdMedia = await this.shopyyService.createMedia(this.site, file);
|
||
return this.mapMedia(createdMedia);
|
||
}
|
||
|
||
async updateMedia(id: string | number, data: any): Promise<UnifiedMediaDTO> {
|
||
const updatedMedia = await this.shopyyService.updateMedia(this.site, id, data);
|
||
return this.mapMedia(updatedMedia);
|
||
}
|
||
|
||
async deleteMedia(id: string | number): Promise<boolean> {
|
||
return await this.shopyyService.deleteMedia(this.site, id);
|
||
}
|
||
|
||
async getReviews(
|
||
params: UnifiedSearchParamsDTO
|
||
): Promise<UnifiedReviewPaginationDTO> {
|
||
const requestParams = this.mapReviewSearchParams(params);
|
||
const { items, total, totalPages, page, per_page } = await this.shopyyService.getReviews(
|
||
this.site,
|
||
requestParams
|
||
);
|
||
return {
|
||
items: items.map(this.mapReview),
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page,
|
||
};
|
||
}
|
||
|
||
async getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]> {
|
||
// Shopyy getAllReviews 暂未实现
|
||
throw new Error('Shopyy getAllReviews 暂未实现');
|
||
}
|
||
|
||
async getReview(id: string | number): Promise<UnifiedReviewDTO> {
|
||
const review = await this.shopyyService.getReview(this.site, id);
|
||
return this.mapReview(review);
|
||
}
|
||
|
||
private mapReview(review: any): UnifiedReviewDTO {
|
||
// 将ShopYY评论数据映射到统一评论DTO格式
|
||
return {
|
||
id: review.id || review.review_id,
|
||
product_id: review.product_id || review.goods_id,
|
||
author: review.author_name || review.username || '',
|
||
email: review.author_email || review.user_email || '',
|
||
content: review.comment || review.content || '',
|
||
rating: Number(review.score || review.rating || 0),
|
||
status: String(review.status || 'approved'),
|
||
date_created:
|
||
typeof review.created_at === 'number'
|
||
? new Date(review.created_at * 1000).toISOString()
|
||
: String(review.created_at || review.date_added || '')
|
||
};
|
||
}
|
||
|
||
private mapReviewSearchParams(params: UnifiedSearchParamsDTO): any {
|
||
const { search, page, per_page, where } = params;
|
||
const shopyyParams: any = {
|
||
page: page || 1,
|
||
limit: per_page || 10,
|
||
};
|
||
|
||
if (search) {
|
||
shopyyParams.search = search;
|
||
}
|
||
|
||
if (where.status) {
|
||
shopyyParams.status = where.status;
|
||
}
|
||
|
||
// if (product_id) {
|
||
// shopyyParams.product_id = product_id;
|
||
// }
|
||
|
||
return shopyyParams;
|
||
}
|
||
|
||
async createReview(data: any): Promise<UnifiedReviewDTO> {
|
||
const createdReview = await this.shopyyService.createReview(this.site, data);
|
||
return this.mapReview(createdReview);
|
||
}
|
||
|
||
async updateReview(id: string | number, data: any): Promise<UnifiedReviewDTO> {
|
||
const updatedReview = await this.shopyyService.updateReview(this.site, id, data);
|
||
return this.mapReview(updatedReview);
|
||
}
|
||
|
||
async deleteReview(id: string | number): Promise<boolean> {
|
||
return await this.shopyyService.deleteReview(this.site, id);
|
||
}
|
||
|
||
// Webhook相关方法
|
||
private mapWebhook(item: ShopyyWebhook): UnifiedWebhookDTO {
|
||
return {
|
||
id: item.id,
|
||
name: item.webhook_name || `Webhook-${item.id}`,
|
||
topic: item.event_code || '',
|
||
delivery_url: item.url || '',
|
||
status: 'active',
|
||
};
|
||
}
|
||
|
||
async getWebhooks(params: UnifiedSearchParamsDTO): Promise<UnifiedWebhookPaginationDTO> {
|
||
const { items, total, totalPages, page, per_page } = await this.shopyyService.getWebhooks(this.site, params);
|
||
return {
|
||
items: items.map(this.mapWebhook),
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page,
|
||
};
|
||
}
|
||
|
||
async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]> {
|
||
// Shopyy getAllWebhooks 暂未实现
|
||
throw new Error('Shopyy getAllWebhooks 暂未实现');
|
||
}
|
||
|
||
async getWebhook(id: string | number): Promise<UnifiedWebhookDTO> {
|
||
const webhook = await this.shopyyService.getWebhook(this.site, id);
|
||
return this.mapWebhook(webhook);
|
||
}
|
||
|
||
async createWebhook(data: CreateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
||
const createdWebhook = await this.shopyyService.createWebhook(this.site, data);
|
||
return this.mapWebhook(createdWebhook);
|
||
}
|
||
|
||
async updateWebhook(id: string | number, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
||
const updatedWebhook = await this.shopyyService.updateWebhook(this.site, id, data);
|
||
return this.mapWebhook(updatedWebhook);
|
||
}
|
||
|
||
async deleteWebhook(id: string | number): Promise<boolean> {
|
||
return await this.shopyyService.deleteWebhook(this.site, id);
|
||
}
|
||
|
||
async getLinks(): Promise<Array<{ title: string, url: string }>> {
|
||
// ShopYY站点的管理后台链接通常基于apiUrl构建
|
||
const url = this.site.websiteUrl
|
||
// 提取基础域名,去掉可能的路径部分
|
||
const baseUrl = url.replace(/\/api\/.*$/i, '');
|
||
|
||
const links = [
|
||
{ title: '访问网站', url: baseUrl },
|
||
{ title: '管理后台', url: `${baseUrl}/admin/` },
|
||
{ title: '订单管理', url: `${baseUrl}/admin/orders.htm` },
|
||
{ title: '产品管理', url: `${baseUrl}/admin/products.htm` },
|
||
{ title: '客户管理', url: `${baseUrl}/admin/customers.htm` },
|
||
{ title: '插件管理', url: `${baseUrl}/admin/apps.htm` },
|
||
{ title: '店铺设置', url: `${baseUrl}/admin/settings.htm` },
|
||
{ title: '营销中心', url: `${baseUrl}/admin/marketing.htm` },
|
||
];
|
||
return links;
|
||
}
|
||
|
||
async getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>> {
|
||
const { items, total, totalPages, page, per_page } =
|
||
await this.shopyyService.fetchCustomersPaged(this.site, params);
|
||
return {
|
||
items: items.map(this.mapCustomer.bind(this)),
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page
|
||
};
|
||
}
|
||
|
||
async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]> {
|
||
// Shopyy getAllCustomers 暂未实现
|
||
throw new Error('Shopyy getAllCustomers 暂未实现');
|
||
}
|
||
|
||
async getCustomer(id: string | number): Promise<UnifiedCustomerDTO> {
|
||
const customer = await this.shopyyService.getCustomer(this.site, id);
|
||
return this.mapCustomer(customer);
|
||
}
|
||
|
||
async createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||
const createdCustomer = await this.shopyyService.createCustomer(this.site, data);
|
||
return this.mapCustomer(createdCustomer);
|
||
}
|
||
|
||
async updateCustomer(id: string | number, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||
const updatedCustomer = await this.shopyyService.updateCustomer(this.site, id, data);
|
||
return this.mapCustomer(updatedCustomer);
|
||
}
|
||
|
||
async deleteCustomer(id: string | number): Promise<boolean> {
|
||
return await this.shopyyService.deleteCustomer(this.site, id);
|
||
}
|
||
|
||
async getVariations(productId: string | number, params: UnifiedSearchParamsDTO): Promise<any> {
|
||
throw new Error('Shopyy getVariations 暂未实现');
|
||
}
|
||
|
||
async getAllVariations(productId: string | number, params?: UnifiedSearchParamsDTO): Promise<UnifiedProductVariationDTO[]> {
|
||
throw new Error('Shopyy getAllVariations 暂未实现');
|
||
}
|
||
|
||
async getVariation(productId: string | number, variationId: string | number): Promise<UnifiedProductVariationDTO> {
|
||
throw new Error('Shopyy getVariation 暂未实现');
|
||
}
|
||
|
||
async createVariation(productId: string | number, data: any): Promise<UnifiedProductVariationDTO> {
|
||
throw new Error('Shopyy createVariation 暂未实现');
|
||
}
|
||
|
||
async deleteVariation(productId: string | number, variationId: string | number): Promise<boolean> {
|
||
throw new Error('Shopyy deleteVariation 暂未实现');
|
||
}
|
||
}
|