chore: 兼容旧数据库 #36

Merged
longbot merged 8 commits from :main into main 2025-12-24 08:12:26 +00:00
36 changed files with 1168 additions and 2065 deletions

4
.gitignore vendored
View File

@ -16,6 +16,6 @@ yarn.lock
**/config.local.ts **/config.local.ts
container container
scripts scripts
ai
tmp_uploads/ tmp_uploads/
.trae .trae
docs

View File

@ -321,6 +321,11 @@ export class ShopyyAdapter implements ISiteAdapter {
}; };
} }
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
// Shopyy getAllProducts 暂未实现
throw new Error('Shopyy getAllProducts 暂未实现');
}
async getProduct(id: string | number): Promise<UnifiedProductDTO> { async getProduct(id: string | number): Promise<UnifiedProductDTO> {
// 使用ShopyyService获取单个产品 // 使用ShopyyService获取单个产品
const product = await this.shopyyService.getProduct(this.site, id); const product = await this.shopyyService.getProduct(this.site, id);
@ -385,6 +390,11 @@ export class ShopyyAdapter implements ISiteAdapter {
}; };
} }
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
// Shopyy getAllOrders 暂未实现
throw new Error('Shopyy getAllOrders 暂未实现');
}
async getOrder(id: string | number): Promise<UnifiedOrderDTO> { async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
const data = await this.shopyyService.getOrder(String(this.site.id), String(id)); const data = await this.shopyyService.getOrder(String(this.site.id), String(id));
return this.mapOrder(data); return this.mapOrder(data);
@ -478,6 +488,11 @@ export class ShopyyAdapter implements ISiteAdapter {
throw new Error('Shopyy does not support subscriptions.'); throw new Error('Shopyy does not support subscriptions.');
} }
async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]> {
// Shopyy getAllSubscriptions 暂未实现
throw new Error('Shopyy getAllSubscriptions 暂未实现');
}
async getMedia( async getMedia(
params: UnifiedSearchParamsDTO params: UnifiedSearchParamsDTO
): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> { ): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> {
@ -488,7 +503,7 @@ export class ShopyyAdapter implements ISiteAdapter {
requestParams requestParams
); );
return { return {
items: items.map(this.mapMedia), items: items.map(this.mapMedia.bind(this)),
total, total,
totalPages, totalPages,
page, page,
@ -496,6 +511,11 @@ export class ShopyyAdapter implements ISiteAdapter {
}; };
} }
async getAllMedia(params?: UnifiedSearchParamsDTO): Promise<UnifiedMediaDTO[]> {
// Shopyy getAllMedia 暂未实现
throw new Error('Shopyy getAllMedia 暂未实现');
}
async createMedia(file: any): Promise<UnifiedMediaDTO> { async createMedia(file: any): Promise<UnifiedMediaDTO> {
const createdMedia = await this.shopyyService.createMedia(this.site, file); const createdMedia = await this.shopyyService.createMedia(this.site, file);
return this.mapMedia(createdMedia); return this.mapMedia(createdMedia);
@ -527,6 +547,11 @@ export class ShopyyAdapter implements ISiteAdapter {
}; };
} }
async getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]> {
// Shopyy getAllReviews 暂未实现
throw new Error('Shopyy getAllReviews 暂未实现');
}
async getReview(id: string | number): Promise<UnifiedReviewDTO> { async getReview(id: string | number): Promise<UnifiedReviewDTO> {
const review = await this.shopyyService.getReview(this.site, id); const review = await this.shopyyService.getReview(this.site, id);
return this.mapReview(review); return this.mapReview(review);
@ -550,7 +575,7 @@ export class ShopyyAdapter implements ISiteAdapter {
} }
private mapReviewSearchParams(params: UnifiedSearchParamsDTO): any { private mapReviewSearchParams(params: UnifiedSearchParamsDTO): any {
const { search, page, per_page, status } = params; const { search, page, per_page, where } = params;
const shopyyParams: any = { const shopyyParams: any = {
page: page || 1, page: page || 1,
limit: per_page || 10, limit: per_page || 10,
@ -560,8 +585,8 @@ export class ShopyyAdapter implements ISiteAdapter {
shopyyParams.search = search; shopyyParams.search = search;
} }
if (status) { if (where.status) {
shopyyParams.status = status; shopyyParams.status = where.status;
} }
// if (product_id) { // if (product_id) {
@ -607,6 +632,11 @@ export class ShopyyAdapter implements ISiteAdapter {
}; };
} }
async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]> {
// Shopyy getAllWebhooks 暂未实现
throw new Error('Shopyy getAllWebhooks 暂未实现');
}
async getWebhook(id: string | number): Promise<UnifiedWebhookDTO> { async getWebhook(id: string | number): Promise<UnifiedWebhookDTO> {
const webhook = await this.shopyyService.getWebhook(this.site, id); const webhook = await this.shopyyService.getWebhook(this.site, id);
return this.mapWebhook(webhook); return this.mapWebhook(webhook);
@ -657,6 +687,11 @@ export class ShopyyAdapter implements ISiteAdapter {
}; };
} }
async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]> {
// Shopyy getAllCustomers 暂未实现
throw new Error('Shopyy getAllCustomers 暂未实现');
}
async getCustomer(id: string | number): Promise<UnifiedCustomerDTO> { async getCustomer(id: string | number): Promise<UnifiedCustomerDTO> {
const customer = await this.shopyyService.getCustomer(this.site, id); const customer = await this.shopyyService.getCustomer(this.site, id);
return this.mapCustomer(customer); return this.mapCustomer(customer);

View File

@ -1,5 +1,4 @@
import { ISiteAdapter } from '../interface/site-adapter.interface'; import { ISiteAdapter } from '../interface/site-adapter.interface';
import { IPlatformService } from '../interface/platform.interface';
import { import {
UnifiedMediaDTO, UnifiedMediaDTO,
UnifiedOrderDTO, UnifiedOrderDTO,
@ -26,10 +25,11 @@ import {
WooProductSearchParams, WooProductSearchParams,
} from '../dto/woocommerce.dto'; } from '../dto/woocommerce.dto';
import { Site } from '../entity/site.entity'; import { Site } from '../entity/site.entity';
import { WPService } from '../service/wp.service';
export class WooCommerceAdapter implements ISiteAdapter { export class WooCommerceAdapter implements ISiteAdapter {
// 构造函数接收站点配置与服务实例 // 构造函数接收站点配置与服务实例
constructor(private site: Site, private wpService: IPlatformService) { constructor(private site: Site, private wpService: WPService) {
this.mapProduct = this.mapProduct.bind(this); this.mapProduct = this.mapProduct.bind(this);
this.mapReview = this.mapReview.bind(this); this.mapReview = this.mapReview.bind(this);
this.mapCustomer = this.mapCustomer.bind(this); this.mapCustomer = this.mapCustomer.bind(this);
@ -71,6 +71,17 @@ export class WooCommerceAdapter implements ISiteAdapter {
} }
} }
// 获取所有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 详情 // 获取单个 webhook 详情
async getWebhook(id: string | number): Promise<UnifiedWebhookDTO> { async getWebhook(id: string | number): Promise<UnifiedWebhookDTO> {
try { try {
@ -159,26 +170,10 @@ export class WooCommerceAdapter implements ISiteAdapter {
const page = Number(params.page ?? 1); const page = Number(params.page ?? 1);
const per_page = Number( params.per_page ?? 20); const per_page = Number( params.per_page ?? 20);
const where = params.where && typeof params.where === 'object' ? params.where : {}; const where = params.where && typeof params.where === 'object' ? params.where : {};
let orderby: string | undefined = params.orderby;
let order: 'asc' | 'desc' | undefined = params.orderDir as any;
if (!orderby && params.order && typeof params.order === 'object') {
const entries = Object.entries(params.order as Record<string, any>);
if (entries.length > 0) {
const [field, dir] = entries[0];
let mappedField = field;
if (['created_at', 'date_created', 'date'].includes(field)) mappedField = 'date';
else if (['name', 'title'].includes(field)) mappedField = 'title';
else if (['id', 'ID'].includes(field)) mappedField = 'id';
else if (['price', 'regular_price', 'sale_price'].includes(field)) mappedField = 'price';
orderby = mappedField;
order = String(dir).toLowerCase() === 'desc' ? 'desc' : 'asc';
}
}
const mapped: any = { const mapped: any = {
...(params.search ? { search: params.search } : {}), ...(params.search ? { search: params.search } : {}),
...(params.status ? { status: params.status } : {}), ...(where.status ? { status: where.status } : {}),
...(orderby ? { orderby } : {}),
...(order ? { order } : {}),
page, page,
per_page, per_page,
}; };
@ -224,10 +219,6 @@ export class WooCommerceAdapter implements ISiteAdapter {
if (where.virtual !== undefined) mapped.virtual = Boolean(where.virtual); if (where.virtual !== undefined) mapped.virtual = Boolean(where.virtual);
if (where.downloadable !== undefined) mapped.downloadable = Boolean(where.downloadable); if (where.downloadable !== undefined) mapped.downloadable = Boolean(where.downloadable);
if (params.ids) {
const idsArr = String(params.ids).split(',').map(v => v.trim()).filter(Boolean);
mapped.include = idsArr;
}
return mapped; return mapped;
} }
@ -237,30 +228,12 @@ export class WooCommerceAdapter implements ISiteAdapter {
const per_page = Number( params.per_page ?? 20); const per_page = Number( params.per_page ?? 20);
// 解析排序参数 支持从 order 对象推导 // 解析排序参数 支持从 order 对象推导
const where = params.where && typeof params.where === 'object' ? params.where : {}; const where = params.where && typeof params.where === 'object' ? params.where : {};
let orderby: string | undefined = params.orderby;
let orderDir: 'asc' | 'desc' | undefined = params.orderDir as any; // if (params.orderBy && typeof params.orderBy === 'object') {
if (!orderby && params.order && typeof params.order === 'object') { // }
const entries = Object.entries(params.order as Record<string, any>);
if (entries.length > 0) {
const [field, dir] = entries[0];
let mappedField = field;
if (['created_at', 'date_created', 'date'].includes(field)) mappedField = 'date';
else if (['modified', 'date_modified'].includes(field)) mappedField = 'modified';
else if (['id', 'ID'].includes(field)) mappedField = 'id';
else if (['name', 'title'].includes(field)) mappedField = 'title';
else if (['slug'].includes(field)) mappedField = 'slug';
else if (['include'].includes(field)) mappedField = 'include';
orderby = mappedField;
orderDir = String(dir).toLowerCase() === 'asc' ? 'asc' : 'desc';
}
} else if (!orderDir && typeof params.order === 'string') {
orderDir = String(params.order).toLowerCase() === 'asc' ? 'asc' : 'desc';
}
const mapped: any = { const mapped: any = {
...(params.search ? { search: params.search } : {}), ...(params.search ? { search: params.search } : {}),
...(orderby ? { orderby } : {}), // ...(orderBy ? { orderBy } : {}),
Review

????????

????????
...(orderDir ? { order: orderDir } : {}),
page, page,
per_page, per_page,
}; };
@ -287,13 +260,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
// 集合过滤参数 // 集合过滤参数
if (where.exclude) mapped.exclude = toArray(where.exclude); if (where.exclude) mapped.exclude = toArray(where.exclude);
if (where.include) mapped.include = toArray(where.include); if (where.include) mapped.include = toArray(where.include);
if (params.ids) mapped.include = String(params.ids).split(',').map(v => v.trim()).filter(Boolean); if (where.ids) mapped.include = toArray(where.ids);
if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset); 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 ?? 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.parent_exclude ?? where.parentExclude) mapped.parent_exclude = toArray(where.parent_exclude ?? where.parentExclude);
// 状态过滤 参数支持数组或逗号分隔字符串 // 状态过滤 参数支持数组或逗号分隔字符串
const statusSource = params.status ?? where.status; const statusSource = where.status;
if (statusSource !== undefined) { if (statusSource !== undefined) {
mapped.status = Array.isArray(statusSource) mapped.status = Array.isArray(statusSource)
? statusSource.map(s => String(s)) ? statusSource.map(s => String(s))
@ -301,7 +274,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
} }
// 客户与产品过滤 // 客户与产品过滤
const customerVal = params.customer_id ?? where.customer ?? where.customer_id; const customerVal = where.customer ?? where.customer_id;
const productVal = where.product ?? where.product_id; const productVal = where.product ?? where.product_id;
const dpVal = where.dp; const dpVal = where.dp;
if (toNumber(customerVal) !== undefined) mapped.customer = Number(customerVal); if (toNumber(customerVal) !== undefined) mapped.customer = Number(customerVal);
@ -321,28 +294,10 @@ export class WooCommerceAdapter implements ISiteAdapter {
const page = Number(params.page ?? 1); const page = Number(params.page ?? 1);
const per_page = Number(params.per_page ?? 20); const per_page = Number(params.per_page ?? 20);
const where = params.where && typeof params.where === 'object' ? params.where : {}; const where = params.where && typeof params.where === 'object' ? params.where : {};
let orderby: string | undefined = params.orderby;
let orderDir: 'asc' | 'desc' | undefined = params.orderDir as any;
if (!orderby && params.order && typeof params.order === 'object') {
const entries = Object.entries(params.order as Record<string, any>);
if (entries.length > 0) {
const [field, dir] = entries[0];
let mappedField = field;
if (['id', 'ID'].includes(field)) mappedField = 'id';
else if (['include'].includes(field)) mappedField = 'include';
else if (['name', 'username'].includes(field)) mappedField = 'name';
else if (['registered_date', 'date_created', 'registered', 'registeredDate'].includes(field)) mappedField = 'registered_date';
orderby = mappedField;
orderDir = String(dir).toLowerCase() === 'asc' ? 'asc' : 'desc';
}
} else if (!orderDir && typeof params.order === 'string') {
orderDir = String(params.order).toLowerCase() === 'asc' ? 'asc' : 'desc';
}
const mapped: any = { const mapped: any = {
...(params.search ? { search: params.search } : {}), ...(params.search ? { search: params.search } : {}),
...(orderby ? { orderby } : {}),
...(orderDir ? { order: orderDir } : {}),
page, page,
per_page, per_page,
}; };
@ -361,11 +316,11 @@ export class WooCommerceAdapter implements ISiteAdapter {
if (where.exclude) mapped.exclude = toArray(where.exclude); if (where.exclude) mapped.exclude = toArray(where.exclude);
if (where.include) mapped.include = toArray(where.include); if (where.include) mapped.include = toArray(where.include);
if (params.ids) mapped.include = String(params.ids).split(',').map(v => v.trim()).filter(Boolean); if (where.ids) mapped.include = toArray(where.ids);
if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset); if (toNumber(where.offset) !== undefined) mapped.offset = Number(where.offset);
if (where.email) mapped.email = String(where.email); if (where.email) mapped.email = String(where.email);
const roleSource = where.role ?? params.status; const roleSource = where.role;
if (roleSource !== undefined) mapped.role = String(roleSource); if (roleSource !== undefined) mapped.role = String(roleSource);
return mapped; return mapped;
@ -522,6 +477,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
}; };
} }
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);
return products.map((product: any) => this.mapProduct(product));
}
async getProduct(id: string | number): Promise<UnifiedProductDTO> { async getProduct(id: string | number): Promise<UnifiedProductDTO> {
// 获取单个产品详情并映射为统一产品DTO // 获取单个产品详情并映射为统一产品DTO
const api = (this.wpService as any).createApi(this.site, 'wc/v3'); const api = (this.wpService as any).createApi(this.site, 'wc/v3');
@ -602,6 +564,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
return this.mapOrder(res.data); 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> { async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
// 创建订单并返回统一订单DTO // 创建订单并返回统一订单DTO
const api = (this.wpService as any).createApi(this.site, 'wc/v3'); const api = (this.wpService as any).createApi(this.site, 'wc/v3');
@ -705,6 +674,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
}; };
} }
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( async getMedia(
params: UnifiedSearchParamsDTO params: UnifiedSearchParamsDTO
): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> { ): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> {
@ -722,6 +698,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
}; };
} }
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} { private mapReview(item: any): UnifiedReviewDTO & {raw: any} {
// 将 WooCommerce 评论数据映射为统一评论DTO // 将 WooCommerce 评论数据映射为统一评论DTO
return { return {
@ -757,6 +740,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
}; };
} }
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> { async createReview(data: any): Promise<UnifiedReviewDTO> {
const res = await this.wpService.createReview(this.site, data); const res = await this.wpService.createReview(this.site, data);
return this.mapReview(res); return this.mapReview(res);
@ -808,7 +798,6 @@ export class WooCommerceAdapter implements ISiteAdapter {
raw: item, raw: item,
}; };
} }
async getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>> { async getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>> {
const requestParams = this.mapCustomerSearchParams(params); const requestParams = this.mapCustomerSearchParams(params);
const { items, total, totalPages, page, per_page } = await this.wpService.fetchResourcePaged<any>( const { items, total, totalPages, page, per_page } = await this.wpService.fetchResourcePaged<any>(
@ -826,6 +815,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
}; };
} }
async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]> {
// 使用sdkGetAll获取所有客户数据不受分页限制
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const customers = await this.wpService.sdkGetAll(api, 'customers', params);
return customers.map((customer: any) => this.mapCustomer(customer));
}
async getCustomer(id: string | number): Promise<UnifiedCustomerDTO> { async getCustomer(id: string | number): Promise<UnifiedCustomerDTO> {
const api = (this.wpService as any).createApi(this.site, 'wc/v3'); const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.get(`customers/${id}`); const res = await api.get(`customers/${id}`);
@ -850,3 +846,4 @@ export class WooCommerceAdapter implements ISiteAdapter {
return true; return true;
} }
} }

