forked from yoone/API
1361 lines
47 KiB
TypeScript
1361 lines
47 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,
|
||
CreateReviewDTO,
|
||
CreateVariationDTO,
|
||
UpdateReviewDTO,
|
||
OrderPaymentStatus,
|
||
} from '../dto/site-api.dto';
|
||
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, ShopyyGetAllOrdersParams } from '../dto/api.dto';
|
||
import {
|
||
ShopyyAllProductQuery,
|
||
ShopyyCustomer,
|
||
ShopyyOrder,
|
||
ShopyyOrderCreateParams,
|
||
ShopyyOrderQuery,
|
||
ShopyyOrderUpdateParams,
|
||
ShopyyProduct,
|
||
ShopyyProductQuery,
|
||
ShopyyVariant,
|
||
ShopyyWebhook,
|
||
} from '../dto/shopyy.dto';
|
||
import {
|
||
OrderStatus,
|
||
} from '../enums/base.enum';
|
||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||
import dayjs = require('dayjs');
|
||
export class ShopyyAdapter implements ISiteAdapter {
|
||
shopyyFinancialStatusMap = {
|
||
'200': '待支付',
|
||
'210': "支付中",
|
||
'220': "部分支付",
|
||
'230': "已支付",
|
||
'240': "支付失败",
|
||
'250': "部分退款",
|
||
'260': "已退款",
|
||
'290': "已取消",
|
||
}
|
||
constructor(private site: any, private shopyyService: ShopyyService) {
|
||
this.mapPlatformToUnifiedCustomer = this.mapPlatformToUnifiedCustomer.bind(this);
|
||
this.mapPlatformToUnifiedProduct = this.mapPlatformToUnifiedProduct.bind(this);
|
||
this.mapPlatformToUnifiedVariation = this.mapPlatformToUnifiedVariation.bind(this);
|
||
this.mapPlatformToUnifiedOrder = this.mapPlatformToUnifiedOrder.bind(this);
|
||
this.mapPlatformToUnifiedMedia = this.mapPlatformToUnifiedMedia.bind(this);
|
||
// this.mapPlatformToUnifiedSubscription = this.mapPlatformToUnifiedSubscription.bind(this);
|
||
}
|
||
|
||
// ========== 客户映射方法 ==========
|
||
mapPlatformToUnifiedCustomer(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,
|
||
};
|
||
}
|
||
|
||
mapUnifiedToPlatformCustomer(data: Partial<UnifiedCustomerDTO>) {
|
||
return data
|
||
}
|
||
|
||
async getCustomer(where: { id?: string | number, email?: string, phone?: string }): Promise<UnifiedCustomerDTO> {
|
||
if (!where.id && !where.email && !where.phone) {
|
||
throw new Error('必须传入 id 或 email 或 phone')
|
||
}
|
||
const customer = await this.shopyyService.getCustomer(this.site, where.id);
|
||
return this.mapPlatformToUnifiedCustomer(customer);
|
||
}
|
||
|
||
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.mapPlatformToUnifiedCustomer.bind(this)),
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page
|
||
};
|
||
}
|
||
|
||
async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]> {
|
||
// Shopyy getAllCustomers 暂未实现
|
||
throw new Error('Shopyy getAllCustomers 暂未实现');
|
||
}
|
||
|
||
async createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||
const createdCustomer = await this.shopyyService.createCustomer(this.site, data);
|
||
return this.mapPlatformToUnifiedCustomer(createdCustomer);
|
||
}
|
||
|
||
async updateCustomer(where: { id: string | number }, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||
const updatedCustomer = await this.shopyyService.updateCustomer(this.site, where.id, data);
|
||
return this.mapPlatformToUnifiedCustomer(updatedCustomer);
|
||
}
|
||
|
||
async deleteCustomer(where: { id: string | number }): Promise<boolean> {
|
||
return await this.shopyyService.deleteCustomer(this.site, where.id);
|
||
}
|
||
|
||
batchProcessCustomers?(data: BatchOperationDTO): Promise<BatchOperationResultDTO> {
|
||
throw new Error('Method not implemented.');
|
||
}
|
||
|
||
// ========== 媒体映射方法 ==========
|
||
mapPlatformToUnifiedMedia(data: any): UnifiedMediaDTO {
|
||
// 映射媒体项目
|
||
return {
|
||
id: data.id,
|
||
date_created: data.created_at,
|
||
date_modified: data.updated_at,
|
||
source_url: data.src,
|
||
title: data.alt || '',
|
||
media_type: '', // Shopyy API未提供,暂时留空
|
||
mime_type: '', // Shopyy API未提供,暂时留空
|
||
};
|
||
}
|
||
|
||
mapUnifiedToPlatformMedia(data: Partial<UnifiedMediaDTO>) {
|
||
return data
|
||
}
|
||
|
||
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.mapPlatformToUnifiedMedia.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.mapPlatformToUnifiedMedia(createdMedia);
|
||
}
|
||
|
||
async updateMedia(where: { id: string | number }, data: any): Promise<UnifiedMediaDTO> {
|
||
const updatedMedia = await this.shopyyService.updateMedia(this.site, where.id, data);
|
||
return this.mapPlatformToUnifiedMedia(updatedMedia);
|
||
}
|
||
|
||
async deleteMedia(where: { id: string | number }): Promise<boolean> {
|
||
return await this.shopyyService.deleteMedia(this.site, where.id);
|
||
}
|
||
|
||
mapMediaSearchParams(params: UnifiedSearchParamsDTO): any {
|
||
return this.mapSearchParams(params)
|
||
}
|
||
|
||
// ========== 订单映射方法 ==========
|
||
mapPlatformToUnifiedOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
||
// console.log(item)
|
||
if (!item) throw new Error('订单数据不能为空')
|
||
// 提取账单和送货地址 如果不存在则为空对象
|
||
const billing = item.billing_address || {};
|
||
const shipping = item.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 || '',
|
||
phone: shipping.phone || (item as any).telephone || '',
|
||
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(
|
||
(product) => ({
|
||
id: product.id,
|
||
name:product.sku_value?.[0]?.value || product.product_title || product.name,
|
||
product_id: product.product_id,
|
||
quantity: product.quantity,
|
||
total: String(product.price ?? ''),
|
||
sku: product.sku || product.sku_code || '',
|
||
price: String(product.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.fulfillmentStatusMap[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,
|
||
};
|
||
}
|
||
|
||
mapUnifiedToPlatformOrder(data: Partial<UnifiedOrderDTO>) {
|
||
return data
|
||
}
|
||
|
||
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): ShopyyOrderCreateParams {
|
||
return data
|
||
}
|
||
|
||
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): ShopyyOrderUpdateParams {
|
||
// 构建 ShopYY 订单更新参数(仅包含传入的字段)
|
||
const params: any = {};
|
||
|
||
// 仅当字段存在时才添加到更新参数中
|
||
if (data.status !== undefined) {
|
||
// 映射订单状态
|
||
const statusMap = {
|
||
[OrderStatus.PENDING]: 100, // pending -> 100 未完成
|
||
[OrderStatus.PROCESSING]: 110, // processing -> 110 待处理
|
||
[OrderStatus.COMPLETED]: 180, // completed -> 180 已完成
|
||
[OrderStatus.CANCEL]: 190 // cancel -> 190 取消
|
||
};
|
||
params.status = statusMap[data.status] || 100;
|
||
}
|
||
|
||
if (data.payment_method !== undefined) {
|
||
params.payment_method = data.payment_method;
|
||
}
|
||
|
||
if (data.billing) {
|
||
// 更新客户信息
|
||
if (data.billing.first_name !== undefined) {
|
||
params.firstname = data.billing.first_name;
|
||
}
|
||
if (data.billing.last_name !== undefined) {
|
||
params.lastname = data.billing.last_name;
|
||
}
|
||
if (data.billing.email !== undefined) {
|
||
params.email = data.billing.email;
|
||
}
|
||
if (data.billing.phone !== undefined) {
|
||
params.phone = data.billing.phone;
|
||
}
|
||
|
||
// 更新账单地址
|
||
params.billing_address = params?.billing_address || {};
|
||
if (data.billing.first_name !== undefined) {
|
||
params.billing_address.first_name = data.billing.first_name;
|
||
}
|
||
if (data.billing.last_name !== undefined) {
|
||
params.billing_address.last_name = data.billing.last_name;
|
||
}
|
||
if (data.billing.company !== undefined) {
|
||
params.billing_address.company = data.billing.company;
|
||
}
|
||
if (data.billing.address_1 !== undefined) {
|
||
params.billing_address.address1 = data.billing.address_1;
|
||
}
|
||
if (data.billing.address_2 !== undefined) {
|
||
params.billing_address.address2 = data.billing.address_2;
|
||
}
|
||
if (data.billing.city !== undefined) {
|
||
params.billing_address.city = data.billing.city;
|
||
}
|
||
if (data.billing.state !== undefined) {
|
||
params.billing_address.province = data.billing.state;
|
||
}
|
||
if (data.billing.postcode !== undefined) {
|
||
params.billing_address.zip = data.billing.postcode;
|
||
}
|
||
if (data.billing.country !== undefined) {
|
||
params.billing_address.country_code = data.billing.country;
|
||
}
|
||
}
|
||
|
||
if (data.shipping) {
|
||
// 更新送货地址
|
||
params.shipping_address = params.shipping_address || {};
|
||
if (data.shipping.first_name !== undefined) {
|
||
params.shipping_address.first_name = data.shipping.first_name;
|
||
}
|
||
if (data.shipping.last_name !== undefined) {
|
||
params.shipping_address.last_name = data.shipping.last_name;
|
||
}
|
||
if (data.shipping.company !== undefined) {
|
||
params.shipping_address.company = data.shipping.company;
|
||
}
|
||
if (data.shipping.address_1 !== undefined) {
|
||
params.shipping_address.address1 = data.shipping.address_1;
|
||
}
|
||
if (data.shipping.address_2 !== undefined) {
|
||
params.shipping_address.address2 = data.shipping.address_2;
|
||
}
|
||
if (data.shipping.city !== undefined) {
|
||
params.shipping_address.city = data.shipping.city;
|
||
}
|
||
if (data.shipping.state !== undefined) {
|
||
params.shipping_address.province = data.shipping.state;
|
||
}
|
||
if (data.shipping.postcode !== undefined) {
|
||
params.shipping_address.zip = data.shipping.postcode;
|
||
}
|
||
if (data.shipping.country !== undefined) {
|
||
params.shipping_address.country_code = data.shipping.country;
|
||
}
|
||
if (data.shipping.phone !== undefined) {
|
||
params.shipping_address.phone = data.shipping.phone;
|
||
}
|
||
}
|
||
|
||
// 更新订单项
|
||
if (data.line_items && data.line_items.length > 0) {
|
||
params.products = data.line_items.map((item: UnifiedOrderLineItemDTO) => ({
|
||
product_id: item.product_id,
|
||
quantity: item.quantity,
|
||
// price: item.price || '0.00',
|
||
sku: item.sku || '',
|
||
}));
|
||
}
|
||
|
||
// 更新物流信息
|
||
if (data.shipping_lines && data.shipping_lines.length > 0) {
|
||
const shippingLine = data.shipping_lines[0];
|
||
if (shippingLine.method_title !== undefined) {
|
||
params.shipping_method = shippingLine.method_title;
|
||
}
|
||
if (shippingLine.total !== undefined) {
|
||
params.shipping_price = shippingLine.total;
|
||
}
|
||
}
|
||
|
||
// // 更新备注信息
|
||
// if (data.note !== undefined) {
|
||
// params.note = data.note;
|
||
// }
|
||
|
||
return params;
|
||
}
|
||
|
||
async getOrder(where: { id: string | number }): Promise<UnifiedOrderDTO> {
|
||
const data = await this.getOrders({
|
||
where: {
|
||
id: where.id,
|
||
},
|
||
page: 1,
|
||
per_page: 1,
|
||
})
|
||
return data.items[0] || null
|
||
// const data = await this.shopyyService.getOrder(this.site.id, String(where.id));
|
||
// return this.mapPlatformToUnifiedOrder(data);
|
||
}
|
||
|
||
async getOrders(
|
||
params: UnifiedSearchParamsDTO
|
||
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
|
||
// 转换订单查询参数
|
||
const normalizedParams = this.mapOrderSearchParams(params);
|
||
const { items, total, totalPages, page, per_page } = await this.shopyyService.fetchResourcePaged<any>(
|
||
this.site,
|
||
'orders',
|
||
normalizedParams
|
||
);
|
||
return {
|
||
items: items.map(this.mapPlatformToUnifiedOrder.bind(this)),
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page,
|
||
};
|
||
}
|
||
mapGetAllOrdersParams(params: UnifiedSearchParamsDTO) :ShopyyGetAllOrdersParams{
|
||
|
||
const pay_at_min = dayjs(params.after || '').unix().toString();
|
||
const pay_at_max = dayjs(params.before || '').unix().toString();
|
||
|
||
return {
|
||
per_page: params.per_page || 100,
|
||
pay_at_min: pay_at_min,
|
||
pay_at_max: pay_at_max,
|
||
order_field: 'pay_at',
|
||
}
|
||
}
|
||
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
||
const normalizedParams = this.mapGetAllOrdersParams(params);
|
||
const data = await this.shopyyService.getAllOrders(this.site.id, normalizedParams);
|
||
return data.map(this.mapPlatformToUnifiedOrder.bind(this));
|
||
}
|
||
|
||
async countOrders(where: Record<string, any>): Promise<number> {
|
||
// 使用最小分页只获取总数
|
||
const searchParams = {
|
||
where,
|
||
page: 1,
|
||
per_page: 1,
|
||
}
|
||
const data = await this.getOrders(searchParams);
|
||
return data.total || 0;
|
||
}
|
||
|
||
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
|
||
// 使用映射方法转换参数
|
||
const requestParams = this.mapCreateOrderParams(data);
|
||
const createdOrder = await this.shopyyService.createOrder(this.site, requestParams);
|
||
return this.mapPlatformToUnifiedOrder(createdOrder);
|
||
}
|
||
|
||
async updateOrder(where: { id: string | number }, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
||
// 使用映射方法转换参数
|
||
const requestParams = this.mapUpdateOrderParams(data);
|
||
return await this.shopyyService.updateOrder(this.site, String(where.id), requestParams);
|
||
}
|
||
|
||
async deleteOrder(where: { id: string | number }): Promise<boolean> {
|
||
return await this.shopyyService.deleteOrder(this.site, where.id);
|
||
}
|
||
|
||
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 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}`);
|
||
}
|
||
}
|
||
|
||
async getOrderFulfillments(orderId: string | number): Promise<any[]> {
|
||
return await this.shopyyService.getFulfillments(this.site, String(orderId));
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
async deleteOrderFulfillment(orderId: string | number, fulfillmentId: string): Promise<boolean> {
|
||
return await this.shopyyService.deleteFulfillment(this.site, String(orderId), fulfillmentId);
|
||
}
|
||
|
||
batchProcessOrders?(data: BatchOperationDTO): Promise<BatchOperationResultDTO> {
|
||
throw new Error('Method not implemented.');
|
||
}
|
||
|
||
mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial<ShopyyOrderQuery> {
|
||
// 首先使用通用参数转换
|
||
const baseParams = this.mapSearchParams(params);
|
||
|
||
// 订单状态映射
|
||
const statusMap = {
|
||
'pending': '100', // 100 未完成
|
||
'processing': '110', // 110 待处理
|
||
'completed': "180", // 180 已完成(确认收货)
|
||
'cancelled': '190', // 190 取消
|
||
};
|
||
|
||
// 如果有状态参数,进行特殊映射
|
||
if (baseParams.status) {
|
||
const unifiedStatus = baseParams.status
|
||
if (statusMap[unifiedStatus]) {
|
||
baseParams.status = statusMap[unifiedStatus];
|
||
}
|
||
}
|
||
|
||
// 处理ID参数
|
||
if (baseParams.id) {
|
||
baseParams.ids = baseParams.id;
|
||
delete baseParams.id;
|
||
}
|
||
|
||
return baseParams;
|
||
}
|
||
|
||
// ========== 产品映射方法 ==========
|
||
mapPlatformToUnifiedProduct(item: ShopyyProduct): 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 || item.variant?.sku_code || '',
|
||
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.mapPlatformToUnifiedVariation.bind(this)) || [],
|
||
permalink: `${this.site.websiteUrl}/products/${item.handle}`,
|
||
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,
|
||
};
|
||
}
|
||
|
||
mapUnifiedToPlatformProduct(data: Partial<UnifiedProductDTO>) {
|
||
return data
|
||
}
|
||
|
||
mapCreateProductParams(data: Partial<UnifiedProductDTO>): Partial<ShopyyProduct> {
|
||
// 构建 ShopYY 产品创建参数
|
||
const params: any = {
|
||
name: data.name || '',
|
||
product_type: data.type || 1, // 默认简单产品
|
||
status: this.mapStatus(data.status || 'publish'),
|
||
price: data.price || '0.00',
|
||
special_price: data.sale_price || '',
|
||
inventory_tracking: data.stock_quantity !== undefined ? 1 : 0,
|
||
inventory_quantity: data.stock_quantity || 0,
|
||
};
|
||
|
||
// 添加变体信息
|
||
if (data.variations && data.variations.length > 0) {
|
||
params.variants = data.variations.map((variation: UnifiedProductVariationDTO) => ({
|
||
sku: variation.sku || '',
|
||
price: variation.price || '0.00',
|
||
special_price: variation.sale_price || '',
|
||
inventory_tracking: variation.stock_quantity !== undefined ? 1 : 0,
|
||
inventory_quantity: variation.stock_quantity || 0,
|
||
}));
|
||
}
|
||
|
||
// 添加图片信息
|
||
if (data.images && data.images.length > 0) {
|
||
params.images = data.images.map((image: any) => ({
|
||
src: image.src,
|
||
alt: image.alt || '',
|
||
position: image.position || 0,
|
||
}));
|
||
}
|
||
|
||
// 添加标签信息
|
||
if (data.tags && data.tags.length > 0) {
|
||
params.tags = data.tags.map((tag: any) => tag.name || '');
|
||
}
|
||
|
||
// 添加分类信息
|
||
if (data.categories && data.categories.length > 0) {
|
||
params.collections = data.categories.map((category: any) => ({
|
||
// id: category.id,
|
||
title: category.name,
|
||
}));
|
||
}
|
||
|
||
return params;
|
||
}
|
||
|
||
mapUpdateProductParams(data: Partial<UnifiedProductDTO>): any {
|
||
// 映射产品状态: publish -> 1, draft -> 0
|
||
const mapStatus = (status: string) => {
|
||
return status === 'publish' ? 1 : 0;
|
||
};
|
||
|
||
// 构建 ShopYY 产品更新参数(仅包含传入的字段)
|
||
const params: any = {};
|
||
|
||
// 仅当字段存在时才添加到更新参数中
|
||
if (data.name !== undefined) params.name = data.name;
|
||
if (data.type !== undefined) params.product_type = data.type;
|
||
if (data.status !== undefined) params.status = mapStatus(data.status);
|
||
if (data.price !== undefined) params.price = data.price;
|
||
if (data.sale_price !== undefined) params.special_price = data.sale_price;
|
||
if (data.sku !== undefined) params.sku = data.sku;
|
||
if (data.stock_quantity !== undefined) {
|
||
params.inventory_tracking = 1;
|
||
params.inventory_quantity = data.stock_quantity;
|
||
}
|
||
if (data.stock_status !== undefined) {
|
||
params.inventory_tracking = 1;
|
||
params.inventory_quantity = data.stock_status === 'instock' ? (data.stock_quantity || 1) : 0;
|
||
}
|
||
|
||
// 添加变体信息(如果存在)
|
||
if (data.variations && data.variations.length > 0) {
|
||
params.variants = data.variations.map((variation: UnifiedProductVariationDTO) => {
|
||
const variationParams: any = {};
|
||
if (variation.id !== undefined) variationParams.id = variation.id;
|
||
if (variation.sku !== undefined) variationParams.sku = variation.sku;
|
||
if (variation.price !== undefined) variationParams.price = variation.price;
|
||
if (variation.sale_price !== undefined) variationParams.special_price = variation.sale_price;
|
||
if (variation.stock_quantity !== undefined) {
|
||
variationParams.inventory_tracking = 1;
|
||
variationParams.inventory_quantity = variation.stock_quantity;
|
||
}
|
||
if (variation.stock_status !== undefined) {
|
||
variationParams.inventory_tracking = 1;
|
||
variationParams.inventory_quantity = variation.stock_status === 'instock' ? (variation.stock_quantity || 1) : 0;
|
||
}
|
||
return variationParams;
|
||
});
|
||
}
|
||
|
||
// 添加图片信息(如果存在)
|
||
if (data.images && data.images.length > 0) {
|
||
params.images = data.images.map((image: any) => ({
|
||
id: image.id,
|
||
src: image.src,
|
||
alt: image.alt || '',
|
||
position: image.position || 0,
|
||
}));
|
||
}
|
||
|
||
// 添加标签信息(如果存在)
|
||
if (data.tags && data.tags.length > 0) {
|
||
params.tags = data.tags.map((tag: any) => tag.name || '');
|
||
}
|
||
|
||
// 添加分类信息(如果存在)
|
||
if (data.categories && data.categories.length > 0) {
|
||
params.collections = data.categories.map((category: any) => ({
|
||
id: category.id,
|
||
title: category.name,
|
||
}));
|
||
}
|
||
|
||
return params;
|
||
}
|
||
|
||
async getProduct(where: { id?: string | number, sku?: string }): Promise<UnifiedProductDTO> {
|
||
if (!where.id && !where.sku) {
|
||
throw new Error('必须传入 id 或 sku')
|
||
}
|
||
if (where.id) {
|
||
// 使用ShopyyService获取单个产品
|
||
const product = await this.shopyyService.getProduct(this.site, where.id);
|
||
return this.mapPlatformToUnifiedProduct(product);
|
||
} else if (where.sku) {
|
||
// 通过sku获取产品
|
||
return this.getProductBySku(where.sku);
|
||
}
|
||
}
|
||
|
||
async getProducts(
|
||
params: UnifiedSearchParamsDTO
|
||
): Promise<UnifiedPaginationDTO<UnifiedProductDTO>> {
|
||
// 转换搜索参数
|
||
const requestParams = this.mapProductQuery(params);
|
||
const response = await this.shopyyService.fetchResourcePaged<ShopyyProduct>(
|
||
this.site,
|
||
'products/list',
|
||
requestParams
|
||
);
|
||
const { items = [], total, totalPages, page, per_page } = response;
|
||
const finalItems = items.map((item) => ({
|
||
...item,
|
||
permalink: `${this.site.websiteUrl}/products/${item.handle}`,
|
||
})).map(this.mapPlatformToUnifiedProduct.bind(this))
|
||
return {
|
||
items: finalItems as UnifiedProductDTO[],
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page,
|
||
};
|
||
}
|
||
mapAllProductParams(params: UnifiedSearchParamsDTO): Partial<ShopyyAllProductQuery> {
|
||
const mapped = {
|
||
...params.where,
|
||
} as any
|
||
if (params.per_page) { mapped.limit = params.per_page }
|
||
return mapped
|
||
}
|
||
|
||
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
||
// 转换搜索参数
|
||
const requestParams = this.mapAllProductParams(params);
|
||
const response = await this.shopyyService.request(
|
||
this.site,
|
||
'products',
|
||
'GET',
|
||
null,
|
||
requestParams
|
||
);
|
||
if (response.code !== 0) {
|
||
throw new Error(response.msg || '获取产品列表失败')
|
||
}
|
||
const { data = [] } = response;
|
||
const finalItems = data.map(this.mapPlatformToUnifiedProduct.bind(this))
|
||
return finalItems
|
||
}
|
||
|
||
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||
// 使用映射方法转换参数
|
||
const requestParams = this.mapCreateProductParams(data);
|
||
const res = await this.shopyyService.createProduct(this.site, requestParams);
|
||
return this.mapPlatformToUnifiedProduct(res);
|
||
}
|
||
|
||
async updateProduct(where: { id?: string | number, sku?: string }, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
||
let productId: string;
|
||
if (where.id) {
|
||
productId = String(where.id);
|
||
} else if (where.sku) {
|
||
// 通过sku获取产品ID
|
||
const product = await this.getProductBySku(where.sku);
|
||
productId = String(product.id);
|
||
} else {
|
||
throw new Error('必须提供id或sku参数');
|
||
}
|
||
// 使用映射方法转换参数
|
||
const requestParams = this.mapUpdateProductParams(data);
|
||
await this.shopyyService.updateProduct(this.site, productId, requestParams);
|
||
return true;
|
||
}
|
||
|
||
async deleteProduct(where: { id?: string | number, sku?: string }): Promise<boolean> {
|
||
let productId: string | number;
|
||
if (where.id) {
|
||
productId = where.id;
|
||
} else if (where.sku) {
|
||
// 通过sku获取产品ID
|
||
const product = await this.getProductBySku(where.sku);
|
||
productId = product.id;
|
||
} else {
|
||
throw new Error('必须提供id或sku参数');
|
||
}
|
||
// Use batch delete
|
||
await this.shopyyService.batchProcessProducts(this.site, { delete: [productId] });
|
||
return true;
|
||
}
|
||
|
||
// 通过sku获取产品详情的私有方法
|
||
private async getProductBySku(sku: string): Promise<UnifiedProductDTO> {
|
||
// 使用Shopyy API的搜索功能通过sku查询产品
|
||
const response = await this.getAllProducts({ where: { sku } });
|
||
console.log('getProductBySku', response)
|
||
const product = response?.[0]
|
||
if (!product) {
|
||
throw new Error(`未找到sku为${sku}的产品`);
|
||
}
|
||
return product
|
||
}
|
||
|
||
async batchProcessProducts(
|
||
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||
): Promise<any> {
|
||
return await this.shopyyService.batchProcessProducts(this.site, data);
|
||
}
|
||
|
||
mapProductQuery(query: UnifiedSearchParamsDTO): ShopyyProductQuery {
|
||
return this.mapSearchParams(query)
|
||
}
|
||
|
||
mapAllProductQuery(query: UnifiedSearchParamsDTO): ShopyyProductQuery {
|
||
return this.mapSearchParams(query)
|
||
}
|
||
|
||
// ========== 评论映射方法 ==========
|
||
|
||
mapUnifiedToPlatformReview(data: Partial<UnifiedReviewDTO>) {
|
||
return data
|
||
}
|
||
|
||
mapCreateReviewParams(data: CreateReviewDTO) {
|
||
return data
|
||
}
|
||
|
||
mapUpdateReviewParams(data: UpdateReviewDTO) {
|
||
return data
|
||
}
|
||
|
||
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.mapPlatformToUnifiedReview),
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page,
|
||
};
|
||
}
|
||
|
||
async getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]> {
|
||
// Shopyy getAllReviews 暂未实现
|
||
throw new Error('Shopyy getAllReviews 暂未实现');
|
||
}
|
||
|
||
async createReview(data: any): Promise<UnifiedReviewDTO> {
|
||
const createdReview = await this.shopyyService.createReview(this.site, data);
|
||
return this.mapPlatformToUnifiedReview(createdReview);
|
||
}
|
||
|
||
async updateReview(where: { id: string | number }, data: any): Promise<UnifiedReviewDTO> {
|
||
const updatedReview = await this.shopyyService.updateReview(this.site, where.id, data);
|
||
return this.mapPlatformToUnifiedReview(updatedReview);
|
||
}
|
||
|
||
async deleteReview(where: { id: string | number }): Promise<boolean> {
|
||
return await this.shopyyService.deleteReview(this.site, where.id);
|
||
}
|
||
|
||
mapPlatformToUnifiedReview(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 || '')
|
||
};
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
return shopyyParams;
|
||
}
|
||
|
||
// ========== 订阅映射方法 ==========
|
||
mapPlatformToUnifiedSubscription(data: any): UnifiedSubscriptionDTO {
|
||
return data
|
||
}
|
||
|
||
mapUnifiedToPlatformSubscription(data: Partial<UnifiedSubscriptionDTO>) {
|
||
return data
|
||
}
|
||
|
||
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 暂未实现');
|
||
}
|
||
|
||
// ========== 产品变体映射方法 ==========
|
||
mapPlatformToUnifiedVariation(variant: ShopyyVariant): UnifiedProductVariationDTO {
|
||
// 映射变体
|
||
console.log('ivarianttem', variant)
|
||
return {
|
||
id: variant.id,
|
||
name: variant.title || '',
|
||
sku: variant.sku || variant.sku_code || '',
|
||
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,
|
||
};
|
||
}
|
||
|
||
mapUnifiedToPlatformVariation(data: Partial<UnifiedProductVariationDTO>) {
|
||
return data
|
||
}
|
||
|
||
mapCreateVariationParams(data: CreateVariationDTO) {
|
||
return data
|
||
}
|
||
|
||
mapUpdateVariationParams(data: Partial<UnifiedProductVariationDTO>): any {
|
||
// 构建 ShopYY 变体更新参数(仅包含传入的字段)
|
||
const params: any = {};
|
||
|
||
// 仅当字段存在时才添加到更新参数中
|
||
if (data.id !== undefined) {
|
||
params.id = data.id;
|
||
}
|
||
if (data.sku !== undefined) {
|
||
params.sku = data.sku;
|
||
}
|
||
if (data.price !== undefined) {
|
||
params.price = data.price;
|
||
}
|
||
if (data.sale_price !== undefined) {
|
||
params.special_price = data.sale_price;
|
||
}
|
||
|
||
// 处理库存信息
|
||
if (data.stock_quantity !== undefined) {
|
||
params.inventory_tracking = 1;
|
||
params.inventory_quantity = data.stock_quantity;
|
||
}
|
||
if (data.stock_status !== undefined) {
|
||
params.inventory_tracking = 1;
|
||
params.inventory_quantity = data.stock_status === 'instock' ? (data.stock_quantity || 1) : 0;
|
||
}
|
||
|
||
return params;
|
||
}
|
||
|
||
async getVariation(productId: string | number, variationId: string | number): Promise<UnifiedProductVariationDTO> {
|
||
throw new Error('Shopyy getVariation 暂未实现');
|
||
}
|
||
|
||
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 createVariation(productId: string | number, data: any): Promise<UnifiedProductVariationDTO> {
|
||
throw new Error('Shopyy createVariation 暂未实现');
|
||
}
|
||
|
||
async updateVariation(productId: string | number, variationId: string | number, data: Partial<UnifiedProductVariationDTO>): Promise<any> {
|
||
// 使用映射方法转换参数
|
||
const requestParams = this.mapUpdateVariationParams(data);
|
||
await this.shopyyService.updateVariation(this.site, String(productId), String(variationId), requestParams);
|
||
return { ...data, id: variationId };
|
||
}
|
||
|
||
async deleteVariation(productId: string | number, variationId: string | number): Promise<boolean> {
|
||
throw new Error('Shopyy deleteVariation 暂未实现');
|
||
}
|
||
|
||
// ========== Webhook映射方法 ==========
|
||
|
||
|
||
mapUnifiedToPlatformWebhook(data: Partial<UnifiedWebhookDTO>) {
|
||
return data
|
||
}
|
||
|
||
mapCreateWebhookParams(data: CreateWebhookDTO) {
|
||
return data
|
||
}
|
||
|
||
mapUpdateWebhookParams(data: UpdateWebhookDTO) {
|
||
return data
|
||
}
|
||
|
||
async getWebhook(where: { id: string | number }): Promise<UnifiedWebhookDTO> {
|
||
const webhook = await this.shopyyService.getWebhook(this.site, where.id);
|
||
return this.mapPlatformToUnifiedWebhook(webhook);
|
||
}
|
||
|
||
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.mapPlatformToUnifiedWebhook),
|
||
total,
|
||
totalPages,
|
||
page,
|
||
per_page,
|
||
};
|
||
}
|
||
|
||
async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]> {
|
||
// Shopyy getAllWebhooks 暂未实现
|
||
throw new Error('Shopyy getAllWebhooks 暂未实现');
|
||
}
|
||
|
||
async createWebhook(data: CreateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
||
const createdWebhook = await this.shopyyService.createWebhook(this.site, data);
|
||
return this.mapPlatformToUnifiedWebhook(createdWebhook);
|
||
}
|
||
|
||
async updateWebhook(where: { id: string | number }, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
||
const updatedWebhook = await this.shopyyService.updateWebhook(this.site, where.id, data);
|
||
return this.mapPlatformToUnifiedWebhook(updatedWebhook);
|
||
}
|
||
|
||
async deleteWebhook(where: { id: string | number }): Promise<boolean> {
|
||
return await this.shopyyService.deleteWebhook(this.site, where.id);
|
||
}
|
||
|
||
mapPlatformToUnifiedWebhook(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 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;
|
||
}
|
||
|
||
// ========== 辅助方法 ==========
|
||
/**
|
||
* 通用搜索参数转换方法,处理 where 和 orderBy 的转换
|
||
* 将统一的搜索参数转换为 ShopYY API 所需的参数格式
|
||
*/
|
||
mapSearchParams(params: UnifiedSearchParamsDTO): any {
|
||
// 处理分页参数
|
||
const page = Number(params.page || 1);
|
||
const limit = Number(params.per_page ?? 20);
|
||
|
||
// 处理 where 条件
|
||
const query: any = {
|
||
...(params.where || {}),
|
||
page,
|
||
limit,
|
||
}
|
||
if (params.orderBy) {
|
||
const [field, dir] = Object.entries(params.orderBy)[0];
|
||
query.order_by = dir === 'desc' ? 'desc' : 'asc';
|
||
query.order_field = field
|
||
}
|
||
return query;
|
||
}
|
||
|
||
// 映射产品状态: publish -> 1, draft -> 0
|
||
mapStatus = (status: string) => {
|
||
return status === 'publish' ? 1 : 0;
|
||
};
|
||
|
||
// 映射库存状态: instock -> 1, outofstock -> 0
|
||
mapStockStatus = (stockStatus: string) => {
|
||
return stockStatus === 'instock' ? 1 : 0;
|
||
};
|
||
|
||
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
|
||
}
|
||
// 物流状态 300 未发货;310 部分发货;320 已发货;330(确认收货)
|
||
fulfillmentStatusMap = {
|
||
// 未发货
|
||
'300': OrderFulfillmentStatus.PENDING,
|
||
// 部分发货
|
||
'310': OrderFulfillmentStatus.PARTIALLY_FULFILLED,
|
||
// 已发货
|
||
'320': OrderFulfillmentStatus.FULFILLED,
|
||
// 已取消
|
||
'330': OrderFulfillmentStatus.CANCELLED,
|
||
// 确认发货
|
||
}
|
||
// 支付状态 200 待支付;210 支付中;220 部分支付;230 已支付;240 支付失败;250 部分退款;260 已退款 ;290 已取消;
|
||
financialStatusMap = {
|
||
// 待支付
|
||
'200': OrderPaymentStatus.PENDING,
|
||
// 支付中
|
||
'210': OrderPaymentStatus.PAYING,
|
||
// 部分支付
|
||
'220': OrderPaymentStatus.PARTIALLY_PAID,
|
||
// 已支付
|
||
'230': OrderPaymentStatus.PAID,
|
||
// 支付失败
|
||
'240': OrderPaymentStatus.FAILED,
|
||
// 部分退款
|
||
'250': OrderPaymentStatus.PARTIALLY_REFUNDED,
|
||
// 已退款
|
||
'260': OrderPaymentStatus.REFUNDED,
|
||
// 已取消
|
||
'290': OrderPaymentStatus.CANCELLED,
|
||
}
|
||
} |