zksu
/
API
forked from yoone/API
1
0
Fork 0
API/src/adapter/woocommerce.adapter.ts

1298 lines
49 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ISiteAdapter } from '../interface/site-adapter.interface';
import {
UnifiedMediaDTO,
UnifiedOrderDTO,
UnifiedProductDTO,
UnifiedSubscriptionDTO,
UnifiedCustomerDTO,
UnifiedReviewPaginationDTO,
UnifiedReviewDTO,
UnifiedWebhookDTO,
UnifiedWebhookPaginationDTO,
CreateWebhookDTO,
UpdateWebhookDTO,
CreateVariationDTO,
UpdateVariationDTO,
UnifiedProductVariationDTO,
UnifiedVariationPaginationDTO,
} from '../dto/site-api.dto';
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO } from '../dto/api.dto';
import {
WooProduct,
WooOrder,
WooSubscription,
WpMedia,
WooCustomer,
WooWebhook,
WooOrderSearchParams,
WooProductSearchParams,
} from '../dto/woocommerce.dto';
import { Site } from '../entity/site.entity';
import { WPService } from '../service/wp.service';
export class WooCommerceAdapter implements ISiteAdapter {
// 构造函数接收站点配置与服务实例
constructor(private site: Site, private wpService: WPService) {
this.mapProduct = this.mapProduct.bind(this);
this.mapReview = this.mapReview.bind(this);
this.mapCustomer = this.mapCustomer.bind(this);
this.mapMedia = this.mapMedia.bind(this);
this.mapOrder = this.mapOrder.bind(this);
this.mapWebhook = this.mapWebhook.bind(this);
}
// 映射 WooCommerce webhook 到统一格式
private mapWebhook(webhook: WooWebhook): UnifiedWebhookDTO {
return {
id: webhook.id.toString(),
name: webhook.name,
status: webhook.status,
topic: webhook.topic,
delivery_url: webhook.delivery_url,
secret: webhook.secret,
api_version: webhook.api_version,
date_created: webhook.date_created,
date_modified: webhook.date_modified,
// metadata: webhook.meta_data || [],
};
}
// 获取站点的 webhooks 列表
async getWebhooks(params: UnifiedSearchParamsDTO): Promise<UnifiedWebhookPaginationDTO> {
try {
const result = await this.wpService.getWebhooks(this.site, params);
return {
items: (result.items as WooWebhook[]).map(this.mapWebhook),
total: result.total,
page: Number(params.page || 1),
per_page: Number(params.per_page || 20),
totalPages: result.totalPages,
};
} catch (error) {
throw new Error(`Failed to get webhooks: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 获取所有webhooks
async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]> {
try {
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const webhooks = await this.wpService.sdkGetAll(api, 'webhooks', params);
return webhooks.map((webhook: any) => this.mapWebhook(webhook));
} catch (error) {
throw new Error(`Failed to get all webhooks: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 获取单个 webhook 详情
async getWebhook(id: string | number): Promise<UnifiedWebhookDTO> {
try {
const result = await this.wpService.getWebhook(this.site, id);
return this.mapWebhook(result as WooWebhook);
} catch (error) {
throw new Error(`Failed to get webhook: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 创建新的 webhook
async createWebhook(data: CreateWebhookDTO): Promise<UnifiedWebhookDTO> {
try {
const params = {
name: data.name,
status: 'active', // 默认状态为活跃
topic: data.topic,
delivery_url: data.delivery_url,
secret: data.secret,
api_version: data.api_version || 'wp/v2',
};
const result = await this.wpService.createWebhook(this.site, params);
return this.mapWebhook(result as WooWebhook);
} catch (error) {
throw new Error(`Failed to create webhook: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 更新现有的 webhook
async updateWebhook(id: string | number, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO> {
try {
const params = {
...(data.name ? { name: data.name } : {}),
...(data.status ? { status: data.status } : {}),
...(data.topic ? { topic: data.topic } : {}),
...(data.delivery_url ? { delivery_url: data.delivery_url } : {}),
...(data.secret ? { secret: data.secret } : {}),
...(data.api_version ? { api_version: data.api_version } : {}),
};
const result = await this.wpService.updateWebhook(this.site, id, params);
return this.mapWebhook(result as WooWebhook);
} catch (error) {
throw new Error(`Failed to update webhook: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 删除指定的 webhook
async deleteWebhook(id: string | number): Promise<boolean> {
try {
await this.wpService.deleteWebhook(this.site, id);
return true;
} catch (error) {
throw new Error(`Failed to delete webhook: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getLinks(): Promise<Array<{ title: string, url: string }>> {
const baseUrl = this.site.apiUrl;
const links = [
{ title: '访问网站', url: baseUrl },
{ title: '管理后台', url: `${baseUrl}/wp-admin/` },
{ title: '订单管理', url: `${baseUrl}/wp-admin/edit.php?post_type=shop_order` },
{ title: '产品管理', url: `${baseUrl}/wp-admin/edit.php?post_type=product` },
{ title: '客户管理', url: `${baseUrl}/wp-admin/users.php` },
{ title: '插件管理', url: `${baseUrl}/wp-admin/plugins.php` },
{ title: '主题管理', url: `${baseUrl}/wp-admin/themes.php` },
{ title: 'WooCommerce设置', url: `${baseUrl}/wp-admin/admin.php?page=wc-settings` },
{ title: 'WooCommerce报告', url: `${baseUrl}/wp-admin/admin.php?page=wc-reports` },
];
return links;
}
createMedia(file: any): Promise<UnifiedMediaDTO> {
throw new Error('Method not implemented.');
}
batchProcessOrders?(data: { create?: any[]; update?: any[]; delete?: Array<string | number>; }): Promise<any> {
throw new Error('Method not implemented.');
}
batchProcessCustomers?(data: { create?: any[]; update?: any[]; delete?: Array<string | number>; }): Promise<any> {
throw new Error('Method not implemented.');
}
private mapProductSearchParams(params: UnifiedSearchParamsDTO): Partial<WooProductSearchParams> {
const page = Number(params.page ?? 1);
const per_page = Number(params.per_page ?? 20);
const where = params.where && typeof params.where === 'object' ? params.where : {};
const mapped: any = {
...(params.search ? { search: params.search } : {}),
...(where.status ? { status: where.status } : {}),
page,
per_page,
};
const toArray = (value: any): any[] => {
if (Array.isArray(value)) return value;
if (value === undefined || value === null) return [];
return String(value).split(',').map(v => v.trim()).filter(Boolean);
};
if (where.search_fields ?? where.searchFields) mapped.search_fields = toArray(where.search_fields ?? where.searchFields);
if (where.after ?? where.date_created_after ?? where.created_after) mapped.after = String(where.after ?? where.date_created_after ?? where.created_after);
if (where.before ?? where.date_created_before ?? where.created_before) mapped.before = String(where.before ?? where.date_created_before ?? where.created_before);
if (where.modified_after ?? where.date_modified_after) mapped.modified_after = String(where.modified_after ?? where.date_modified_after);
if (where.modified_before ?? where.date_modified_before) mapped.modified_before = String(where.modified_before ?? where.date_modified_before);
if (where.dates_are_gmt ?? where.datesAreGmt) mapped.dates_are_gmt = Boolean(where.dates_are_gmt ?? where.datesAreGmt);
if (where.exclude ?? where.exclude_ids ?? where.excludedIds) mapped.exclude = toArray(where.exclude ?? where.exclude_ids ?? where.excludedIds);
if (where.include ?? where.ids) mapped.include = toArray(where.include ?? where.ids);
if (where.offset !== undefined) mapped.offset = Number(where.offset);
if (where.parent ?? where.parentId) mapped.parent = toArray(where.parent ?? where.parentId);
if (where.parent_exclude ?? where.parentExclude) mapped.parent_exclude = toArray(where.parent_exclude ?? where.parentExclude);
if (where.slug) mapped.slug = String(where.slug);
if (!mapped.status && (where.status || where.include_status || where.exclude_status || where.includeStatus || where.excludeStatus)) {
if (where.include_status ?? where.includeStatus) mapped.include_status = String(where.include_status ?? where.includeStatus);
if (where.exclude_status ?? where.excludeStatus) mapped.exclude_status = String(where.exclude_status ?? where.excludeStatus);
if (where.status) mapped.status = String(where.status);
}
if (where.type) mapped.type = String(where.type);
if (where.include_types ?? where.includeTypes) mapped.include_types = String(where.include_types ?? where.includeTypes);
if (where.exclude_types ?? where.excludeTypes) mapped.exclude_types = String(where.exclude_types ?? where.excludeTypes);
if (where.sku) mapped.sku = String(where.sku);
if (where.featured ?? where.isFeatured) mapped.featured = Boolean(where.featured ?? where.isFeatured);
if (where.category ?? where.categoryId) mapped.category = String(where.category ?? where.categoryId);
if (where.tag ?? where.tagId) mapped.tag = String(where.tag ?? where.tagId);
if (where.shipping_class ?? where.shippingClass) mapped.shipping_class = String(where.shipping_class ?? where.shippingClass);
if (where.attribute ?? where.attributeName) mapped.attribute = String(where.attribute ?? where.attributeName);
if (where.attribute_term ?? where.attributeTermId ?? where.attributeTerm) mapped.attribute_term = String(where.attribute_term ?? where.attributeTermId ?? where.attributeTerm);
if (where.tax_class ?? where.taxClass) mapped.tax_class = String(where.tax_class ?? where.taxClass);
if (where.on_sale ?? where.onSale) mapped.on_sale = Boolean(where.on_sale ?? where.onSale);
if (where.min_price ?? where.minPrice) mapped.min_price = String(where.min_price ?? where.minPrice);
if (where.max_price ?? where.maxPrice) mapped.max_price = String(where.max_price ?? where.maxPrice);
if (where.stock_status ?? where.stockStatus) mapped.stock_status = String(where.stock_status ?? where.stockStatus);
if (where.virtual !== undefined) mapped.virtual = Boolean(where.virtual);
if (where.downloadable !== undefined) mapped.downloadable = Boolean(where.downloadable);
return mapped;
}
private mapOrderSearchParams(params: UnifiedSearchParamsDTO): Partial<WooOrderSearchParams> {
// 计算分页参数
const page = Number(params.page ?? 1);
const per_page = Number(params.per_page ?? 20);
// 解析排序参数 支持从 order 对象推导
const where = params.where && typeof params.where === 'object' ? params.where : {};
// if (params.orderBy && typeof params.orderBy === 'object') {
// }
const mapped: any = {
...(params.search ? { search: params.search } : {}),
// ...(orderBy ? { orderBy } : {}),
page,
per_page,
};
const toArray = (value: any): any[] => {
if (Array.isArray(value)) return value;
if (value === undefined || value === null) return [];
return String(value).split(',').map(v => v.trim()).filter(Boolean);
};
const toNumber = (value: any): number | undefined => {
if (value === undefined || value === null || value === '') return undefined;
const n = Number(value);
return Number.isFinite(n) ? n : undefined;
};
// 时间过滤参数
if (where.after ?? where.date_created_after ?? where.created_after) mapped.after = String(where.after ?? where.date_created_after ?? where.created_after);
if (where.before ?? where.date_created_before ?? where.created_before) mapped.before = String(where.before ?? where.date_created_before ?? where.created_before);
if (where.modified_after ?? where.date_modified_after) mapped.modified_after = String(where.modified_after ?? where.date_modified_after);
if (where.modified_before ?? where.date_modified_before) mapped.modified_before = String(where.modified_before ?? where.date_modified_before);
if (where.dates_are_gmt ?? where.datesAreGmt) mapped.dates_are_gmt = Boolean(where.dates_are_gmt ?? where.datesAreGmt);
// 集合过滤参数
if (where.exclude) mapped.exclude = toArray(where.exclude);
if (where.include) mapped.include = toArray(where.include);
if (where.ids) mapped.include = toArray(where.ids);
if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset);
if (where.parent ?? where.parentId) mapped.parent = toArray(where.parent ?? where.parentId);
if (where.parent_exclude ?? where.parentExclude) mapped.parent_exclude = toArray(where.parent_exclude ?? where.parentExclude);
// 状态过滤 参数支持数组或逗号分隔字符串
const statusSource = where.status;
if (statusSource !== undefined) {
mapped.status = Array.isArray(statusSource)
? statusSource.map(s => String(s))
: String(statusSource).split(',').map(s => s.trim()).filter(Boolean);
}
// 客户与产品过滤
const customerVal = where.customer ?? where.customer_id;
const productVal = where.product ?? where.product_id;
const dpVal = where.dp;
if (toNumber(customerVal) !== undefined) mapped.customer = Number(customerVal);
if (toNumber(productVal) !== undefined) mapped.product = Number(productVal);
if (toNumber(dpVal) !== undefined) mapped.dp = Number(dpVal);
// 创建来源过滤 支持逗号分隔
const createdViaVal = where.created_via;
if (createdViaVal !== undefined) mapped.created_via = Array.isArray(createdViaVal)
? createdViaVal.join(',')
: String(createdViaVal);
return mapped;
}
private mapCustomerSearchParams(params: UnifiedSearchParamsDTO): Record<string, any> {
const page = Number(params.page ?? 1);
const per_page = Number(params.per_page ?? 20);
const where = params.where && typeof params.where === 'object' ? params.where : {};
const mapped: any = {
...(params.search ? { search: params.search } : {}),
page,
per_page,
};
// 处理orderBy参数转换为WooCommerce API的order和orderby格式
if (params.orderBy) {
// 支持字符串格式 "field:desc" 或对象格式 { "field": "desc" }
if (typeof params.orderBy === 'string') {
const [field, direction = 'desc'] = params.orderBy.split(':');
mapped.orderby = field;
mapped.order = direction.toLowerCase() === 'asc' ? 'asc' : 'desc';
} else if (typeof params.orderBy === 'object') {
const entries = Object.entries(params.orderBy);
if (entries.length > 0) {
const [field, direction] = entries[0];
mapped.orderby = field;
mapped.order = direction === 'asc' ? 'asc' : 'desc';
}
}
}
const toArray = (value: any): any[] => {
if (Array.isArray(value)) return value;
if (value === undefined || value === null) return [];
return String(value).split(',').map(v => v.trim()).filter(Boolean);
};
const toNumber = (value: any): number | undefined => {
if (value === undefined || value === null || value === '') return undefined;
const n = Number(value);
return Number.isFinite(n) ? n : undefined;
};
if (where.exclude) mapped.exclude = toArray(where.exclude);
if (where.include) mapped.include = toArray(where.include);
if (where.ids) mapped.include = toArray(where.ids);
if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset);
if (where.email) mapped.email = String(where.email);
const roleSource = where.role;
if (roleSource !== undefined) mapped.role = String(roleSource);
return mapped;
}
private mapProduct(item: WooProduct): UnifiedProductDTO {
// 将 WooCommerce 产品数据映射为统一产品DTO
// 保留常用字段与时间信息以便前端统一展示
// https://woocommerce.github.io/woocommerce-rest-api-docs/?javascript#product-properties
// 映射变体数据
const mappedVariations = item.variations && Array.isArray(item.variations)
? item.variations
.filter((variation: any) => typeof variation !== 'number') // 过滤掉数字类型的变体ID
.map((variation: any) => {
// 将变体属性转换为统一格式
const mappedAttributes = variation.attributes && Array.isArray(variation.attributes)
? variation.attributes.map((attr: any) => ({
id: attr.id,
name: attr.name || '',
position: attr.position,
visible: attr.visible,
variation: attr.variation,
option: attr.option || '' // 变体属性使用 option 而不是 options
}))
: [];
// 映射变体图片
const mappedImage = variation.image
? {
id: variation.image.id,
src: variation.image.src,
name: variation.image.name,
alt: variation.image.alt,
}
: undefined;
return {
id: variation.id,
name: variation.name || item.name, // 如果变体没有名称,使用父产品名称
sku: variation.sku || '',
regular_price: String(variation.regular_price || ''),
sale_price: String(variation.sale_price || ''),
price: String(variation.price || ''),
stock_status: variation.stock_status || 'outofstock',
stock_quantity: variation.stock_quantity || 0,
attributes: mappedAttributes,
image: mappedImage
};
})
: [];
return {
id: item.id,
date_created: item.date_created,
date_modified: item.date_modified,
type: item.type, // simple grouped external variable
status: item.status, // draft pending private publish
sku: item.sku,
name: item.name,
//价格
regular_price: item.regular_price,
sale_price: item.sale_price,
price: item.price,
stock_status: item.stock_status,
stock_quantity: item.stock_quantity,
images: (item.images || []).map((img: any) => ({
id: img.id,
src: img.src,
name: img.name,
alt: img.alt,
})),
categories: (item.categories || []).map((c: any) => ({
id: c.id,
name: c.name,
})),
tags: (item.tags || []).map((t: any) => ({
id: t.id,
name: t.name,
})),
attributes: (item.attributes || []).map(attr => ({
id: attr.id,
name: attr.name || '',
position: attr.position,
visible: attr.visible,
variation: attr.variation,
options: attr.options || []
})),
variations: mappedVariations,
permalink: item.permalink,
raw: item,
};
}
private buildFullAddress(addr: any): string {
if (!addr) return '';
const name = addr.fullname || `${addr.first_name || ''} ${addr.last_name || ''}`.trim();
return [
name,
addr.company,
addr.address_1,
addr.address_2,
addr.city,
addr.state,
addr.postcode,
addr.country,
addr.phone
].filter(Boolean).join(', ');
}
private mapOrder(item: WooOrder): UnifiedOrderDTO {
// 将 WooCommerce 订单数据映射为统一订单DTO
// 包含账单地址与收货地址以及创建与更新时间
// 映射物流追踪信息,将后端格式转换为前端期望的格式
const fulfillments = (item.fulfillments || []).map((track: any) => ({
tracking_number: track.tracking_number || '',
shipping_provider: track.shipping_provider || '',
shipping_method: track.shipping_method || '',
status: track.status || '',
date_created: track.date_created || '',
items: track.items || [],
}));
return {
id: item.id,
number: item.number,
status: item.status,
currency: item.currency,
total: item.total,
customer_id: item.customer_id,
customer_email: item.billing?.email || '', // TODO 与 email 重复 保留一个即可
email: item.billing?.email || '',
customer_name: `${item.billing?.first_name || ''} ${item.billing?.last_name || ''}`.trim(),
refunds: item.refunds?.map?.(refund => ({
id: refund.id,
reason: refund.reason,
total: refund.total,
})),
line_items: (item.line_items as any[]).map(li => ({
...li,
productId: li.product_id,
})),
customer_ip_address: item.customer_ip_address ?? '',
date_paid: item.date_paid ?? '',
utm_source: item?.meta_data?.find(el => el.key === '_wc_order_attribution_utm_source')?.value || '',
device_type: item?.meta_data?.find(el => el.key === '_wc_order_attribution_device_type')?.value || '',
source_type: item?.meta_data?.find(el => el.key === '_wc_order_attribution_source_type')?.value || '',
billing: item.billing,
shipping: item.shipping,
billing_full_address: this.buildFullAddress(item.billing),
shipping_full_address: this.buildFullAddress(item.shipping),
payment_method: item.payment_method_title,
date_created: item.date_created,
date_modified: item.date_modified,
shipping_lines: item.shipping_lines,
fee_lines: item.fee_lines,
coupon_lines: item.coupon_lines,
fulfillments,
raw: item,
};
}
private mapSubscription(item: WooSubscription): UnifiedSubscriptionDTO {
// 将 WooCommerce 订阅数据映射为统一订阅DTO
// 若缺少创建时间则回退为开始时间
return {
id: item.id,
status: item.status,
customer_id: item.customer_id,
billing_period: item.billing_period,
billing_interval: item.billing_interval,
date_created: item.date_created ?? item.start_date,
date_modified: item.date_modified,
start_date: item.start_date,
next_payment_date: item.next_payment_date,
line_items: item.line_items,
raw: item,
};
}
private mapMedia(item: WpMedia): UnifiedMediaDTO {
// 将 WordPress 媒体数据映射为统一媒体DTO
// 兼容不同字段命名的时间信息
return {
id: item.id,
title:
typeof item.title === 'string'
? item.title
: item.title?.rendered || '',
media_type: item.media_type,
mime_type: item.mime_type,
source_url: item.source_url,
date_created: item.date_created ?? item.date,
date_modified: item.date_modified ?? item.modified,
};
}
async getProducts(
params: UnifiedSearchParamsDTO
): Promise<UnifiedPaginationDTO<UnifiedProductDTO>> {
// 获取产品列表并使用统一分页结构返回
const requestParams = this.mapProductSearchParams(params);
const { items, total, totalPages, page, per_page } =
await this.wpService.fetchResourcePaged<any>(
this.site,
'products',
requestParams
);
// 对于类型为 variable 的产品,需要加载完整的变体数据
const productsWithVariations = await Promise.all(
items.map(async (item: any) => {
// 如果产品类型是 variable 且有变体 ID 列表,则加载完整的变体数据
if (item.type === 'variable' && item.variations && Array.isArray(item.variations) && item.variations.length > 0) {
try {
// 批量获取该产品的所有变体数据
const variations = await this.wpService.sdkGetAll(
(this.wpService as any).createApi(this.site, 'wc/v3'),
`products/${item.id}/variations`
);
// 将完整的变体数据添加到产品对象中
item.variations = variations;
} catch (error) {
// 如果获取变体失败,保持原有的 ID 数组
console.error(`获取产品 ${item.id} 的变体数据失败:`, error);
}
}
return item;
})
);
return {
items: productsWithVariations.map(this.mapProduct),
total,
totalPages,
page,
per_page,
};
}
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
// 使用sdkGetAll获取所有产品数据不受分页限制
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const products = await this.wpService.sdkGetAll(api, 'products', params);
// 对于类型为 variable 的产品,需要加载完整的变体数据
const productsWithVariations = await Promise.all(
products.map(async (product: any) => {
// 如果产品类型是 variable 且有变体 ID 列表,则加载完整的变体数据
if (product.type === 'variable' && product.variations && Array.isArray(product.variations) && product.variations.length > 0) {
try {
// 批量获取该产品的所有变体数据
const variations = await this.wpService.sdkGetAll(
api,
`products/${product.id}/variations`
);
// 将完整的变体数据添加到产品对象中
product.variations = variations;
} catch (error) {
// 如果获取变体失败,保持原有的 ID 数组
console.error(`获取产品 ${product.id} 的变体数据失败:`, error);
}
}
return product;
})
);
return productsWithVariations.map((product: any) => this.mapProduct(product));
}
async getProduct(id: string | number): Promise<UnifiedProductDTO> {
// 获取单个产品详情并映射为统一产品DTO
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.get(`products/${id}`);
const product = res.data;
// 如果产品类型是 variable 且有变体 ID 列表,则加载完整的变体数据
if (product.type === 'variable' && product.variations && Array.isArray(product.variations) && product.variations.length > 0) {
try {
// 批量获取该产品的所有变体数据
const variations = await this.wpService.sdkGetAll(
api,
`products/${product.id}/variations`
);
// 将完整的变体数据添加到产品对象中
product.variations = variations;
} catch (error) {
// 如果获取变体失败,保持原有的 ID 数组
console.error(`获取产品 ${product.id} 的变体数据失败:`, error);
}
}
return this.mapProduct(product);
}
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
// 创建产品并返回统一产品DTO
const res = await this.wpService.createProduct(this.site, data);
return this.mapProduct(res);
}
async updateProduct(id: string | number, data: Partial<UnifiedProductDTO>): Promise<boolean> {
// 更新产品并返回统一产品DTO
const res = await this.wpService.updateProduct(this.site, String(id), data as any);
return res
}
async getOrderNotes(orderId: string | number): Promise<any[]> {
// 获取订单备注列表
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.get(`orders/${orderId}/notes`);
return res.data;
}
async createOrderNote(orderId: string | number, data: any): Promise<any> {
// 创建订单备注
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.post(`orders/${orderId}/notes`, data);
return res.data;
}
async deleteProduct(id: string | number): Promise<boolean> {
// 删除产品
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
try {
await api.delete(`products/${id}`, { force: true });
return true;
} catch (e) {
return false;
}
}
async batchProcessProducts(
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
): Promise<any> {
// 批量处理产品增删改
return await this.wpService.batchProcessProducts(this.site, data);
}
async getOrders(
params: UnifiedSearchParamsDTO
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
const requestParams = this.mapOrderSearchParams(params);
const { items, total, totalPages, page, per_page } =
await this.wpService.fetchResourcePaged<any>(this.site, 'orders', requestParams);
// 并行获取所有订单的履行信息
const ordersWithFulfillments = await Promise.all(
items.map(async (order: any) => {
try {
// 获取订单的履行信息
const fulfillments = await this.getOrderFulfillments(order.id);
// 将履行信息添加到订单对象中
return {
...order,
fulfillments: fulfillments || []
};
} catch (error) {
// 如果获取履行信息失败,仍然返回订单,只是履行信息为空数组
console.error(`获取订单 ${order.id} 的履行信息失败:`, error);
return {
...order,
fulfillments: []
};
}
})
);
return {
items: ordersWithFulfillments.map(this.mapOrder),
total,
totalPages,
page,
per_page,
};
}
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
// 获取单个订单详情
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.get(`orders/${id}`);
return this.mapOrder(res.data);
}
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
// 使用sdkGetAll获取所有订单数据不受分页限制
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const orders = await this.wpService.sdkGetAll(api, 'orders', params);
return orders.map((order: any) => this.mapOrder(order));
}
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
// 创建订单并返回统一订单DTO
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.post('orders', data);
return this.mapOrder(res.data);
}
async updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
// 更新订单并返回布尔结果
return await this.wpService.updateOrder(this.site, String(id), data as any);
}
async deleteOrder(id: string | number): Promise<boolean> {
// 删除订单
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
await api.delete(`orders/${id}`, { force: true });
return true;
}
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> {
throw new Error('暂无实现')
// 订单履行(发货)
// const api = (this.wpService as any).createApi(this.site, 'wc/v3');
// try {
// // 更新订单状态为已完成
// await api.put(`orders/${orderId}`, { status: 'completed' });
// // 如果提供了物流信息,添加到订单备注
// if (data.tracking_number || data.shipping_provider) {
// const note = `订单已发货${data.tracking_number ? `,物流单号:${data.tracking_number}` : ''}${data.shipping_provider ? `,物流公司:${data.shipping_provider}` : ''}`;
// await api.post(`orders/${orderId}/notes`, { note, customer_note: true });
// }
// return {
// success: true,
// order_id: orderId,
// fulfillment_id: `fulfillment_${orderId}_${Date.now()}`,
// tracking_number: data.tracking_number,
// shipping_provider: data.shipping_provider,
// fulfilled_at: new Date().toISOString()
// };
// } catch (error) {
// throw new Error(`履行失败: ${error.message}`);
// }
}
async cancelFulfillment(orderId: string | number, data: {
reason?: string;
shipment_id?: string;
}): Promise<any> {
throw new Error('暂未实现')
// 取消订单履行
// const api = (this.wpService as any).createApi(this.site, 'wc/v3');
// try {
// // 将订单状态改回处理中
// await api.put(`orders/${orderId}`, { status: 'processing' });
// // 添加取消履行的备注
// const note = `订单履行已取消${data.reason ? `,原因:${data.reason}` : ''}`;
// await api.post(`orders/${orderId}/notes`, { note, customer_note: true });
// return {
// success: true,
// order_id: orderId,
// shipment_id: data.shipment_id,
// reason: data.reason,
// cancelled_at: new Date().toISOString()
// };
// } catch (error) {
// throw new Error(`取消履行失败: ${error.message}`);
// }
}
async getSubscriptions(
params: UnifiedSearchParamsDTO
): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>> {
// 获取订阅列表并映射为统一订阅DTO集合
const { items, total, totalPages, page, per_page } =
await this.wpService.fetchResourcePaged<any>(
this.site,
'subscriptions',
params
);
return {
items: items.map(this.mapSubscription),
total,
totalPages,
page,
per_page,
};
}
async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]> {
// 使用sdkGetAll获取所有订阅数据不受分页限制
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const subscriptions = await this.wpService.sdkGetAll(api, 'subscriptions', params);
return subscriptions.map((subscription: any) => this.mapSubscription(subscription));
}
async getMedia(
params: UnifiedSearchParamsDTO
): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> {
// 获取媒体列表并映射为统一媒体DTO集合
const { items, total, totalPages, page, per_page } = await this.wpService.fetchMediaPaged(
this.site,
params
);
return {
items: items.map(this.mapMedia.bind(this)),
total,
totalPages,
page,
per_page,
};
}
async getAllMedia(params?: UnifiedSearchParamsDTO): Promise<UnifiedMediaDTO[]> {
// 使用sdkGetAll获取所有媒体数据不受分页限制
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const media = await this.wpService.sdkGetAll(api, 'media', params);
return media.map((mediaItem: any) => this.mapMedia(mediaItem));
}
private mapReview(item: any): UnifiedReviewDTO & { raw: any } {
// 将 WooCommerce 评论数据映射为统一评论DTO
return {
id: item.id,
product_id: item.product_id,
author: item.reviewer,
email: item.reviewer_email,
content: item.review,
rating: item.rating,
status: item.status,
date_created: item.date_created,
raw: item
};
}
async getReviews(
params: UnifiedSearchParamsDTO
): Promise<UnifiedReviewPaginationDTO> {
// 获取评论列表并使用统一分页结构返回
const requestParams = this.mapProductSearchParams(params);
const { items, total, totalPages, page, per_page } =
await this.wpService.fetchResourcePaged<any>(
this.site,
'products/reviews',
requestParams
);
return {
items: items.map(this.mapReview.bind(this)),
total,
totalPages,
page,
per_page,
};
}
async getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]> {
// 使用sdkGetAll获取所有评论数据不受分页限制
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const reviews = await this.wpService.sdkGetAll(api, 'products/reviews', params);
return reviews.map((review: any) => this.mapReview(review));
}
async createReview(data: any): Promise<UnifiedReviewDTO> {
const res = await this.wpService.createReview(this.site, data);
return this.mapReview(res);
}
async updateReview(id: number, data: any): Promise<UnifiedReviewDTO> {
const res = await this.wpService.updateReview(this.site, id, data);
return this.mapReview(res);
}
async deleteReview(id: number): Promise<boolean> {
return await this.wpService.deleteReview(this.site, id);
}
async deleteMedia(id: string | number): Promise<boolean> {
// 删除媒体资源
await this.wpService.deleteMedia(Number(this.site.id), Number(id), true);
return true;
}
async updateMedia(id: string | number, data: any): Promise<any> {
// 更新媒体信息
return await this.wpService.updateMedia(Number(this.site.id), Number(id), data);
}
async convertMediaToWebp(ids: Array<string | number>): Promise<{ converted: any[]; failed: any[] }> {
// 函数说明 调用服务层将站点的指定媒体批量转换为 webp 并上传
const result = await this.wpService.convertMediaToWebp(Number(this.site.id), ids);
return result as any;
}
private mapCustomer(item: WooCustomer): UnifiedCustomerDTO {
// 将 WooCommerce 客户数据映射为统一客户DTO
// 包含基础信息地址信息与时间信息
return {
id: item.id,
avatar: item.avatar_url,
email: item.email,
orders: Number(item.orders ?? 0),
total_spend: Number(item.total_spent ?? 0),
first_name: item.first_name,
last_name: item.last_name,
username: item.username,
phone: item.billing?.phone || item.shipping?.phone,
billing: item.billing,
shipping: item.shipping,
date_created: item.date_created,
date_modified: item.date_modified,
raw: item,
};
}
async getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>> {
const requestParams = this.mapCustomerSearchParams(params);
const { items, total, totalPages, page, per_page } = await this.wpService.fetchResourcePaged<any>(
this.site,
'customers',
requestParams
);
return {
items: items.map((i: any) => this.mapCustomer(i)),
total,
totalPages,
page,
per_page,
};
}
async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]> {
// 使用sdkGetAll获取所有客户数据不受分页限制
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
// 处理orderBy参数转换为WooCommerce API需要的格式
const requestParams = this.mapCustomerSearchParams(params || {});
const customers = await this.wpService.sdkGetAll(api, 'customers', requestParams);
return customers.map((customer: any) => this.mapCustomer(customer));
}
async getCustomer(id: string | number): Promise<UnifiedCustomerDTO> {
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.get(`customers/${id}`);
return this.mapCustomer(res.data);
}
async createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.post('customers', data);
return this.mapCustomer(res.data);
}
async updateCustomer(id: string | number, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.put(`customers/${id}`, data);
return this.mapCustomer(res.data);
}
async deleteCustomer(id: string | number): Promise<boolean> {
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
await api.delete(`customers/${id}`, { force: true });
return true;
}
async getOrderFulfillments(orderId: string | number): Promise<any[]> {
return await this.wpService.getFulfillments(this.site, String(orderId));
}
async createOrderFulfillment(orderId: string | number, data: {
tracking_number: string;
shipping_provider: string;
shipping_method?: string;
status?: string;
date_created?: string;
items?: Array<{
order_item_id: number;
quantity: number;
}>;
}): Promise<any> {
const shipmentData: any = {
shipping_provider: data.shipping_provider,
tracking_number: data.tracking_number,
};
if (data.shipping_method) {
shipmentData.shipping_method = data.shipping_method;
}
if (data.status) {
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);
return response.data;
}
async updateOrderFulfillment(orderId: string | number, fulfillmentId: string, data: {
tracking_number?: string;
shipping_provider?: string;
shipping_method?: string;
status?: string;
date_created?: string;
items?: Array<{
order_item_id: number;
quantity: number;
}>;
}): Promise<any> {
return await this.wpService.updateFulfillment(this.site, String(orderId), fulfillmentId, data);
}
async deleteOrderFulfillment(orderId: string | number, fulfillmentId: string): Promise<boolean> {
return await this.wpService.deleteFulfillment(this.site, String(orderId), fulfillmentId);
}
// 映射 WooCommerce 变体到统一格式
private mapVariation(variation: any, productName?: string): UnifiedProductVariationDTO {
// 将变体属性转换为统一格式
const mappedAttributes = variation.attributes && Array.isArray(variation.attributes)
? variation.attributes.map((attr: any) => ({
id: attr.id,
name: attr.name || '',
position: attr.position,
visible: attr.visible,
variation: attr.variation,
option: attr.option || ''
}))
: [];
// 映射变体图片
const mappedImage = variation.image
? {
id: variation.image.id,
src: variation.image.src,
name: variation.image.name,
alt: variation.image.alt,
}
: undefined;
return {
id: variation.id,
name: variation.name || productName || '',
sku: variation.sku || '',
regular_price: String(variation.regular_price || ''),
sale_price: String(variation.sale_price || ''),
price: String(variation.price || ''),
stock_status: variation.stock_status || 'outofstock',
stock_quantity: variation.stock_quantity || 0,
attributes: mappedAttributes,
image: mappedImage,
description: variation.description || '',
enabled: variation.status === 'publish',
downloadable: variation.downloadable || false,
virtual: variation.virtual || false,
manage_stock: variation.manage_stock || false,
weight: variation.weight || '',
length: variation.dimensions?.length || '',
width: variation.dimensions?.width || '',
height: variation.dimensions?.height || '',
shipping_class: variation.shipping_class || '',
tax_class: variation.tax_class || '',
menu_order: variation.menu_order || 0,
};
}
// 获取产品变体列表
async getVariations(productId: string | number, params: UnifiedSearchParamsDTO): Promise<UnifiedVariationPaginationDTO> {
try {
const page = Number(params.page ?? 1);
const per_page = Number(params.per_page ?? 20);
const result = await this.wpService.getVariations(this.site, Number(productId), page, per_page);
// 获取产品名称用于变体显示
const product = await this.wpService.getProduct(this.site, Number(productId));
const productName = product?.name || '';
return {
items: (result.items as any[]).map((variation: any) => this.mapVariation(variation, productName)),
total: result.total,
page: result.page,
per_page: result.per_page,
totalPages: result.totalPages,
};
} catch (error) {
throw new Error(`获取产品变体列表失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 获取所有产品变体
async getAllVariations(productId: string | number, params?: UnifiedSearchParamsDTO): Promise<UnifiedProductVariationDTO[]> {
try {
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const variations = await this.wpService.sdkGetAll(api, `products/${productId}/variations`, params);
// 获取产品名称用于变体显示
const product = await this.wpService.getProduct(this.site, Number(productId));
const productName = product?.name || '';
return variations.map((variation: any) => this.mapVariation(variation, productName));
} catch (error) {
throw new Error(`获取所有产品变体失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 获取单个产品变体
async getVariation(productId: string | number, variationId: string | number): Promise<UnifiedProductVariationDTO> {
try {
const variation = await this.wpService.getVariation(this.site, Number(productId), Number(variationId));
// 获取产品名称用于变体显示
const product = await this.wpService.getProduct(this.site, Number(productId));
const productName = product?.name || '';
return this.mapVariation(variation, productName);
} catch (error) {
throw new Error(`获取产品变体失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 创建产品变体
async createVariation(productId: string | number, data: CreateVariationDTO): Promise<UnifiedProductVariationDTO> {
try {
// 将统一DTO转换为WooCommerce API格式
const createData: any = {
sku: data.sku,
regular_price: data.regular_price,
sale_price: data.sale_price,
stock_status: data.stock_status,
stock_quantity: data.stock_quantity,
description: data.description,
status: data.enabled ? 'publish' : 'draft',
downloadable: data.downloadable,
virtual: data.virtual,
manage_stock: data.manage_stock,
weight: data.weight,
dimensions: {
length: data.length,
width: data.width,
height: data.height,
},
shipping_class: data.shipping_class,
tax_class: data.tax_class,
menu_order: data.menu_order,
};
// 映射属性
if (data.attributes && Array.isArray(data.attributes)) {
createData.attributes = data.attributes.map(attr => ({
id: attr.id,
name: attr.name,
option: attr.option || attr.options?.[0] || '',
}));
}
// 映射图片
if (data.image) {
createData.image = {
id: data.image.id,
};
}
const variation = await this.wpService.createVariation(this.site, String(productId), createData);
// 获取产品名称用于变体显示
const product = await this.wpService.getProduct(this.site, Number(productId));
const productName = product?.name || '';
return this.mapVariation(variation, productName);
} catch (error) {
throw new Error(`创建产品变体失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 更新产品变体
async updateVariation(productId: string | number, variationId: string | number, data: UpdateVariationDTO): Promise<UnifiedProductVariationDTO> {
try {
// 将统一DTO转换为WooCommerce API格式
const updateData: any = {
sku: data.sku,
regular_price: data.regular_price,
sale_price: data.sale_price,
stock_status: data.stock_status,
stock_quantity: data.stock_quantity,
description: data.description,
status: data.enabled !== undefined ? (data.enabled ? 'publish' : 'draft') : undefined,
downloadable: data.downloadable,
virtual: data.virtual,
manage_stock: data.manage_stock,
weight: data.weight,
shipping_class: data.shipping_class,
tax_class: data.tax_class,
menu_order: data.menu_order,
};
// 映射尺寸
if (data.length || data.width || data.height) {
updateData.dimensions = {};
if (data.length) updateData.dimensions.length = data.length;
if (data.width) updateData.dimensions.width = data.width;
if (data.height) updateData.dimensions.height = data.height;
}
// 映射属性
if (data.attributes && Array.isArray(data.attributes)) {
updateData.attributes = data.attributes.map(attr => ({
id: attr.id,
name: attr.name,
option: attr.option || attr.options?.[0] || '',
}));
}
// 映射图片
if (data.image) {
updateData.image = {
id: data.image.id,
};
}
const variation = await this.wpService.updateVariation(this.site, String(productId), String(variationId), updateData);
// 获取产品名称用于变体显示
const product = await this.wpService.getProduct(this.site, Number(productId));
const productName = product?.name || '';
return this.mapVariation(variation, productName);
} catch (error) {
throw new Error(`更新产品变体失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
// 删除产品变体
async deleteVariation(productId: string | number, variationId: string | number): Promise<boolean> {
try {
await this.wpService.deleteVariation(this.site, String(productId), String(variationId));
return true;
} catch (error) {
throw new Error(`删除产品变体失败: ${error instanceof Error ? error.message : String(error)}`);
}
}
}