View File

@ -1,8 +1,6 @@
import { MidwayConfig } from '@midwayjs/core'; import { MidwayConfig } from '@midwayjs/core';
import { join } from 'path'; import { join } from 'path';
import { Product } from '../entity/product.entity'; import { Product } from '../entity/product.entity';
import { WpProduct } from '../entity/wp_product.entity';
import { Variation } from '../entity/variation.entity';
import { User } from '../entity/user.entity'; import { User } from '../entity/user.entity';
import { PurchaseOrder } from '../entity/purchase_order.entity'; import { PurchaseOrder } from '../entity/purchase_order.entity';
import { PurchaseOrderItem } from '../entity/purchase_order_item.entity'; import { PurchaseOrderItem } from '../entity/purchase_order_item.entity';
@ -53,8 +51,6 @@ export default {
Product, Product,
ProductStockComponent, ProductStockComponent,
ProductSiteSku, ProductSiteSku,
WpProduct,
Variation,
User, User,
PurchaseOrder, PurchaseOrder,
PurchaseOrderItem, PurchaseOrderItem,
@ -116,17 +112,6 @@ export default {
// secret: 'YOONE2024!@abc', // secret: 'YOONE2024!@abc',
// expiresIn: '7d', // expiresIn: '7d',
// }, // },
// wpSite: [
// {
// id: '2',
// wpApiUrl: 'http://localhost:10004',
// consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652',
// consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38',
// name: 'Local',
// email: 'tom@yoonevape.com',
// emailPswd: '',
// },
// ],
swagger: { swagger: {
auth: { auth: {
name: 'authorization', name: 'authorization',

View File

@ -16,8 +16,10 @@ export default {
dataSource: { dataSource: {
default: { default: {
host: 'localhost', host: 'localhost',
port: "3306",
username: 'root', username: 'root',
password: '12345678', password: 'root',
database: 'inventory',
}, },
}, },
}, },
@ -25,46 +27,12 @@ export default {
origin: '*', // 允许所有来源跨域请求 origin: '*', // 允许所有来源跨域请求
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法 allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法
allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头 allowHeaders: ['Content-Type', 'Authorization'], // 允许的自定义请求头
credentials: true, // 允许携带凭据cookies等 credentials: true, // 允许携带凭据(cookies等)
}, },
jwt: { jwt: {
secret: 'YOONE2024!@abc', secret: 'YOONE2024!@abc',
expiresIn: '7d', expiresIn: '7d',
}, },
wpSite: [
{
id: '-1',
siteName: 'Admin',
email: '2469687281@qq.com',
},
{
id: '2',
wpApiUrl: 'http://t2-shop.local/',
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
siteName: 'Local',
email: '2469687281@qq.com',
emailPswd: 'lulin91.',
},
{
id: '3',
wpApiUrl: 'http://t1-shop.local/',
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
siteName: 'Local-test-2',
email: '2469687281@qq.com',
emailPswd: 'lulin91.',
},
// {
// id: '2',
// wpApiUrl: 'http://localhost:10004',
// consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652',
// consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38',
// siteName: 'Local',
// email: 'tom@yoonevape.com',
// emailPswd: 'lulin91.',
// },
],
freightcom: { freightcom: {
url: 'https://customer-external-api.ssd-test.freightcom.com', url: 'https://customer-external-api.ssd-test.freightcom.com',
token: '6zGj1qPTL1jIkbLmgaiYc6SwHUIXJ2t25htUF8uuFYiCg8ILCY6xnBEbvrX1p79L', token: '6zGj1qPTL1jIkbLmgaiYc6SwHUIXJ2t25htUF8uuFYiCg8ILCY6xnBEbvrX1p79L',

View File

@ -81,8 +81,5 @@ export class MainConfiguration {
} }
} }
); );
const sites = this.app.getConfig('wpSite') || [];
await this.siteService.syncFromConfig(sites);
} }
} }

View File

@ -3,6 +3,7 @@ import { successResponse, errorResponse } from '../utils/response.util';
import { CustomerService } from '../service/customer.service'; import { CustomerService } from '../service/customer.service';
import { QueryCustomerListDTO, CustomerTagDTO } from '../dto/customer.dto'; import { QueryCustomerListDTO, CustomerTagDTO } from '../dto/customer.dto';
import { ApiOkResponse } from '@midwayjs/swagger'; import { ApiOkResponse } from '@midwayjs/swagger';
import { UnifiedSearchParamsDTO } from '../dto/site-api.dto';
@Controller('/customer') @Controller('/customer')
export class CustomerController { export class CustomerController {
@ -13,7 +14,18 @@ export class CustomerController {
@Get('/getcustomerlist') @Get('/getcustomerlist')
async getCustomerList(@Query() query: QueryCustomerListDTO) { async getCustomerList(@Query() query: QueryCustomerListDTO) {
try { try {
const result = await this.customerService.getCustomerList(query as any); const result = await this.customerService.getCustomerList(query)
return successResponse(result);
} catch (error) {
return errorResponse(error.message);
}
}
@ApiOkResponse({ type: Object })
@Get('/getcustomerstatisticlist')
async getCustomerStatisticList(@Query() query: QueryCustomerListDTO) {
try {
const result = await this.customerService.getCustomerStatisticList(query as any);
return successResponse(result); return successResponse(result);
} catch (error) { } catch (error) {
return errorResponse(error.message); return errorResponse(error.message);
@ -63,4 +75,24 @@ export class CustomerController {
return errorResponse(error.message); return errorResponse(error.message);
} }
} }
}
/**
*
*
* service层controller只负责参数传递和响应
*/
@ApiOkResponse({ type: Object })
@Post('/sync')
async syncCustomers(@Body() body: { siteId: number; params?: UnifiedSearchParamsDTO }) {
try {
const { siteId, params = {} } = body;
// 调用service层的同步方法所有业务逻辑都在service中处理
const syncResult = await this.customerService.syncCustomersFromSite(siteId, params);
return successResponse(syncResult);
} catch (error) {
return errorResponse(error.message);
}
}
}

View File

@ -253,21 +253,6 @@ export class ProductController {
} }
} }
// 获取所有 WordPress 商品
@ApiOkResponse({ description: '获取所有 WordPress 商品' })
@Get('/wp-products')
async getWpProducts() {
try {
const data = await this.productService.getWpProducts();
return successResponse(data);
} catch (error) {
return errorResponse(error?.message || error);
}
}
// 通用属性接口:分页列表 // 通用属性接口:分页列表
@ApiOkResponse() @ApiOkResponse()
@Get('/attribute') @Get('/attribute')

View File

@ -23,6 +23,7 @@ import {
CancelShipOrderDTO, CancelShipOrderDTO,
BatchShipOrdersDTO, BatchShipOrdersDTO,
} from '../dto/site-api.dto'; } from '../dto/site-api.dto';
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
import { SiteApiService } from '../service/site-api.service'; import { SiteApiService } from '../service/site-api.service';
import { errorResponse, successResponse } from '../utils/response.util'; import { errorResponse, successResponse } from '../utils/response.util';
import { ILogger } from '@midwayjs/core'; import { ILogger } from '@midwayjs/core';
@ -270,8 +271,8 @@ export class SiteApiController {
page += 1; page += 1;
} }
let items = all; let items = all;
if (query.ids) { if (query.where?.ids) {
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean)); const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean));
items = items.filter(i => ids.has(String(i.id))); items = items.filter(i => ids.has(String(i.id)));
} }
const header = ['id', 'name', 'type', 'status', 'sku', 'regular_price', 'sale_price', 'price', 'stock_status', 'stock_quantity', 'image_src']; const header = ['id', 'name', 'type', 'status', 'sku', 'regular_price', 'sale_price', 'price', 'stock_status', 'stock_quantity', 'image_src'];
@ -533,10 +534,10 @@ export class SiteApiController {
} }
@Post('/:siteId/products/batch') @Post('/:siteId/products/batch')
@ApiOkResponse({ type: Object }) @ApiOkResponse({ type: BatchOperationResultDTO })
async batchProducts( async batchProducts(
@Param('siteId') siteId: number, @Param('siteId') siteId: number,
@Body() body: { create?: any[]; update?: any[]; delete?: Array<string | number> } @Body() body: BatchOperationDTO
) { ) {
this.logger.info(`[Site API] 批量处理产品开始, siteId: ${siteId}`); this.logger.info(`[Site API] 批量处理产品开始, siteId: ${siteId}`);
try { try {
@ -549,14 +550,18 @@ export class SiteApiController {
const created: any[] = []; const created: any[] = [];
const updated: any[] = []; const updated: any[] = [];
const deleted: Array<string | number> = []; const deleted: Array<string | number> = [];
const failed: any[] = []; const errors: Array<{identifier: string, error: string}> = [];
if (body.create?.length) { if (body.create?.length) {
for (const item of body.create) { for (const item of body.create) {
try { try {
const data = await adapter.createProduct(item); const data = await adapter.createProduct(item);
created.push(data); created.push(data);
} catch (e) { } catch (e) {
failed.push({ action: 'create', item, error: (e as any).message }); errors.push({
identifier: String(item.id || item.sku || 'unknown'),
error: (e as any).message
});
} }
} }
} }
@ -567,7 +572,10 @@ export class SiteApiController {
const data = await adapter.updateProduct(id, item); const data = await adapter.updateProduct(id, item);
updated.push(data); updated.push(data);
} catch (e) { } catch (e) {
failed.push({ action: 'update', item, error: (e as any).message }); errors.push({
identifier: String(item.id || 'unknown'),
error: (e as any).message
});
} }
} }
} }
@ -576,14 +584,28 @@ export class SiteApiController {
try { try {
const ok = await adapter.deleteProduct(id); const ok = await adapter.deleteProduct(id);
if (ok) deleted.push(id); if (ok) deleted.push(id);
else failed.push({ action: 'delete', id, error: 'delete failed' }); else errors.push({
identifier: String(id),
error: 'delete failed'
});
} catch (e) { } catch (e) {
failed.push({ action: 'delete', id, error: (e as any).message }); errors.push({
identifier: String(id),
error: (e as any).message
});
} }
} }
} }
this.logger.info(`[Site API] 批量处理产品完成, siteId: ${siteId}`); this.logger.info(`[Site API] 批量处理产品完成, siteId: ${siteId}`);
return successResponse({ created, updated, deleted, failed }); return successResponse({
total: (body.create?.length || 0) + (body.update?.length || 0) + (body.delete?.length || 0),
processed: created.length + updated.length + deleted.length,
created: created.length,
updated: updated.length,
deleted: deleted.length,
errors: errors
});
} catch (error) { } catch (error) {
this.logger.error(`[Site API] 批量处理产品失败, siteId: ${siteId}, 错误信息: ${error.message}`); this.logger.error(`[Site API] 批量处理产品失败, siteId: ${siteId}, 错误信息: ${error.message}`);
return errorResponse(error.message); return errorResponse(error.message);
@ -600,10 +622,6 @@ export class SiteApiController {
try { try {
const adapter = await this.siteApiService.getAdapter(siteId); const adapter = await this.siteApiService.getAdapter(siteId);
const where = { ...(query.where || {}) }; const where = { ...(query.where || {}) };
if (query.customer_id) {
where.customer = query.customer_id;
where.customer_id = query.customer_id;
}
const data = await adapter.getOrders({ ...query, where }); const data = await adapter.getOrders({ ...query, where });
this.logger.info(`[Site API] 获取订单列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个订单`); this.logger.info(`[Site API] 获取订单列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个订单`);
return successResponse(data); return successResponse(data);
@ -623,8 +641,8 @@ export class SiteApiController {
this.logger.info(`[Site API] 获取客户订单列表开始, siteId: ${siteId}, customerId: ${customerId}, query: ${JSON.stringify(query)}`); this.logger.info(`[Site API] 获取客户订单列表开始, siteId: ${siteId}, customerId: ${customerId}, query: ${JSON.stringify(query)}`);
try { try {
const adapter = await this.siteApiService.getAdapter(siteId); const adapter = await this.siteApiService.getAdapter(siteId);
const where = { ...(query.where || {}), customer: customerId, customer_id: customerId }; const where = { ...(query.where || {}), customer: customerId };
const data = await adapter.getOrders({ ...query, where, customer_id: customerId }); const data = await adapter.getOrders({ ...query, where });
this.logger.info(`[Site API] 获取客户订单列表成功, siteId: ${siteId}, customerId: ${customerId}, 共获取到 ${data.total} 个订单`); this.logger.info(`[Site API] 获取客户订单列表成功, siteId: ${siteId}, customerId: ${customerId}, 共获取到 ${data.total} 个订单`);
return successResponse(data); return successResponse(data);
} catch (error) { } catch (error) {
@ -652,8 +670,8 @@ export class SiteApiController {
page += 1; page += 1;
} }
let items = all; let items = all;
if (query.ids) { if (query.where?.ids) {
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean)); const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean));
items = items.filter(i => ids.has(String(i.id))); items = items.filter(i => ids.has(String(i.id)));
} }
const header = ['id', 'number', 'status', 'currency', 'total', 'customer_id', 'customer_name', 'email', 'payment_method', 'phone', 'billing_full_address', 'shipping_full_address', 'date_created']; const header = ['id', 'number', 'status', 'currency', 'total', 'customer_id', 'customer_name', 'email', 'payment_method', 'phone', 'billing_full_address', 'shipping_full_address', 'date_created'];
@ -793,10 +811,10 @@ export class SiteApiController {
} }
@Post('/:siteId/orders/batch') @Post('/:siteId/orders/batch')
@ApiOkResponse({ type: Object }) @ApiOkResponse({ type: BatchOperationResultDTO })
async batchOrders( async batchOrders(
@Param('siteId') siteId: number, @Param('siteId') siteId: number,
@Body() body: { create?: any[]; update?: any[]; delete?: Array<string | number> } @Body() body: BatchOperationDTO
) { ) {
this.logger.info(`[Site API] 批量处理订单开始, siteId: ${siteId}`); this.logger.info(`[Site API] 批量处理订单开始, siteId: ${siteId}`);
try { try {
@ -804,14 +822,18 @@ export class SiteApiController {
const created: any[] = []; const created: any[] = [];
const updated: any[] = []; const updated: any[] = [];
const deleted: Array<string | number> = []; const deleted: Array<string | number> = [];
const failed: any[] = []; const errors: Array<{identifier: string, error: string}> = [];
if (body.create?.length) { if (body.create?.length) {
for (const item of body.create) { for (const item of body.create) {
try { try {
const data = await adapter.createOrder(item); const data = await adapter.createOrder(item);
created.push(data); created.push(data);
} catch (e) { } catch (e) {
failed.push({ action: 'create', item, error: (e as any).message }); errors.push({
identifier: String(item.id || item.order_number || 'unknown'),
error: (e as any).message
});
} }
} }
} }
@ -821,9 +843,15 @@ export class SiteApiController {
const id = item.id; const id = item.id;
const ok = await adapter.updateOrder(id, item); const ok = await adapter.updateOrder(id, item);
if (ok) updated.push(item); if (ok) updated.push(item);
else failed.push({ action: 'update', item, error: 'update failed' }); else errors.push({
identifier: String(item.id || 'unknown'),
error: 'update failed'
});
} catch (e) { } catch (e) {
failed.push({ action: 'update', item, error: (e as any).message }); errors.push({
identifier: String(item.id || 'unknown'),
error: (e as any).message
});
} }
} }
} }
@ -832,14 +860,28 @@ export class SiteApiController {
try { try {
const ok = await adapter.deleteOrder(id); const ok = await adapter.deleteOrder(id);
if (ok) deleted.push(id); if (ok) deleted.push(id);
else failed.push({ action: 'delete', id, error: 'delete failed' }); else errors.push({
identifier: String(id),
error: 'delete failed'
});
} catch (e) { } catch (e) {
failed.push({ action: 'delete', id, error: (e as any).message }); errors.push({
identifier: String(id),
error: (e as any).message
});
} }
} }
} }
this.logger.info(`[Site API] 批量处理订单完成, siteId: ${siteId}`); this.logger.info(`[Site API] 批量处理订单完成, siteId: ${siteId}`);
return successResponse({ created, updated, deleted, failed }); return successResponse({
total: (body.create?.length || 0) + (body.update?.length || 0) + (body.delete?.length || 0),
processed: created.length + updated.length + deleted.length,
created: created.length,
updated: updated.length,
deleted: deleted.length,
errors: errors
});
} catch (error) { } catch (error) {
this.logger.error(`[Site API] 批量处理订单失败, siteId: ${siteId}, 错误信息: ${error.message}`); this.logger.error(`[Site API] 批量处理订单失败, siteId: ${siteId}, 错误信息: ${error.message}`);
return errorResponse(error.message); return errorResponse(error.message);
@ -1005,8 +1047,8 @@ export class SiteApiController {
page += 1; page += 1;
} }
let items = all; let items = all;
if (query.ids) { if (query.where?.ids) {
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean)); const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean));
items = items.filter(i => ids.has(String(i.id))); items = items.filter(i => ids.has(String(i.id)));
} }
const header = ['id', 'status', 'customer_id', 'billing_period', 'billing_interval', 'start_date', 'next_payment_date']; const header = ['id', 'status', 'customer_id', 'billing_period', 'billing_interval', 'start_date', 'next_payment_date'];
@ -1055,8 +1097,8 @@ export class SiteApiController {
page += 1; page += 1;
} }
let items = all; let items = all;
if (query.ids) { if (query.where?.ids) {
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean)); const ids = new Set(String(query.where.ids).split(',').map(v => v.trim()).filter(Boolean));
items = items.filter(i => ids.has(String(i.id))); items = items.filter(i => ids.has(String(i.id)));
} }
const header = ['id', 'title', 'media_type', 'mime_type', 'source_url', 'date_created']; const header = ['id', 'title', 'media_type', 'mime_type', 'source_url', 'date_created'];
@ -1220,10 +1262,6 @@ export class SiteApiController {
page += 1; page += 1;
} }
let items = all; let items = all;
if (query.ids) {
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean));
items = items.filter(i => ids.has(String(i.id)));
}
const header = ['id', 'email', 'first_name', 'last_name', 'fullname', 'username', 'phone', 'orders', 'total_spend', 'role', 'billing_full_address', 'shipping_full_address', 'date_created']; const header = ['id', 'email', 'first_name', 'last_name', 'fullname', 'username', 'phone', 'orders', 'total_spend', 'role', 'billing_full_address', 'shipping_full_address', 'date_created'];
const formatAddress = (addr: any) => [ const formatAddress = (addr: any) => [
addr?.fullname, addr?.fullname,

View File

@ -1,6 +1,6 @@
import { Body, Controller, Get, Inject, Param, Put, Post, Query } from '@midwayjs/core'; import { Body, Controller, Get, Inject, Param, Put, Post, Query } from '@midwayjs/core';
import { ApiOkResponse } from '@midwayjs/swagger'; import { ApiOkResponse } from '@midwayjs/swagger';
import { WpSitesResponse } from '../dto/reponse.dto'; import { SitesResponse } from '../dto/reponse.dto';
import { errorResponse, successResponse } from '../utils/response.util'; import { errorResponse, successResponse } from '../utils/response.util';
import { SiteService } from '../service/site.service'; import { SiteService } from '../service/site.service';
import { CreateSiteDTO, DisableSiteDTO, QuerySiteDTO, UpdateSiteDTO } from '../dto/site.dto'; import { CreateSiteDTO, DisableSiteDTO, QuerySiteDTO, UpdateSiteDTO } from '../dto/site.dto';
@ -10,7 +10,7 @@ export class SiteController {
@Inject() @Inject()
siteService: SiteService; siteService: SiteService;
@ApiOkResponse({ description: '关联网站', type: WpSitesResponse }) @ApiOkResponse({ description: '关联网站', type: SitesResponse })
@Get('/all') @Get('/all')
async all() { async all() {
try { try {

View File

@ -1,4 +1,4 @@
import { HttpStatus, Inject } from '@midwayjs/core'; import { HttpStatus, ILogger, Inject, Logger } from '@midwayjs/core';
import { import {
Controller, Controller,
Post, Post,
@ -25,6 +25,9 @@ export class WebhookController {
@Inject() @Inject()
ctx: Context; ctx: Context;
@Logger()
logger: ILogger;
@Inject() @Inject()
private readonly siteService: SiteService; private readonly siteService: SiteService;
@ -48,7 +51,7 @@ export class WebhookController {
// 从数据库获取站点配置 // 从数据库获取站点配置
const site = await this.siteService.get(siteId, true); const site = await this.siteService.get(siteId, true);
if (!site || !source.includes(site.apiUrl)) { if (!site || !source?.includes(site.apiUrl)) {
console.log('domain not match'); console.log('domain not match');
return { return {
code: HttpStatus.BAD_REQUEST, code: HttpStatus.BAD_REQUEST,

View File

@ -1,232 +0,0 @@
import {
Controller,
Param,
Post,
Inject,
Get,
Query,
Put,
Body,
Files,
Del,
} from '@midwayjs/core';
import { WpProductService } from '../service/wp_product.service';
import { errorResponse, successResponse } from '../utils/response.util';
import { ApiOkResponse } from '@midwayjs/swagger';
import { BooleanRes, WpProductListRes } from '../dto/reponse.dto';
import {
QueryWpProductDTO,
UpdateVariationDTO,
UpdateWpProductDTO,
BatchSyncProductsDTO,
BatchUpdateTagsDTO,
BatchUpdateProductsDTO,
} from '../dto/wp_product.dto';
import {
ProductsRes,
} from '../dto/reponse.dto';
@Controller('/wp_product')
export class WpProductController {
// 移除控制器内的配置站点引用,统一由服务层处理站点数据
@Inject()
private readonly wpProductService: WpProductService;
// 平台服务保留按需注入
@ApiOkResponse({
type: BooleanRes,
})
@Del('/:id')
async delete(@Param('id') id: number) {
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products 删除');
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/import/:siteId')
async importProducts(@Param('siteId') siteId: number, @Files() files) {
try {
if (!files || files.length === 0) {
throw new Error('请上传文件');
}
await this.wpProductService.importProducts(siteId, files[0]);
return successResponse(true);
} catch (error) {
console.error('导入失败:', error);
return errorResponse(error.message || '导入失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/setconstitution')
async setConstitution(@Body() body: any) {
try {
return successResponse(true);
} catch (error) {
return errorResponse(error.message || '设置失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/batch-update')
async batchUpdateProducts(@Body() body: BatchUpdateProductsDTO) {
try {
await this.wpProductService.batchUpdateProducts(body);
return successResponse(true);
} catch (error) {
return errorResponse(error.message || '批量更新失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/batch-update-tags')
async batchUpdateTags(@Body() body: BatchUpdateTagsDTO) {
try {
await this.wpProductService.batchUpdateTags(body.ids, body.tags);
return successResponse(true);
} catch (error) {
return errorResponse(error.message || '批量更新标签失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/sync/:siteId')
async syncProducts(@Param('siteId') siteId: number) {
try {
const result = await this.wpProductService.syncSite(siteId);
return successResponse(result);
} catch (error) {
console.log(error);
return errorResponse('同步失败');
}
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/batch-sync-to-site/:siteId')
async batchSyncToSite(
@Param('siteId') siteId: number,
@Body() body: BatchSyncProductsDTO
) {
try {
await this.wpProductService.batchSyncToSite(siteId, body.productIds);
return successResponse(true, '批量同步成功');
} catch (error) {
console.error('批量同步失败:', error);
return errorResponse(error.message || '批量同步失败');
}
}
@ApiOkResponse({
type: WpProductListRes,
})
@Get('/list')
async getWpProducts(@Query() query: QueryWpProductDTO) {
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products 列表');
}
@ApiOkResponse({
type: BooleanRes
})
@Post('/updateState/:id')
async updateWPProductState(
@Param('id') id: number,
@Body() body: any, // todo
) {
try {
const res = await this.wpProductService.updateProductStatus(id, body?.status, body?.stock_status);
return successResponse(res);
} catch (error) {
return errorResponse(error.message);
}
}
/**
*
* @param siteId ID
* @param body
*/
@ApiOkResponse({
type: BooleanRes,
})
@Post('/siteId/:siteId/products')
async createProduct(
@Param('siteId') siteId: number,
@Body() body: any
) {
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products 创建');
}
/**
*
* @param productId ID
* @param body
*/
@ApiOkResponse({
type: BooleanRes,
})
@Put('/siteId/:siteId/products/:productId')
async updateProduct(
@Param('siteId') siteId: number,
@Param('productId') productId: string,
@Body() body: UpdateWpProductDTO
) {
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products/:id 更新');
}
@ApiOkResponse({
type: BooleanRes,
})
@Post('/sync-to-product/:id')
async syncToProduct(@Param('id') id: number) {
try {
await this.wpProductService.syncToProduct(id);
return successResponse(true);
} catch (error) {
return errorResponse(error.message);
}
}
/**
*
* @param productId ID
* @param variationId ID
* @param body
*/
@Put('/siteId/:siteId/products/:productId/variations/:variationId')
async updateVariation(
@Param('siteId') siteId: number,
@Param('productId') productId: string,
@Param('variationId') variationId: string,
@Body() body: UpdateVariationDTO
) {
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products/:productId/variations/:variationId 更新');
}
@ApiOkResponse({
description: '通过name搜索产品/订单',
type: ProductsRes,
})
@Get('/search')
async searchProducts(@Query('name') name: string) {
try {
// 调用服务获取产品数据
const products = await this.wpProductService.findProductsByName(name);
return successResponse(products);
} catch (error) {
return errorResponse(error.message || '获取数据失败');
}
}
}

View File

@ -0,0 +1,91 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class UpdateProductTableName1765358400000 implements MigrationInterface {
name = 'UpdateProductTableName1765358400000'
public async up(queryRunner: QueryRunner): Promise<void> {
// 1. 使用 try-catch 方式删除外键约束,避免因外键不存在导致迁移失败
try {
await queryRunner.query("ALTER TABLE `product_attributes_dict_item` DROP FOREIGN KEY `FK_592cdbdaebfec346c202ffb82ca`");
} catch (error) {
// 忽略外键不存在的错误
console.log('Warning: Failed to drop foreign key on product_attributes_dict_item. It may not exist.');
}
try {
await queryRunner.query("ALTER TABLE `product_stock_component` DROP FOREIGN KEY `FK_6fe75663083f572a49e7f46909b`");
} catch (error) {
console.log('Warning: Failed to drop foreign key on product_stock_component. It may not exist.');
}
try {
await queryRunner.query("ALTER TABLE `product_site_sku` DROP FOREIGN KEY `FK_3b9b7f3d8a6d9f3e2c0b1a4d5e6`");
} catch (error) {
console.log('Warning: Failed to drop foreign key on product_site_sku. It may not exist.');
}
try {
await queryRunner.query("ALTER TABLE `order_sale` DROP FOREIGN KEY `FK_order_sale_product`");
} catch (error) {
console.log('Warning: Failed to drop foreign key on order_sale. It may not exist.');
}
// 2. 将 product 表重命名为 product_v2
await queryRunner.query("ALTER TABLE `product` RENAME TO `product_v2`");
// 3. 重新创建所有外键约束,引用新的 product_v2 表
await queryRunner.query("ALTER TABLE `product_attributes_dict_item` ADD CONSTRAINT `FK_592cdbdaebfec346c202ffb82ca` FOREIGN KEY (`productId`) REFERENCES `product_v2`(`id`) ON DELETE CASCADE ON UPDATE CASCADE");
await queryRunner.query("ALTER TABLE `product_stock_component` ADD CONSTRAINT `FK_6fe75663083f572a49e7f46909b` FOREIGN KEY (`productId`) REFERENCES `product_v2`(`id`) ON DELETE CASCADE");
await queryRunner.query("ALTER TABLE `product_site_sku` ADD CONSTRAINT `FK_3b9b7f3d8a6d9f3e2c0b1a4d5e6` FOREIGN KEY (`productId`) REFERENCES `product_v2`(`id`) ON DELETE CASCADE");
// 4. 为 order_sale 表添加外键约束
try {
await queryRunner.query("ALTER TABLE `order_sale` ADD CONSTRAINT `FK_order_sale_product` FOREIGN KEY (`productId`) REFERENCES `product_v2`(`id`) ON DELETE CASCADE");
} catch (error) {
console.log('Warning: Failed to add foreign key on order_sale. It may already exist.');
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
// 回滚操作
// 1. 删除外键约束
try {
await queryRunner.query("ALTER TABLE `product_attributes_dict_item` DROP FOREIGN KEY `FK_592cdbdaebfec346c202ffb82ca`");
} catch (error) {
console.log('Warning: Failed to drop foreign key on product_attributes_dict_item during rollback.');
}
try {
await queryRunner.query("ALTER TABLE `product_stock_component` DROP FOREIGN KEY `FK_6fe75663083f572a49e7f46909b`");
} catch (error) {
console.log('Warning: Failed to drop foreign key on product_stock_component during rollback.');
}
try {
await queryRunner.query("ALTER TABLE `product_site_sku` DROP FOREIGN KEY `FK_3b9b7f3d8a6d9f3e2c0b1a4d5e6`");
} catch (error) {
console.log('Warning: Failed to drop foreign key on product_site_sku during rollback.');
}
try {
await queryRunner.query("ALTER TABLE `order_sale` DROP FOREIGN KEY `FK_order_sale_product`");
} catch (error) {
console.log('Warning: Failed to drop foreign key on order_sale during rollback.');
}
// 2. 将 product_v2 表重命名回 product
await queryRunner.query("ALTER TABLE `product_v2` RENAME TO `product`");
// 3. 重新创建外键约束,引用回原来的 product 表
await queryRunner.query("ALTER TABLE `product_attributes_dict_item` ADD CONSTRAINT `FK_592cdbdaebfec346c202ffb82ca` FOREIGN KEY (`productId`) REFERENCES `product`(`id`) ON DELETE CASCADE ON UPDATE CASCADE");
await queryRunner.query("ALTER TABLE `product_stock_component` ADD CONSTRAINT `FK_6fe75663083f572a49e7f46909b` FOREIGN KEY (`productId`) REFERENCES `product`(`id`) ON DELETE CASCADE");
await queryRunner.query("ALTER TABLE `product_site_sku` ADD CONSTRAINT `FK_3b9b7f3d8a6d9f3e2c0b1a4d5e6` FOREIGN KEY (`productId`) REFERENCES `product`(`id`) ON DELETE CASCADE");
// 4. 为 order_sale 表重新创建外键约束
try {
await queryRunner.query("ALTER TABLE `order_sale` ADD CONSTRAINT `FK_order_sale_product` FOREIGN KEY (`productId`) REFERENCES `product`(`id`) ON DELETE CASCADE");
} catch (error) {
console.log('Warning: Failed to add foreign key on order_sale during rollback. It may already exist.');
}
}
}

210
src/dto/batch.dto.ts Normal file
View File

@ -0,0 +1,210 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Rule, RuleType } from '@midwayjs/validate';
/**
*
*/
export interface BatchErrorItem {
// 错误项标识可以是ID、邮箱等
identifier: string;
// 错误信息
error: string;
}
/**
*
*/
export interface BatchOperationResult {
// 总处理数量
total: number;
// 成功处理数量
processed: number;
// 创建数量
created?: number;
// 更新数量
updated?: number;
// 删除数量
deleted?: number;
// 跳过的数量(如数据已存在或无需处理)
skipped?: number;
// 错误列表
errors: BatchErrorItem[];
}
/**
*
*/
export interface SyncOperationResult extends BatchOperationResult {
// 同步成功数量
synced: number;
}
/**
* DTO
*/
export class BatchErrorItemDTO {
@ApiProperty({ description: '错误项标识如ID、邮箱等', type: String })
@Rule(RuleType.string().required())
identifier: string;
@ApiProperty({ description: '错误信息', type: String })
@Rule(RuleType.string().required())
error: string;
}
/**
* DTO
*/
export class BatchOperationResultDTO {
@ApiProperty({ description: '总处理数量', type: Number })
total: number;
@ApiProperty({ description: '成功处理数量', type: Number })
processed: number;
@ApiProperty({ description: '创建数量', type: Number, required: false })
created?: number;
@ApiProperty({ description: '更新数量', type: Number, required: false })
updated?: number;
@ApiProperty({ description: '删除数量', type: Number, required: false })
deleted?: number;
@ApiProperty({ description: '跳过的数量', type: Number, required: false })
skipped?: number;
@ApiProperty({ description: '错误列表', type: [BatchErrorItemDTO] })
errors: BatchErrorItemDTO[];
}
/**
* DTO
*/
export class SyncOperationResultDTO extends BatchOperationResultDTO {
@ApiProperty({ description: '同步成功数量', type: Number })
synced: number;
}
/**
* DTO
*/
export class BatchCreateDTO<T = any> {
@ApiProperty({ description: '要创建的数据列表', type: Array })
@Rule(RuleType.array().required())
items: T[];
}
/**
* DTO
*/
export class BatchUpdateDTO<T = any> {
@ApiProperty({ description: '要更新的数据列表', type: Array })
@Rule(RuleType.array().required())
items: T[];
}
/**
* DTO
*/
export class BatchDeleteDTO {
@ApiProperty({ description: '要删除的ID列表', type: [String, Number] })
@Rule(RuleType.array().items(RuleType.alternatives().try(RuleType.string(), RuleType.number())).required())
ids: Array<string | number>;
}
/**
* DTO
*/
export class BatchOperationDTO<T = any> {
@ApiProperty({ description: '要创建的数据列表', type: Array, required: false })
@Rule(RuleType.array().optional())
create?: T[];
@ApiProperty({ description: '要更新的数据列表', type: Array, required: false })
@Rule(RuleType.array().optional())
update?: T[];
@ApiProperty({ description: '要删除的ID列表', type: [String, Number], required: false })
@Rule(RuleType.array().items(RuleType.alternatives().try(RuleType.string(), RuleType.number())).optional())
delete?: Array<string | number>;
}
/**
* DTO
*/
export class PaginatedBatchOperationDTO<T = any> {
@ApiProperty({ description: '页码', type: Number, required: false, default: 1 })
@Rule(RuleType.number().integer().min(1).optional())
page?: number = 1;
@ApiProperty({ description: '每页数量', type: Number, required: false, default: 100 })
@Rule(RuleType.number().integer().min(1).max(1000).optional())
pageSize?: number = 100;
@ApiProperty({ description: '要创建的数据列表', type: Array, required: false })
@Rule(RuleType.array().optional())
create?: T[];
@ApiProperty({ description: '要更新的数据列表', type: Array, required: false })
@Rule(RuleType.array().optional())
update?: T[];
@ApiProperty({ description: '要删除的ID列表', type: [String, Number], required: false })
@Rule(RuleType.array().items(RuleType.alternatives().try(RuleType.string(), RuleType.number())).optional())
delete?: Array<string | number>;
}
/**
* DTO
*/
export class SyncParamsDTO {
@ApiProperty({ description: '页码', type: Number, required: false, default: 1 })
@Rule(RuleType.number().integer().min(1).optional())
page?: number = 1;
@ApiProperty({ description: '每页数量', type: Number, required: false, default: 100 })
@Rule(RuleType.number().integer().min(1).max(1000).optional())
pageSize?: number = 100;
@ApiProperty({ description: '开始时间', type: String, required: false })
@Rule(RuleType.string().optional())
startDate?: string;
@ApiProperty({ description: '结束时间', type: String, required: false })
@Rule(RuleType.string().optional())
endDate?: string;
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
@Rule(RuleType.boolean().optional())
force?: boolean = false;
}
/**
* DTO
*/
export class BatchQueryDTO {
@ApiProperty({ description: 'ID列表', type: [String, Number] })
@Rule(RuleType.array().items(RuleType.alternatives().try(RuleType.string(), RuleType.number())).required())
ids: Array<string | number>;
@ApiProperty({ description: '包含关联数据', type: Boolean, required: false, default: false })
@Rule(RuleType.boolean().optional())
includeRelations?: boolean = false;
}
/**
*
*/
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
@ApiProperty({ description: '操作成功的数据列表', type: Array })
data?: T[];
}
/**
*
*/
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
@ApiProperty({ description: '同步成功的数据列表', type: Array })
data?: T[];
}

View File

@ -36,3 +36,35 @@ export class CustomerTagDTO {
@ApiProperty() @ApiProperty()
tag: string; tag: string;
} }
export class CustomerDto {
@ApiProperty()
id: number;
@ApiProperty()
site_id: number;
@ApiProperty()
email: string;
@ApiProperty()
avatar: string;
@ApiProperty()
tags: string[];
@ApiProperty()
rate: number;
@ApiProperty()
state: string;
}
export class CustomerListResponseDTO {
@ApiProperty()
total: number;
@ApiProperty({ type: [CustomerDto] })
list: CustomerDto[];
}

View File

@ -11,7 +11,6 @@ import { OrderStatusCountDTO } from './order.dto';
import { SiteConfig } from './site.dto'; import { SiteConfig } from './site.dto';
import { PurchaseOrderDTO, StockDTO, StockRecordDTO } from './stock.dto'; import { PurchaseOrderDTO, StockDTO, StockRecordDTO } from './stock.dto';
import { LoginResDTO } from './user.dto'; import { LoginResDTO } from './user.dto';
import { WpProductDTO } from './wp_product.dto';
import { OrderSale } from '../entity/order_sale.entity'; import { OrderSale } from '../entity/order_sale.entity';
import { Service } from '../entity/service.entity'; import { Service } from '../entity/service.entity';
import { RateDTO } from './freightcom.dto'; import { RateDTO } from './freightcom.dto';
@ -25,7 +24,7 @@ import { Dict } from '../entity/dict.entity';
export class BooleanRes extends SuccessWrapper(Boolean) {} export class BooleanRes extends SuccessWrapper(Boolean) {}
//网站配置返回数据 //网站配置返回数据
export class WpSitesResponse extends SuccessArrayWrapper(SiteConfig) {} export class SitesResponse extends SuccessArrayWrapper(SiteConfig) {}
//产品分页数据 //产品分页数据
export class ProductPaginatedResponse extends PaginatedWrapper(Product) {} export class ProductPaginatedResponse extends PaginatedWrapper(Product) {}
//产品分页返回数据 //产品分页返回数据
@ -77,15 +76,6 @@ export class ProductSizeAllRes extends SuccessArrayWrapper(Dict) {}
// 产品尺寸返回数据 // 产品尺寸返回数据
export class ProductSizeRes extends SuccessWrapper(Dict) {} export class ProductSizeRes extends SuccessWrapper(Dict) {}
//产品分页数据
export class WpProductPaginatedResponse extends PaginatedWrapper(
WpProductDTO
) {}
//产品分页返回数据
export class WpProductListRes extends SuccessWrapper(
WpProductPaginatedResponse
) {}
export class LoginRes extends SuccessWrapper(LoginResDTO) {} export class LoginRes extends SuccessWrapper(LoginResDTO) {}
export class StockPaginatedRespone extends PaginatedWrapper(StockDTO) {} export class StockPaginatedRespone extends PaginatedWrapper(StockDTO) {}
export class StockListRes extends SuccessWrapper(StockPaginatedRespone) {} export class StockListRes extends SuccessWrapper(StockPaginatedRespone) {}

View File

@ -14,9 +14,6 @@ export class UnifiedPaginationDTO<T> {
@ApiProperty({ description: '每页数量', example: 20 }) @ApiProperty({ description: '每页数量', example: 20 })
per_page: number; per_page: number;
@ApiProperty({ description: '每页数量别名', example: 20 })
page_size?: number;
@ApiProperty({ description: '总页数', example: 5 }) @ApiProperty({ description: '总页数', example: 5 })
totalPages: number; totalPages: number;
} }
@ -529,7 +526,7 @@ export class UploadMediaDTO {
filename: string; filename: string;
} }
export class UnifiedSearchParamsDTO { export class UnifiedSearchParamsDTO<Where=Record<string, any>> {
// 统一查询参数DTO用于承载分页与筛选与排序参数 // 统一查询参数DTO用于承载分页与筛选与排序参数
@ApiProperty({ description: '页码', example: 1, required: false }) @ApiProperty({ description: '页码', example: 1, required: false })
page?: number; page?: number;
@ -537,36 +534,18 @@ export class UnifiedSearchParamsDTO {
@ApiProperty({ description: '每页数量', example: 20, required: false }) @ApiProperty({ description: '每页数量', example: 20, required: false })
per_page?: number; per_page?: number;
@ApiProperty({ description: '每页数量别名', example: 20, required: false })
page_size?: number;
@ApiProperty({ description: '搜索关键词', required: false }) @ApiProperty({ description: '搜索关键词', required: false })
search?: string; search?: string;
@ApiProperty({ description: '状态', required: false })
status?: string;
@ApiProperty({ description: '客户ID,用于筛选订单', required: false })
customer_id?: number;
@ApiProperty({ description: '过滤条件对象', type: 'object', required: false }) @ApiProperty({ description: '过滤条件对象', type: 'object', required: false })
where?: Record<string, any>; where?: Where;
@ApiProperty({ @ApiProperty({
description: '排序对象,例如 { "sku": "desc" }', description: '排序对象,例如 { "sku": "desc" }',
type: 'object', type: 'object',
required: false, required: false,
}) })
order?: Record<string, 'asc' | 'desc'> | string; orderBy?: Record<string, 'asc' | 'desc'> | string;
@ApiProperty({ description: '排序字段(兼容旧入参)', required: false })
orderby?: string;
@ApiProperty({ description: '排序方式(兼容旧入参)', required: false })
orderDir?: 'asc' | 'desc';
@ApiProperty({ description: '选中ID列表,逗号分隔', required: false })
ids?: string;
} }
export class UnifiedWebhookDTO { export class UnifiedWebhookDTO {

View File

@ -1,6 +1,7 @@
// WooCommerce 平台原始数据类型定义 // WooCommerce 平台原始数据类型定义
// 仅包含当前映射逻辑所需字段以保持简洁与类型安全 // 仅包含当前映射逻辑所需字段以保持简洁与类型安全
// 产品类型 // 产品类型
export interface WooProduct { export interface WooProduct {
// 产品主键 // 产品主键
@ -124,6 +125,9 @@ export interface WooProduct {
// 元数据 // 元数据
meta_data?: Array<{ id?: number; key: string; value: any }>; meta_data?: Array<{ id?: number; key: string; value: any }>;
} }
export interface WooVariation{
}
// 订单类型 // 订单类型
export interface WooOrder { export interface WooOrder {

View File

@ -1,136 +0,0 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Variation } from '../entity/variation.entity';
import { WpProduct } from '../entity/wp_product.entity';
import { Rule, RuleType } from '@midwayjs/validate';
import { ProductStatus } from '../enums/base.enum';
export class VariationDTO extends Variation {}
export class WpProductDTO extends WpProduct {
@ApiProperty({ description: '变体列表', type: VariationDTO, isArray: true })
variations?: VariationDTO[];
}
export class UpdateVariationDTO {
@ApiProperty({ description: '产品名称' })
@Rule(RuleType.string().optional())
name?: string;
@ApiProperty({ description: 'SKU' })
@Rule(RuleType.string().allow('').optional())
sku?: string;
@ApiProperty({ description: '常规价格', type: Number })
@Rule(RuleType.number().optional())
regular_price?: number; // 常规价格
@ApiProperty({ description: '销售价格', type: Number })
@Rule(RuleType.number().optional())
sale_price?: number; // 销售价格
@ApiProperty({ description: '是否促销中', type: Boolean })
@Rule(RuleType.boolean().optional())
on_sale?: boolean; // 是否促销中
}
export class UpdateWpProductDTO {
@ApiProperty({ description: '变体名称' })
@Rule(RuleType.string().optional())
name?: string;
@ApiProperty({ description: 'SKU' })
@Rule(RuleType.string().allow('').optional())
sku?: string;
@ApiProperty({ description: '常规价格', type: Number })
@Rule(RuleType.number().optional())
regular_price?: number; // 常规价格
@ApiProperty({ description: '销售价格', type: Number })
@Rule(RuleType.number().optional())
sale_price?: number; // 销售价格
@ApiProperty({ description: '是否促销中', type: Boolean })
@Rule(RuleType.boolean().optional())
on_sale?: boolean; // 是否促销中
@ApiProperty({ description: '分类列表', type: [String] })
@Rule(RuleType.array().items(RuleType.string()).optional())
categories?: string[];
@ApiProperty({ description: '标签列表', type: [String] })
@Rule(RuleType.array().items(RuleType.string()).optional())
tags?: string[];
@ApiProperty({ description: '站点ID', required: false })
@Rule(RuleType.number().optional())
siteId?: number;
}
export class QueryWpProductDTO {
@ApiProperty({ example: '1', description: '页码' })
@Rule(RuleType.number())
current: number;
@ApiProperty({ example: '10', description: '每页大小' })
@Rule(RuleType.number())
pageSize: number;
@ApiProperty({ example: 'ZYN', description: '产品名' })
@Rule(RuleType.string())
name?: string;
@ApiProperty({ example: '1', description: '站点ID' })
@Rule(RuleType.string())
siteId?: string;
@ApiProperty({ description: '产品状态', enum: ProductStatus })
@Rule(RuleType.string().valid(...Object.values(ProductStatus)))
status?: ProductStatus;
@ApiProperty({ description: 'SKU列表', type: Array })
@Rule(RuleType.array().items(RuleType.string()).single())
skus?: string[];
}
export class BatchSyncProductsDTO {
@ApiProperty({ description: '产品ID列表', type: [Number] })
@Rule(RuleType.array().items(RuleType.number()).required())
productIds: number[];
}
export class BatchUpdateTagsDTO {
@ApiProperty({ description: '产品ID列表', type: [Number] })
@Rule(RuleType.array().items(RuleType.number()).required())
ids: number[];
@ApiProperty({ description: '标签列表', type: [String] })
@Rule(RuleType.array().items(RuleType.string()).required())
tags: string[];
}
export class BatchUpdateProductsDTO {
@ApiProperty({ description: '产品ID列表', type: [Number] })
@Rule(RuleType.array().items(RuleType.number()).required())
ids: number[];
@ApiProperty({ description: '常规价格', type: Number })
@Rule(RuleType.number())
regular_price?: number;
@ApiProperty({ description: '销售价格', type: Number })
@Rule(RuleType.number())
sale_price?: number;
@ApiProperty({ description: '分类列表', type: [String] })
@Rule(RuleType.array().items(RuleType.string()))
categories?: string[];
@ApiProperty({ description: '标签列表', type: [String] })
@Rule(RuleType.array().items(RuleType.string()))
tags?: string[];
@ApiProperty({ description: '状态', enum: ProductStatus })
@Rule(RuleType.string().valid(...Object.values(ProductStatus)))
status?: ProductStatus;
}

View File

@ -1,13 +1,58 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; import { Column, Entity, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('customer') @Entity('customer')
export class Customer { export class Customer {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;
@Column({ nullable: true })
site_id: number;
@Column({ nullable: true })
origin_id: string;
@Column({ unique: true }) @Column({ unique: true })
email: string; email: string;
@Column({ nullable: true })
first_name: string;
@Column({ nullable: true })
last_name: string;
@Column({ nullable: true })
fullname: string;
@Column({ nullable: true })
username: string;
@Column({ nullable: true })
phone: string;
@Column({ nullable: true })
avatar: string;
@Column({ type: 'json', nullable: true })
billing: any;
@Column({ type: 'json', nullable: true })
shipping: any;
@Column({ type: 'json', nullable: true })
raw: any;
@Column({ default: 0}) @Column({ default: 0})
rate: number; rate: number;
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
@Column({ nullable: true })
site_created_at: Date;
@Column({ nullable: true })
site_updated_at: Date;
} }

View File

@ -9,10 +9,14 @@ import {
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
UpdateDateColumn, UpdateDateColumn,
} from 'typeorm'; } from 'typeorm';
// import { Product } from './product.entity';
@Entity('order_sale') @Entity('order_sale')
@Exclude() @Exclude()
export class OrderSale { export class OrderSale {
// @ManyToOne(() => Product, { onDelete: 'CASCADE' })
// @JoinColumn({ name: 'productId' })
// product: Product;
@ApiProperty() @ApiProperty()
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
@Expose() @Expose()

View File

@ -16,7 +16,7 @@ import { ProductStockComponent } from './product_stock_component.entity';
import { ProductSiteSku } from './product_site_sku.entity'; import { ProductSiteSku } from './product_site_sku.entity';
import { Category } from './category.entity'; import { Category } from './category.entity';
@Entity() @Entity('product_v2')
export class Product { export class Product {
@ApiProperty({ @ApiProperty({
example: '1', example: '1',

View File

@ -10,7 +10,7 @@ import {
import { ApiProperty } from '@midwayjs/swagger'; import { ApiProperty } from '@midwayjs/swagger';
import { Product } from './product.entity'; import { Product } from './product.entity';
@Entity('product_site_sku') @Entity('product_site_sku')
export class ProductSiteSku { export class ProductSiteSku {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;

View File

@ -2,7 +2,7 @@ import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from 't
import { Area } from './area.entity'; import { Area } from './area.entity';
import { StockPoint } from './stock_point.entity'; import { StockPoint } from './stock_point.entity';
@Entity('site') @Entity('site_v2')
export class Site { export class Site {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id: number;

View File

@ -1,104 +0,0 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
Unique,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { ApiProperty } from '@midwayjs/swagger';
@Entity('variation')
@Unique(['siteId', 'externalProductId', 'externalVariationId']) // 确保变体的唯一性
export class Variation {
@ApiProperty({
example: '1',
description: 'ID',
type: 'number',
required: true,
})
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({
description: '站点 id',
example: 1,
required: false
})
@Column({ nullable: true })
siteId: number;
@ApiProperty({
example: '1',
description: 'wp产品ID',
type: 'string',
required: true,
})
@Column()
externalProductId: string; // WooCommerce 产品 ID
@ApiProperty({
example: '1',
description: 'wp变体ID',
type: 'string',
required: true,
})
@Column()
externalVariationId: string; // WooCommerce 变体 ID
@ApiProperty({
example: '1',
description: '对应WP产品表的ID',
type: 'number',
required: true,
})
@Column()
productId: number; // 对应WP产品表的 ID
@ApiProperty({ description: 'sku', type: 'string' })
@Column({ nullable: true })
sku?: string; // sku 编码
@ApiProperty({
description: '变体名称',
type: 'string',
required: true,
})
@Column()
name: string;
@ApiProperty({ description: '常规价格', type: Number })
@Column('decimal', { precision: 10, scale: 2, nullable: true })
regular_price: number; // 常规价格
@ApiProperty({ description: '销售价格', type: Number })
@Column('decimal', { precision: 10, scale: 2, nullable: true })
sale_price: number; // 销售价格
@ApiProperty({ description: '是否促销中', type: Boolean })
@Column({ nullable: true, type: Boolean })
on_sale: boolean; // 是否促销中
@ApiProperty({ description: '是否删除', type: Boolean })
@Column({ nullable: true, type: Boolean , default: false })
on_delete: boolean; // 是否删除
@Column({ type: 'json', nullable: true })
attributes: Record<string, any>; // 变体的属性
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -5,15 +5,6 @@ export interface IUserOptions {
uid: number; uid: number;
} }
export interface WpSite {
id: string;
wpApiUrl: string;
consumerKey: string;
consumerSecret: string;
name: string;
email: string;
emailPswd: string;
}
export interface PaginationParams { export interface PaginationParams {
current?: number; // 当前页码 current?: number; // 当前页码

View File

@ -14,6 +14,7 @@ import {
CreateWebhookDTO, CreateWebhookDTO,
UpdateWebhookDTO, UpdateWebhookDTO,
} from '../dto/site-api.dto'; } from '../dto/site-api.dto';
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
export interface ISiteAdapter { export interface ISiteAdapter {
/** /**
@ -21,6 +22,11 @@ export interface ISiteAdapter {
*/ */
getProducts(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedProductDTO>>; getProducts(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedProductDTO>>;
/**
*
*/
getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]>;
/** /**
* *
*/ */
@ -31,6 +37,11 @@ export interface ISiteAdapter {
*/ */
getOrders(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>>; getOrders(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>>;
/**
*
*/
getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]>;
/** /**
* *
*/ */
@ -41,11 +52,21 @@ export interface ISiteAdapter {
*/ */
getSubscriptions(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>>; getSubscriptions(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>>;
/**
*
*/
getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]>;
/** /**
* *
*/ */
getMedia(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>>; getMedia(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>>;
/**
*
*/
getAllMedia(params?: UnifiedSearchParamsDTO): Promise<UnifiedMediaDTO[]>;
/** /**
* *
*/ */
@ -56,6 +77,11 @@ export interface ISiteAdapter {
*/ */
getReviews(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedReviewDTO>>; getReviews(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedReviewDTO>>;
/**
*
*/
getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]>;
/** /**
* *
*/ */
@ -101,27 +127,33 @@ export interface ISiteAdapter {
*/ */
deleteProduct(id: string | number): Promise<boolean>; deleteProduct(id: string | number): Promise<boolean>;
batchProcessProducts?(data: { create?: any[]; update?: any[]; delete?: Array<string | number> }): Promise<any>; batchProcessProducts?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO>; createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO>;
updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean>; updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean>;
deleteOrder(id: string | number): Promise<boolean>; deleteOrder(id: string | number): Promise<boolean>;
batchProcessOrders?(data: { create?: any[]; update?: any[]; delete?: Array<string | number> }): Promise<any>; batchProcessOrders?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>>; getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>>;
getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]>;
getCustomer(id: string | number): Promise<UnifiedCustomerDTO>; getCustomer(id: string | number): Promise<UnifiedCustomerDTO>;
createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>; createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
updateCustomer(id: string | number, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>; updateCustomer(id: string | number, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
deleteCustomer(id: string | number): Promise<boolean>; deleteCustomer(id: string | number): Promise<boolean>;
batchProcessCustomers?(data: { create?: any[]; update?: any[]; delete?: Array<string | number> }): Promise<any>; batchProcessCustomers?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
/** /**
* webhooks列表 * webhooks列表
*/ */
getWebhooks(params: UnifiedSearchParamsDTO): Promise<UnifiedWebhookPaginationDTO>; getWebhooks(params: UnifiedSearchParamsDTO): Promise<UnifiedWebhookPaginationDTO>;
/**
* webhooks
*/
getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]>;
/** /**
* webhook * webhook
*/ */

View File

@ -23,6 +23,13 @@ export class AuthMiddleware implements IMiddleware<Context, NextFunction> {
'/webhook/woocommerce', '/webhook/woocommerce',
'/logistics/getTrackingNumber', '/logistics/getTrackingNumber',
'/logistics/getListByTrackingId', '/logistics/getListByTrackingId',
'/product/categories/all',
'/product/category/1/attributes',
'/product/category/2/attributes',
'/product/category/3/attributes',
'/product/category/4/attributes',
'/product/list',
'/dict/items',
]; ];
match(ctx: Context) { match(ctx: Context) {

View File

@ -1,9 +1,12 @@
import { Provide } from '@midwayjs/core'; import { Provide, Inject } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Order } from '../entity/order.entity'; import { Order } from '../entity/order.entity';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { CustomerTag } from '../entity/customer_tag.entity'; import { CustomerTag } from '../entity/customer_tag.entity';
import { Customer } from '../entity/customer.entity'; import { Customer } from '../entity/customer.entity';
import { SiteApiService } from './site-api.service';
import { UnifiedCustomerDTO, UnifiedPaginationDTO, UnifiedSearchParamsDTO } from '../dto/site-api.dto';
import { SyncOperationResult, BatchErrorItem } from '../dto/batch.dto';
@Provide() @Provide()
export class CustomerService { export class CustomerService {
@ -16,7 +19,183 @@ export class CustomerService {
@InjectEntityModel(Customer) @InjectEntityModel(Customer)
customerModel: Repository<Customer>; customerModel: Repository<Customer>;
async getCustomerList(param: Record<string, any>) { @Inject()
siteApiService: SiteApiService;
/**
*
*/
async findCustomerByEmail(email: string): Promise<Customer | null> {
return await this.customerModel.findOne({ where: { email } });
}
/**
*
*
*/
private mapSiteCustomerToCustomer(siteCustomer: UnifiedCustomerDTO, siteId: number): Partial<Customer> {
return {
site_id: siteId, // 使用站点ID而不是客户ID
origin_id: "" + siteCustomer.id,
email: siteCustomer.email,
first_name: siteCustomer.first_name,
last_name: siteCustomer.last_name,
fullname: siteCustomer.fullname || `${siteCustomer.first_name || ''} ${siteCustomer.last_name || ''}`.trim(),
username: siteCustomer.username || '',
phone: siteCustomer.phone || '',
avatar: siteCustomer.avatar,
billing: siteCustomer.billing,
shipping: siteCustomer.shipping,
raw: siteCustomer.raw || siteCustomer,
site_created_at: this.parseDate(siteCustomer.date_created),
site_updated_at: this.parseDate(siteCustomer.date_modified)
};
}
/**
*
*/
private parseDate(dateValue: any): Date | null {
if (!dateValue) return null;
if (dateValue instanceof Date) {
return dateValue;
}
if (typeof dateValue === 'number') {
// 处理Unix时间戳秒或毫秒
return new Date(dateValue > 9999999999 ? dateValue : dateValue * 1000);
}
if (typeof dateValue === 'string') {
const date = new Date(dateValue);
return isNaN(date.getTime()) ? null : date;
}
return null;
}
/**
*
*/
async createCustomer(customerData: Partial<Customer>): Promise<Customer> {
const customer = this.customerModel.create(customerData);
return await this.customerModel.save(customer);
}
/**
*
*/
async updateCustomer(id: number, customerData: Partial<Customer>): Promise<Customer> {
await this.customerModel.update(id, customerData);
return await this.customerModel.findOne({ where: { id } });
}
/**
* upsert
*
*/
async upsertCustomer(
customerData: Partial<Customer>,
): Promise<{ customer: Customer; isCreated: boolean }> {
if(!customerData.email) throw new Error("客户邮箱不能为空");
// 首先尝试根据邮箱查找现有客户
const existingCustomer = await this.findCustomerByEmail(customerData.email);
if (existingCustomer) {
// 如果客户存在,更新客户信息
const updatedCustomer = await this.updateCustomer(existingCustomer.id, customerData);
return { customer: updatedCustomer, isCreated: false };
} else {
// 如果客户不存在,创建新客户
const newCustomer = await this.createCustomer(customerData);
return { customer: newCustomer, isCreated: true };
}
}
/**
*
* 使
*/
async upsertManyCustomers(
customersData: Array<Partial<Customer>>
): Promise<{
customers: Customer[];
created: number;
updated: number;
processed: number;
errors: BatchErrorItem[];
}> {
const results = {
customers: [],
created: 0,
updated: 0,
processed: 0,
errors: []
};
// 批量处理每个客户
for (const customerData of customersData) {
try {
const result = await this.upsertCustomer(customerData);
results.customers.push(result.customer);
if (result.isCreated) {
results.created++;
} else {
results.updated++;
}
results.processed++;
} catch (error) {
// 记录错误但不中断整个批量操作
results.errors.push({
identifier: customerData.email || String(customerData.id) || 'unknown',
error: error.message
});
}
}
return results;
}
/**
*
* adapter获取站点客户数据
* upsertManyCustomers保存这些客户
*/
async syncCustomersFromSite(
siteId: number,
params?: UnifiedSearchParamsDTO
): Promise<SyncOperationResult> {
try {
// 第一步:获取适配器并从站点获取客户数据
const adapter = await this.siteApiService.getAdapter(siteId);
const siteCustomers = await adapter.getAllCustomers(params || {});
// 第二步:将站点客户数据转换为客户实体数据
const customersData = siteCustomers.map(siteCustomer => {
return this.mapSiteCustomerToCustomer(siteCustomer, siteId);
});
// 第三步批量upsert客户数据
const upsertResult = await this.upsertManyCustomers(customersData);
return {
total: siteCustomers.length,
processed: upsertResult.customers.length,
synced: upsertResult.customers.length,
updated: upsertResult.updated,
created: upsertResult.created,
errors: upsertResult.errors
};
} catch (error) {
// 如果获取适配器或站点数据失败,抛出错误
throw new Error(`同步客户数据失败: ${error.message}`);
}
}
async getCustomerStatisticList(param: Record<string, any>) {
const { const {
current = 1, current = 1,
pageSize = 10, pageSize = 10,
@ -148,6 +327,112 @@ export class CustomerService {
}; };
} }
/**
*
*
* 使TypeORM查询构建器实现
*/
async getCustomerList(param: Record<string, any>): Promise<UnifiedPaginationDTO<any>>{
const {
current = 1,
pageSize = 10,
email,
firstName,
lastName,
phone,
state,
rate,
sorterKey,
sorterValue,
} = param;
// 创建查询构建器
const queryBuilder = this.customerModel
.createQueryBuilder('c')
.leftJoinAndSelect(
'customer_tag',
'ct',
'ct.email = c.email'
)
.select([
'c.id',
'c.email',
'c.first_name',
'c.last_name',
'c.fullname',
'c.username',
'c.phone',
'c.avatar',
'c.billing',
'c.shipping',
'c.rate',
'c.site_id',
'c.created_at',
'c.updated_at',
'c.site_created_at',
'c.site_updated_at'
])
.groupBy('c.id');
// 邮箱搜索
if (email) {
queryBuilder.andWhere('c.email LIKE :email', { email: `%${email}%` });
}
// 姓名搜索
if (firstName) {
queryBuilder.andWhere('c.first_name LIKE :firstName', { firstName: `%${firstName}%` });
}
if (lastName) {
queryBuilder.andWhere('c.last_name LIKE :lastName', { lastName: `%${lastName}%` });
}
// 电话搜索
if (phone) {
queryBuilder.andWhere('c.phone LIKE :phone', { phone: `%${phone}%` });
}
// 省份搜索
if (state) {
queryBuilder.andWhere("JSON_UNQUOTE(JSON_EXTRACT(c.billing, '$.state')) = :state", { state });
}
// 评分过滤
if (rate !== undefined && rate !== null) {
queryBuilder.andWhere('c.rate = :rate', { rate: Number(rate) });
}
// 排序处理
if (sorterKey) {
const order = sorterValue === 'descend' ? 'DESC' : 'ASC';
queryBuilder.orderBy(`c.${sorterKey}`, order);
} else {
queryBuilder.orderBy('c.created_at', 'DESC');
}
// 分页
queryBuilder.skip((current - 1) * pageSize).take(pageSize);
// 执行查询
const [items, total] = await queryBuilder.getManyAndCount();
// 处理tags字段将逗号分隔的字符串转换为数组
const processedItems = items.map(item => {
const plainItem = JSON.parse(JSON.stringify(item));
plainItem.tags = plainItem.tags ? plainItem.tags.split(',').filter(tag => tag) : [];
return plainItem;
});
return {
items: processedItems,
total,
page: current,
per_page: pageSize,
totalPages: Math.ceil(total / pageSize),
};
}
async addTag(email: string, tag: string) { async addTag(email: string, tag: string) {
const isExist = await this.customerTagModel.findOneBy({ email, tag }); const isExist = await this.customerTagModel.findOneBy({ email, tag });
@ -172,4 +457,4 @@ export class CustomerService {
async setRate(params: { id: number; rate: number }) { async setRate(params: { id: number; rate: number }) {
return await this.customerModel.update(params.id, { rate: params.rate }); return await this.customerModel.update(params.id, { rate: params.rate });
} }
} }

View File

@ -1,6 +1,5 @@
import { Inject, Provide } from '@midwayjs/core'; import { Inject, Provide } from '@midwayjs/core';
import { WPService } from './wp.service'; import { WPService } from './wp.service';
import { WpSite } from '../interface';
import { Order } from '../entity/order.entity'; import { Order } from '../entity/order.entity';
import { In, Like, Repository } from 'typeorm'; import { In, Like, Repository } from 'typeorm';
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm'; import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
@ -8,7 +7,6 @@ import { plainToClass } from 'class-transformer';
import { OrderItem } from '../entity/order_item.entity'; import { OrderItem } from '../entity/order_item.entity';
import { OrderSale } from '../entity/order_sale.entity'; import { OrderSale } from '../entity/order_sale.entity';
import { WpProduct } from '../entity/wp_product.entity';
import { Product } from '../entity/product.entity'; import { Product } from '../entity/product.entity';
import { OrderFee } from '../entity/order_fee.entity'; import { OrderFee } from '../entity/order_fee.entity';
import { OrderRefund } from '../entity/order_refund.entity'; import { OrderRefund } from '../entity/order_refund.entity';
@ -22,7 +20,6 @@ import {
OrderStatus, OrderStatus,
StockRecordOperationType, StockRecordOperationType,
} from '../enums/base.enum'; } from '../enums/base.enum';
import { Variation } from '../entity/variation.entity';
import { CreateOrderNoteDTO, QueryOrderSalesDTO } from '../dto/order.dto'; import { CreateOrderNoteDTO, QueryOrderSalesDTO } from '../dto/order.dto';
import dayjs = require('dayjs'); import dayjs = require('dayjs');
import { OrderDetailRes } from '../dto/reponse.dto'; import { OrderDetailRes } from '../dto/reponse.dto';
@ -58,13 +55,6 @@ export class OrderService {
@InjectEntityModel(OrderSale) @InjectEntityModel(OrderSale)
orderSaleModel: Repository<OrderSale>; orderSaleModel: Repository<OrderSale>;
@InjectEntityModel(WpProduct)
wpProductModel: Repository<WpProduct>;
@InjectEntityModel(Variation)
variationModel: Repository<Variation>;
@InjectEntityModel(Product) @InjectEntityModel(Product)
productModel: Repository<Product>; productModel: Repository<Product>;
@ -1444,8 +1434,7 @@ export class OrderService {
async cancelOrder(id: number) { async cancelOrder(id: number) {
const order = await this.orderModel.findOne({ where: { id } }); const order = await this.orderModel.findOne({ where: { id } });
if (!order) throw new Error(`订单 ${id}不存在`); if (!order) throw new Error(`订单 ${id}不存在`);
const s: any = await this.siteService.get(Number(order.siteId), true); const site = await this.siteService.get(Number(order.siteId), true);
const site = { id: String(s.id), wpApiUrl: s.apiUrl, consumerKey: s.consumerKey, consumerSecret: s.consumerSecret, name: s.name, email: '', emailPswd: '' } as WpSite;
if (order.status !== OrderStatus.CANCEL) { if (order.status !== OrderStatus.CANCEL) {
await this.wpService.updateOrder(site, order.externalOrderId, { await this.wpService.updateOrder(site, order.externalOrderId, {
status: OrderStatus.CANCEL, status: OrderStatus.CANCEL,
@ -1620,86 +1609,84 @@ export class OrderService {
//换货确认按钮改成调用这个方法 //换货确认按钮改成调用这个方法
//换货功能更新OrderSale和Orderitem数据 //换货功能更新OrderSale和Orderitem数据
async updateExchangeOrder(orderId: number, data: any) { async updateExchangeOrder(orderId: number, data: any) {
try { throw new Error('暂未实现')
const dataSource = this.dataSourceManager.getDataSource('default'); // try {
let transactionError = undefined; // const dataSource = this.dataSourceManager.getDataSource('default');
// let transactionError = undefined;
await dataSource.transaction(async manager => { // await dataSource.transaction(async manager => {
const orderRepo = manager.getRepository(Order); // const orderRepo = manager.getRepository(Order);
const orderSaleRepo = manager.getRepository(OrderSale); // const orderSaleRepo = manager.getRepository(OrderSale);
const orderItemRepo = manager.getRepository(OrderItem); // const orderItemRepo = manager.getRepository(OrderItem);
const productRepo = manager.getRepository(Product); // const productRepo = manager.getRepository(ProductV2);
const WpProductRepo = manager.getRepository(WpProduct);
const order = await orderRepo.findOneBy({ id: orderId }); // const order = await orderRepo.findOneBy({ id: orderId });
let product: Product; // let product: ProductV2;
let wpProduct: WpProduct;
await orderSaleRepo.delete({ orderId }); // await orderSaleRepo.delete({ orderId });
await orderItemRepo.delete({ orderId }); // await orderItemRepo.delete({ orderId });
for (const sale of data['sales']) { // for (const sale of data['sales']) {
product = await productRepo.findOneBy({ sku: sale['sku'] }); // product = await productRepo.findOneBy({ sku: sale['sku'] });
await orderSaleRepo.save({ // await orderSaleRepo.save({
orderId, // orderId,
siteId: order.siteId, // siteId: order.siteId,
productId: product.id, // productId: product.id,
name: product.name, // name: product.name,
sku: sale['sku'], // sku: sale['sku'],
quantity: sale['quantity'], // quantity: sale['quantity'],
}); // });
}; // };
for (const item of data['items']) { // for (const item of data['items']) {
wpProduct = await WpProductRepo.findOneBy({ sku: item['sku'] }); // product = await productRepo.findOneBy({ sku: item['sku'] });
// await orderItemRepo.save({
// orderId,
// siteId: order.siteId,
// productId: product.id,
// name: product.name,
// externalOrderId: order.externalOrderId,
// externalProductId: product.externalProductId,
await orderItemRepo.save({ // sku: item['sku'],
orderId, // quantity: item['quantity'],
siteId: order.siteId, // });
productId: wpProduct.id,
name: wpProduct.name,
externalOrderId: order.externalOrderId,
externalProductId: wpProduct.externalProductId,
sku: item['sku'], // };
quantity: item['quantity'],
});
}; // //将是否换货状态改为true
// await orderRepo.update(
// order.id
// , {
// is_exchange: true
// });
//将是否换货状态改为true // //查询这个用户换过多少次货
await orderRepo.update( // const counts = await orderRepo.countBy({
order.id // is_editable: true,
, { // customer_email: order.customer_email,
is_exchange: true // });
});
//查询这个用户换过多少次货 // //批量更新当前用户换货次数
const counts = await orderRepo.countBy({ // await orderRepo.update({
is_editable: true, // customer_email: order.customer_email
customer_email: order.customer_email, // }, {
}); // exchange_frequency: counts
// });
//批量更新当前用户换货次数 // }).catch(error => {
await orderRepo.update({ // transactionError = error;
customer_email: order.customer_email // });
}, {
exchange_frequency: counts
});
}).catch(error => { // if (transactionError !== undefined) {
transactionError = error; // throw new Error(`更新物流信息错误:${transactionError.message}`);
}); // }
// return true;
if (transactionError !== undefined) { // } catch (error) {
throw new Error(`更新物流信息错误:${transactionError.message}`); // throw new Error(`更新发货产品失败:${error.message}`);
} // }
return true;
} catch (error) {
throw new Error(`更新发货产品失败:${error.message}`);
}
} }
} }

View File

@ -19,8 +19,6 @@ import {
SizePaginatedResponse, SizePaginatedResponse,
} from '../dto/reponse.dto'; } from '../dto/reponse.dto';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { WpProduct } from '../entity/wp_product.entity';
import { Variation } from '../entity/variation.entity';
import { Dict } from '../entity/dict.entity'; import { Dict } from '../entity/dict.entity';
import { DictItem } from '../entity/dict_item.entity'; import { DictItem } from '../entity/dict_item.entity';
import { Context } from '@midwayjs/koa'; import { Context } from '@midwayjs/koa';
@ -53,12 +51,6 @@ export class ProductService {
@InjectEntityModel(DictItem) @InjectEntityModel(DictItem)
dictItemModel: Repository<DictItem>; dictItemModel: Repository<DictItem>;
@InjectEntityModel(WpProduct)
wpProductModel: Repository<WpProduct>;
@InjectEntityModel(Variation)
variationModel: Repository<Variation>;
@InjectEntityModel(Stock) @InjectEntityModel(Stock)
stockModel: Repository<Stock>; stockModel: Repository<Stock>;
@ -74,11 +66,6 @@ export class ProductService {
@InjectEntityModel(Category) @InjectEntityModel(Category)
categoryModel: Repository<Category>; categoryModel: Repository<Category>;
// 获取所有 WordPress 商品
async getWpProducts() {
return this.wpProductModel.find();
}
// 获取所有分类 // 获取所有分类
async getCategoriesAll(): Promise<Category[]> { async getCategoriesAll(): Promise<Category[]> {
return this.categoryModel.find({ return this.categoryModel.find({

View File

@ -6,6 +6,7 @@ import { SiteService } from './site.service';
import { Site } from '../entity/site.entity'; import { Site } from '../entity/site.entity';
import { UnifiedReviewDTO } from '../dto/site-api.dto'; import { UnifiedReviewDTO } from '../dto/site-api.dto';
import { ShopyyReview } from '../dto/shopyy.dto'; import { ShopyyReview } from '../dto/shopyy.dto';
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
/** /**
* ShopYY平台服务实现 * ShopYY平台服务实现
@ -533,10 +534,40 @@ export class ShopyyService {
* @param data * @param data
* @returns * @returns
*/ */
async batchProcessProducts(site: any, data: { create?: any[]; update?: any[]; delete?: any[] }): Promise<any> { async batchProcessProducts(site: any, data: BatchOperationDTO): Promise<BatchOperationResultDTO> {
// ShopYY API: POST /products/batch // ShopYY API: POST /products/batch
const response = await this.request(site, 'products/batch', 'POST', data); const response = await this.request(site, 'products/batch', 'POST', data);
return response.data; const result = response.data;
// 转换 ShopYY 批量操作结果为统一格式
const errors: Array<{identifier: string, error: string}> = [];
// 假设 ShopYY 返回格式与 WooCommerce 类似: { create: [...], update: [...], delete: [...] }
// 错误信息可能在每个项目的 error 字段中
const checkForErrors = (items: any[]) => {
items.forEach(item => {
if (item.error) {
errors.push({
identifier: String(item.id || item.sku || 'unknown'),
error: typeof item.error === 'string' ? item.error : JSON.stringify(item.error)
});
}
});
};
// 检查每个操作类型的结果中的错误
if (result.create) checkForErrors(result.create);
if (result.update) checkForErrors(result.update);
if (result.delete) checkForErrors(result.delete);
return {
total: (data.create?.length || 0) + (data.update?.length || 0) + (data.delete?.length || 0),
processed: (result.create?.length || 0) + (result.update?.length || 0) + (result.delete?.length || 0),
created: result.create?.length || 0,
updated: result.update?.length || 0,
deleted: result.delete?.length || 0,
errors: errors
};
} }
/** /**

View File

@ -2,7 +2,6 @@ import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository, Like, In } from 'typeorm'; import { Repository, Like, In } from 'typeorm';
import { Site } from '../entity/site.entity'; import { Site } from '../entity/site.entity';
import { WpSite } from '../interface';
import { CreateSiteDTO, UpdateSiteDTO } from '../dto/site.dto'; import { CreateSiteDTO, UpdateSiteDTO } from '../dto/site.dto';
import { Area } from '../entity/area.entity'; import { Area } from '../entity/area.entity';
import { StockPoint } from '../entity/stock_point.entity'; import { StockPoint } from '../entity/stock_point.entity';
@ -19,29 +18,6 @@ export class SiteService {
@InjectEntityModel(StockPoint) @InjectEntityModel(StockPoint)
stockPointModel: Repository<StockPoint>; stockPointModel: Repository<StockPoint>;
async syncFromConfig(sites: WpSite[] = []) {
// 将配置中的 WpSite 同步到数据库 Site 表(用于一次性导入或初始化)
for (const siteConfig of sites) {
// 按站点名称查询是否已存在记录
const exist = await this.siteModel.findOne({
where: { name: siteConfig.name },
});
// 将 WpSite 字段映射为 Site 实体字段
const payload: Partial<Site> = {
name: siteConfig.name,
apiUrl: (siteConfig as any).wpApiUrl,
consumerKey: (siteConfig as any).consumerKey,
consumerSecret: (siteConfig as any).consumerSecret,
type: 'woocommerce',
};
// 存在则更新,不存在则插入新记录
if (exist) {
await this.siteModel.update({ id: exist.id }, payload);
} else {
await this.siteModel.insert(payload as Site);
}
}
}
async create(data: CreateSiteDTO) { async create(data: CreateSiteDTO) {
// 从 DTO 中分离出区域代码和其他站点数据 // 从 DTO 中分离出区域代码和其他站点数据

View File

@ -5,22 +5,22 @@
import { Inject, Provide } from '@midwayjs/core'; import { Inject, Provide } from '@midwayjs/core';
import axios, { AxiosRequestConfig } from 'axios'; import axios, { AxiosRequestConfig } from 'axios';
import WooCommerceRestApi, { WooCommerceRestApiVersion } from '@woocommerce/woocommerce-rest-api'; import WooCommerceRestApi, { WooCommerceRestApiVersion } from '@woocommerce/woocommerce-rest-api';
import { WpProduct } from '../entity/wp_product.entity';
import { Variation } from '../entity/variation.entity';
import { UpdateVariationDTO, UpdateWpProductDTO } from '../dto/wp_product.dto';
import { SiteService } from './site.service'; import { SiteService } from './site.service';
import { IPlatformService } from '../interface/platform.interface'; import { IPlatformService } from '../interface/platform.interface';
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
import * as FormData from 'form-data'; import * as FormData from 'form-data';
import * as fs from 'fs'; import * as fs from 'fs';
import { WooProduct, WooVariation } from '../dto/woocommerce.dto';
const MAX_PAGE_SIZE = 100;
@Provide() @Provide()
export class WPService implements IPlatformService { export class WPService implements IPlatformService {
getCustomer(site: any, id: number): Promise<any> {
throw new Error('Method not implemented.');
}
@Inject() @Inject()
private readonly siteService: SiteService; private readonly siteService: SiteService;
getCustomer(site: any, id: number): Promise<any> {
throw new Error('Method not implemented.');
}
/** /**
* URL,, / / * URL,, / /
* 使用示例:this.buildURL(wpApiUrl, '/wp-json', 'wc/v3/products', productId) * 使用示例:this.buildURL(wpApiUrl, '/wp-json', 'wc/v3/products', productId)
@ -79,11 +79,79 @@ export class WPService implements IPlatformService {
/** /**
* SDK , * SDK ,
* 使,
* date_created ,
*/ */
private async sdkGetAll<T>(api: WooCommerceRestApi, resource: string, params: Record<string, any> = {}, maxPages: number = 50): Promise<T[]> { async sdkGetAll<T>(api: WooCommerceRestApi, resource: string, params: Record<string, any> = {}, maxPages: number = MAX_PAGE_SIZE): Promise<T[]> {
// 直接传入较大的per_page参数一次性获取所有数据 return this.sdkGetAllConcurrent<T>(api, resource, params, maxPages);
const { items } = await this.sdkGetPage<T>(api, resource, { ...params, per_page: 100 }); }
return items;
/**
* SDK ,使
*
* date_created ,
*/
private async sdkGetAllConcurrent<T>(
api: WooCommerceRestApi,
resource: string,
params: Record<string, any> = {},
maxPages: number = MAX_PAGE_SIZE,
concurrencyLimit: number = 5
): Promise<T[]> {
// 设置默认排序为 date_created 倒序,确保获取最新数据
const defaultParams = {
order: 'desc', // 倒序,优先获取最新数据
per_page: MAX_PAGE_SIZE,
...params
};
// 首先获取第一页数据,同时获取总页数信息
const firstPage = await this.sdkGetPage<T>(api, resource, { ...defaultParams, page: 1 });
const { items: firstPageItems, totalPages } = firstPage;
// 如果只有一页数据,直接返回
if (totalPages <= 1) {
return firstPageItems;
}
// 限制最大页数,避免过多的并发请求
const actualMaxPages = Math.min(totalPages, maxPages);
// 收集所有页面数据,从第二页开始
const allItems = [...firstPageItems];
let currentPage = 2;
// 使用并发限制,避免一次性发起过多请求
while (currentPage <= actualMaxPages) {
const batchPromises: Promise<T[]>[] = [];
const batchSize = Math.min(concurrencyLimit, actualMaxPages - currentPage + 1);
// 创建当前批次的并发请求
for (let i = 0; i < batchSize; i++) {
const page = currentPage + i;
const pagePromise = this.sdkGetPage<T>(api, resource, { ...defaultParams, page })
.then(pageResult => pageResult.items)
.catch(error => {
console.error(`获取第 ${page} 页数据失败:`, error);
return []; // 如果某页获取失败,返回空数组,不影响整体结果
});
batchPromises.push(pagePromise);
}
// 等待当前批次完成
const batchResults = await Promise.all(batchPromises);
// 合并当前批次的数据
for (const pageItems of batchResults) {
allItems.push(...pageItems);
}
// 移动到下一批次
currentPage += batchSize;
}
return allItems;
} }
/** /**
@ -174,7 +242,7 @@ export class WPService implements IPlatformService {
async getProducts(site: any, page: number = 1, pageSize: number = 100): Promise<any> { async getProducts(site: any, page: number = 1, pageSize: number = 100): Promise<any> {
const api = this.createApi(site, 'wc/v3'); const api = this.createApi(site, 'wc/v3');
return await this.sdkGetPage<WpProduct>(api, 'products', { page, per_page: pageSize }); return await this.sdkGetPage<WooProduct>(api, 'products', { page, per_page: pageSize });
} }
async getProduct(site: any, id: number): Promise<any> { async getProduct(site: any, id: number): Promise<any> {
@ -195,17 +263,17 @@ export class WPService implements IPlatformService {
async getVariations(site: any, productId: number, page: number = 1, pageSize: number = 100): Promise<any> { async getVariations(site: any, productId: number, page: number = 1, pageSize: number = 100): Promise<any> {
const api = this.createApi(site, 'wc/v3'); const api = this.createApi(site, 'wc/v3');
return await this.sdkGetPage<Variation>(api, `products/${productId}/variations`, { page, per_page: pageSize }); return await this.sdkGetPage<WooVariation>(api, `products/${productId}/variations`, { page, per_page: pageSize });
} }
async getVariation( async getVariation(
site: any, site: any,
productId: number, productId: number,
variationId: number variationId: number
): Promise<Variation> { ): Promise<WooVariation> {
const api = this.createApi(site, 'wc/v3'); const api = this.createApi(site, 'wc/v3');
const res = await api.get(`products/${productId}/variations/${variationId}`); const res = await api.get(`products/${productId}/variations/${variationId}`);
return res.data as Variation; return res.data as WooVariation;
} }
async getOrder( async getOrder(
@ -323,7 +391,7 @@ export class WPService implements IPlatformService {
async updateProduct( async updateProduct(
site: any, site: any,
productId: string, productId: string,
data: UpdateWpProductDTO data: WooProduct
): Promise<any> { ): Promise<any> {
const { regular_price, sale_price, ...params } = data; const { regular_price, sale_price, ...params } = data;
const api = this.createApi(site, 'wc/v3'); const api = this.createApi(site, 'wc/v3');
@ -440,7 +508,7 @@ export class WPService implements IPlatformService {
site: any, site: any,
productId: string, productId: string,
variationId: string, variationId: string,
data: Partial<UpdateVariationDTO> data: Partial<WooVariation & any>
): Promise<boolean> { ): Promise<boolean> {
const { regular_price, sale_price, ...params } = data; const { regular_price, sale_price, ...params } = data;
const api = this.createApi(site, 'wc/v3'); const api = this.createApi(site, 'wc/v3');
@ -551,12 +619,42 @@ export class WPService implements IPlatformService {
*/ */
async batchProcessProducts( async batchProcessProducts(
site: any, site: any,
data: { create?: any[]; update?: any[]; delete?: any[] } data: BatchOperationDTO
): Promise<any> { ): Promise<BatchOperationResultDTO> {
const api = this.createApi(site, 'wc/v3'); const api = this.createApi(site, 'wc/v3');
try { try {
const response = await api.post('products/batch', data); const response = await api.post('products/batch', data);
return response.data; const result = response.data;
// 转换 WooCommerce 批量操作结果为统一格式
const errors: Array<{identifier: string, error: string}> = [];
// WooCommerce 返回格式: { create: [...], update: [...], delete: [...] }
// 错误信息可能在每个项目的 error 字段中
const checkForErrors = (items: any[]) => {
items.forEach(item => {
if (item.error) {
errors.push({
identifier: String(item.id || item.sku || 'unknown'),
error: typeof item.error === 'string' ? item.error : JSON.stringify(item.error)
});
}
});
};
// 检查每个操作类型的结果中的错误
if (result.create) checkForErrors(result.create);
if (result.update) checkForErrors(result.update);
if (result.delete) checkForErrors(result.delete);
return {
total: (data.create?.length || 0) + (data.update?.length || 0) + (data.delete?.length || 0),
processed: (result.create?.length || 0) + (result.update?.length || 0) + (result.delete?.length || 0),
created: result.create?.length || 0,
updated: result.update?.length || 0,
deleted: result.delete?.length || 0,
errors: errors
};
} catch (error) { } catch (error) {
console.error('批量处理产品失败:', error.response?.data || error.message); console.error('批量处理产品失败:', error.response?.data || error.message);
throw error; throw error;

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
{"root":["./src/configuration.ts","./src/interface.ts","./src/config/config.default.ts","./src/config/config.local.ts","./src/config/config.unittest.ts","./src/controller/api.controller.ts","./src/controller/area.controller.ts","./src/controller/customer.controller.ts","./src/controller/dict.controller.ts","./src/controller/locale.controller.ts","./src/controller/logistics.controller.ts","./src/controller/order.controller.ts","./src/controller/product.controller.ts","./src/controller/site.controller.ts","./src/controller/statistics.controller.ts","./src/controller/stock.controller.ts","./src/controller/subscription.controller.ts","./src/controller/template.controller.ts","./src/controller/user.controller.ts","./src/controller/webhook.controller.ts","./src/controller/wp_product.controller.ts","./src/db/datasource.ts","./src/db/migrations/1764238434984-product-dict-item-many-to-many.ts","./src/db/migrations/1764294088896-area.ts","./src/db/migrations/1764299629279-productstock.ts","./src/db/seeds/area.seeder.ts","./src/db/seeds/dict.seeder.ts","./src/db/seeds/template.seeder.ts","./src/decorator/user.decorator.ts","./src/dto/area.dto.ts","./src/dto/customer.dto.ts","./src/dto/dict.dto.ts","./src/dto/freightcom.dto.ts","./src/dto/logistics.dto.ts","./src/dto/order.dto.ts","./src/dto/product.dto.ts","./src/dto/reponse.dto.ts","./src/dto/site.dto.ts","./src/dto/statistics.dto.ts","./src/dto/stock.dto.ts","./src/dto/subscription.dto.ts","./src/dto/template.dto.ts","./src/dto/user.dto.ts","./src/dto/wp_product.dto.ts","./src/entity/area.entity.ts","./src/entity/auth_code.ts","./src/entity/customer.entity.ts","./src/entity/customer_tag.entity.ts","./src/entity/device_whitelist.ts","./src/entity/dict.entity.ts","./src/entity/dict_item.entity.ts","./src/entity/order.entity.ts","./src/entity/order_coupon.entity.ts","./src/entity/order_fee.entity.ts","./src/entity/order_item.entity.ts","./src/entity/order_item_original.entity.ts","./src/entity/order_items_original.entity.ts","./src/entity/order_note.entity.ts","./src/entity/order_refund.entity.ts","./src/entity/order_refund_item.entity.ts","./src/entity/order_sale.entity.ts","./src/entity/order_shipment.entity.ts","./src/entity/order_shipping.entity.ts","./src/entity/product.entity.ts","./src/entity/product_stock_component.entity.ts","./src/entity/purchase_order.entity.ts","./src/entity/purchase_order_item.entity.ts","./src/entity/service.entity.ts","./src/entity/shipment.entity.ts","./src/entity/shipment_item.entity.ts","./src/entity/shipping_address.entity.ts","./src/entity/site.entity.ts","./src/entity/stock.entity.ts","./src/entity/stock_point.entity.ts","./src/entity/stock_record.entity.ts","./src/entity/subscription.entity.ts","./src/entity/template.entity.ts","./src/entity/transfer.entity.ts","./src/entity/transfer_item.entity.ts","./src/entity/user.entity.ts","./src/entity/variation.entity.ts","./src/entity/wp_product.entity.ts","./src/enums/base.enum.ts","./src/filter/default.filter.ts","./src/filter/notfound.filter.ts","./src/job/sync_products.job.ts","./src/job/sync_shipment.job.ts","./src/middleware/auth.middleware.ts","./src/middleware/report.middleware.ts","./src/service/area.service.ts","./src/service/authcode.service.ts","./src/service/canadapost.service.ts","./src/service/customer.service.ts","./src/service/devicewhitelist.service.ts","./src/service/dict.service.ts","./src/service/freightcom.service.ts","./src/service/logistics.service.ts","./src/service/mail.service.ts","./src/service/order.service.ts","./src/service/product.service.ts","./src/service/site.service.ts","./src/service/statistics.service.ts","./src/service/stock.service.ts","./src/service/subscription.service.ts","./src/service/template.service.ts","./src/service/uni_express.service.ts","./src/service/user.service.ts","./src/service/wp.service.ts","./src/service/wp_product.service.ts","./src/utils/helper.util.ts","./src/utils/object-transform.util.ts","./src/utils/paginate.util.ts","./src/utils/paginated-response.util.ts","./src/utils/response-wrapper.util.ts","./src/utils/response.util.ts"],"version":"5.9.3"} {"root":["./src/configuration.ts","./src/interface.ts","./src/config/config.default.ts","./src/config/config.local.ts","./src/config/config.unittest.ts","./src/controller/api.controller.ts","./src/controller/area.controller.ts","./src/controller/customer.controller.ts","./src/controller/dict.controller.ts","./src/controller/locale.controller.ts","./src/controller/logistics.controller.ts","./src/controller/order.controller.ts","./src/controller/product.controller.ts","./src/controller/site.controller.ts","./src/controller/statistics.controller.ts","./src/controller/stock.controller.ts","./src/controller/subscription.controller.ts","./src/controller/template.controller.ts","./src/controller/user.controller.ts","./src/controller/webhook.controller.ts","./src/controller/wp_product.controller.ts","./src/db/datasource.ts","./src/db/migrations/1764238434984-product-dict-item-many-to-many.ts","./src/db/migrations/1764294088896-area.ts","./src/db/migrations/1764299629279-productstock.ts","./src/db/seeds/area.seeder.ts","./src/db/seeds/dict.seeder.ts","./src/db/seeds/template.seeder.ts","./src/decorator/user.decorator.ts","./src/dto/area.dto.ts","./src/dto/customer.dto.ts","./src/dto/dict.dto.ts","./src/dto/freightcom.dto.ts","./src/dto/logistics.dto.ts","./src/dto/order.dto.ts","./src/dto/product.dto.ts","./src/dto/reponse.dto.ts","./src/dto/site.dto.ts","./src/dto/statistics.dto.ts","./src/dto/stock.dto.ts","./src/dto/subscription.dto.ts","./src/dto/template.dto.ts","./src/dto/user.dto.ts","./src/dto/wp_product.dto.ts","./src/entity/area.entity.ts","./src/entity/auth_code.ts","./src/entity/customer.entity.ts","./src/entity/customer_tag.entity.ts","./src/entity/device_whitelist.ts","./src/entity/dict.entity.ts","./src/entity/dict_item.entity.ts","./src/entity/order.entity.ts","./src/entity/order_coupon.entity.ts","./src/entity/order_fee.entity.ts","./src/entity/order_item.entity.ts","./src/entity/order_item_original.entity.ts","./src/entity/order_items_original.entity.ts","./src/entity/order_note.entity.ts","./src/entity/order_refund.entity.ts","./src/entity/order_refund_item.entity.ts","./src/entity/order_sale.entity.ts","./src/entity/order_shipment.entity.ts","./src/entity/order_shipping.entity.ts","./src/entity/product.ts","./src/entity/product_stock_component.entity.ts","./src/entity/purchase_order.entity.ts","./src/entity/purchase_order_item.entity.ts","./src/entity/service.entity.ts","./src/entity/shipment.entity.ts","./src/entity/shipment_item.entity.ts","./src/entity/shipping_address.entity.ts","./src/entity/site.entity.ts","./src/entity/stock.entity.ts","./src/entity/stock_point.entity.ts","./src/entity/stock_record.entity.ts","./src/entity/subscription.entity.ts","./src/entity/template.entity.ts","./src/entity/transfer.entity.ts","./src/entity/transfer_item.entity.ts","./src/entity/user.entity.ts","./src/entity/variation.entity.ts","./src/entity/wp_product.ts","./src/enums/base.enum.ts","./src/filter/default.filter.ts","./src/filter/notfound.filter.ts","./src/job/sync_products.job.ts","./src/job/sync_shipment.job.ts","./src/middleware/auth.middleware.ts","./src/middleware/report.middleware.ts","./src/service/area.service.ts","./src/service/authcode.service.ts","./src/service/canadapost.service.ts","./src/service/customer.service.ts","./src/service/devicewhitelist.service.ts","./src/service/dict.service.ts","./src/service/freightcom.service.ts","./src/service/logistics.service.ts","./src/service/mail.service.ts","./src/service/order.service.ts","./src/service/product.service.ts","./src/service/site.service.ts","./src/service/statistics.service.ts","./src/service/stock.service.ts","./src/service/subscription.service.ts","./src/service/template.service.ts","./src/service/uni_express.service.ts","./src/service/user.service.ts","./src/service/wp.service.ts","./src/service/wp_product.service.ts","./src/utils/helper.util.ts","./src/utils/object-transform.util.ts","./src/utils/paginate.util.ts","./src/utils/paginated-response.util.ts","./src/utils/response-wrapper.util.ts","./src/utils/response.util.ts"],"version":"5.9.3"}