Compare commits

...

1 Commits

Author SHA1 Message Date
tikkhun 6f35d5d9c9 feat(订单): 添加获取订单总数功能
实现订单总数统计接口,包括:
1. 在ISiteAdapter接口添加countOrders方法
2. 在WooCommerce和Shopyy适配器中实现该方法
3. 添加控制器端点暴露该功能
4. 优化订单查询参数映射逻辑

refactor(Shopyy): 重构搜索参数映射逻辑

将通用的搜索参数映射逻辑提取为独立方法,提高代码复用性

refactor(interface): 重构站点适配器接口,按功能模块组织方法

重构 ISiteAdapter 接口,将相关方法按功能模块(客户、媒体、订单、产品等)分组
移除废弃的 fulfillOrder 方法
新增多个数据映射方法以支持统一数据格式转换

refactor(api): 统一接口参数为对象形式并支持多条件查询

重构所有接口方法,将直接传递id参数改为接受where条件对象
支持通过id、sku、email等多条件查询实体
优化产品服务逻辑,支持通过sku直接查询产品
统一各适配器实现,确保接口一致性

feat(订单): 添加获取订单总数功能

实现订单总数统计接口,包括:
1. 在ISiteAdapter接口添加countOrders方法
2. 在WooCommerce和Shopyy适配器中实现该方法
3. 添加控制器端点暴露该功能
4. 优化订单查询参数映射逻辑

refactor(Shopyy): 重构搜索参数映射逻辑

将通用的搜索参数映射逻辑提取为独立方法,提高代码复用性

refactor(interface): 重构站点适配器接口,按功能模块组织方法

重构 ISiteAdapter 接口,将相关方法按功能模块(客户、媒体、订单、产品等)分组
移除废弃的 fulfillOrder 方法
新增多个数据映射方法以支持统一数据格式转换

Test

refactor(api): 统一接口参数为对象形式并支持多条件查询

重构所有接口方法,将直接传递id参数改为接受where条件对象
支持通过id、sku、email等多条件查询实体
优化产品服务逻辑,支持通过sku直接查询产品
统一各适配器实现,确保接口一致性

feat(shopyy): 实现全量商品查询功能并优化产品相关逻辑

- 新增ShopyyAllProductQuery类支持全量商品查询参数
- 实现getAllProducts方法支持带条件查询
- 优化getProductBySku方法使用新查询接口
- 公开request方法便于子类调用
- 增加错误日志记录产品查找失败情况
- 修复产品permalink生成逻辑

docs: 统一中文括号格式为全角括号

将代码中的中文括号格式从半角"()"统一修改为全角"()",并删除测试文档文件test-site-sku-methods.md

chore: config.local 还原

docs(dto): 修正注释中的中文括号格式

docs(dto): 修正注释中的括号格式

docs: 修正中文标点符号和注释格式

统一将中文注释和文档中的全角括号和冒号改为半角格式
修正部分TODO注释的标点符号
统一接口文档中的描述符号格式
2026-01-08 20:21:33 +08:00
25 changed files with 163 additions and 471 deletions

View File

@ -1,184 +0,0 @@
# Permutation页面列表显示问题分析和修复方案
## 问题分析
经过代码分析,发现了以下几个可能导致列表不显示的问题:
### 1. API路径不匹配
前端代码中引用的API函数名与后端控制器中的路径不一致
- 前端:`productcontrollerGetcategoriesall`、`productcontrollerGetcategoryattributes`、`productcontrollerGetproductlist`
- 后端实际的API路径`/product/categories/all`、`/product/category/:id/attributes`、`/product/list`
### 2. 数据格式问题
- `getCategoryAttributes`返回的数据结构与前端期望的不匹配
- 属性值获取逻辑可能存在问题
### 3. 组合生成逻辑问题
- 在生成排列组合时,数据结构和键值对应可能不正确
## 修复方案
### 后端修复
1. **修改getCategoryAttributes方法** - 在`/Users/zksu/Developer/work/workcode/API/src/service/product.service.ts`中:
```typescript
// 获取分类下的属性配置
async getCategoryAttributes(categoryId: number): Promise<any[]> {
const category = await this.categoryModel.findOne({
where: { id: categoryId },
relations: ['attributes', 'attributes.attributeDict', 'attributes.attributeDict.items'],
});
if (!category) {
return [];
}
// 格式化返回,匹配前端期望的数据结构
return category.attributes.map(attr => ({
id: attr.id,
dictId: attr.attributeDict.id,
name: attr.attributeDict.name, // 用于generateKeyFromPermutation
title: attr.attributeDict.title, // 用于列标题
dict: {
id: attr.attributeDict.id,
name: attr.attributeDict.name,
title: attr.attributeDict.title,
items: attr.attributeDict.items || []
}
}));
}
```
2. **确保dict/items接口可用** - 检查字典项获取接口:
在`/Users/zksu/Developer/work/workcode/API/src/controller/dict.controller.ts`中添加或确认:
```typescript
@Get('/items')
async getDictItems(@Query('dictId') dictId: number) {
try {
const dict = await this.dictModel.findOne({
where: { id: dictId },
relations: ['items']
});
if (!dict) {
return [];
}
return dict.items || [];
} catch (error) {
return errorResponse(error?.message || error);
}
}
```
### 前端修复建议
1. **添加错误处理和调试信息**
```typescript
// 在获取属性值的地方添加错误处理
const fetchData = async () => {
setLoading(true);
try {
// 1. Fetch Attributes
const attrRes = await productcontrollerGetcategoryattributes({
id: categoryId,
});
console.log('Attributes response:', attrRes); // 调试用
const attrs = Array.isArray(attrRes) ? attrRes : attrRes?.data || [];
setAttributes(attrs);
// 2. Fetch Attribute Values (Dict Items)
const valuesMap: Record<string, any[]> = {};
for (const attr of attrs) {
const dictId = attr.dict?.id || attr.dictId;
if (dictId) {
try {
const itemsRes = await request('/dict/items', {
params: { dictId },
});
console.log(`Dict items for ${attr.name}:`, itemsRes); // 调试用
valuesMap[attr.name] = itemsRes || [];
} catch (error) {
console.error(`Failed to fetch items for dict ${dictId}:`, error);
valuesMap[attr.name] = [];
}
}
}
setAttributeValues(valuesMap);
// 3. Fetch Existing Products
await fetchProducts(categoryId);
} catch (error) {
console.error('Error in fetchData:', error);
message.error('获取数据失败');
} finally {
setLoading(false);
}
};
```
2. **修复组合生成逻辑**
```typescript
// 修改generateKeyFromPermutation函数
const generateKeyFromPermutation = (perm: any) => {
const parts = Object.keys(perm).map((attrName) => {
const valItem = perm[attrName];
const val = valItem.name || valItem.value; // 兼容不同的数据格式
return `${attrName}:${val}`;
});
return parts.sort().join('|');
};
// 修改generateAttributeKey函数
const generateAttributeKey = (attrs: any[]) => {
const parts = attrs.map((a) => {
const key = a.dict?.name || a.dictName || a.name;
const val = a.name || a.value;
return `${key}:${val}`;
});
return parts.sort().join('|');
};
```
3. **添加空状态处理**
```typescript
// 在ProTable中添加空状态提示
<ProTable
// ... 其他属性
locale={{
emptyText: permutations.length === 0 && !loading ? '暂无数据,请检查分类属性配置' : '暂无数据'
}}
/>
```
## 调试步骤
1. **检查网络请求**
- 打开浏览器开发者工具
- 检查 `/product/categories/all` 请求是否成功
- 检查 `/product/category/:id/attributes` 请求返回的数据格式
- 检查 `/dict/items?dictId=:id` 请求是否成功
- 检查 `/product/list` 请求是否成功
2. **检查控制台日志**
- 查看属性数据是否正确加载
- 查看属性值是否正确获取
- 查看排列组合是否正确生成
3. **检查数据结构**
- 确认 `attributes` 数组是否正确
- 确认 `attributeValues` 对象是否正确填充
- 确认 `permutations` 数组是否正确生成
## 测试验证
1. 选择一个有属性配置的分类
2. 确认属性有对应的字典项
3. 检查排列组合是否正确显示
4. 验证现有产品匹配是否正确

View File

@ -403,9 +403,12 @@ export class ShopyyAdapter implements ISiteAdapter {
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): any {
return data
}
mapProductQuery(query: UnifiedSearchParamsDTO): ShopyyProductQuery {
return this.mapSearchParams(query)
}
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): any {
// 构建 ShopYY 订单更新参数(仅包含传入的字段)
// 构建 ShopYY 订单更新参数(仅包含传入的字段)
const params: any = {};
// 仅当字段存在时才添加到更新参数中
@ -665,7 +668,7 @@ export class ShopyyAdapter implements ISiteAdapter {
const statusMap = {
'pending': '100', // 100 未完成
'processing': '110', // 110 待处理
'completed': "180", // 180 已完成(确认收货)
'completed': "180", // 180 已完成(确认收货)
'cancelled': '190', // 190 取消
};
@ -797,7 +800,7 @@ export class ShopyyAdapter implements ISiteAdapter {
return status === 'publish' ? 1 : 0;
};
// 构建 ShopYY 产品更新参数(仅包含传入的字段)
// 构建 ShopYY 产品更新参数(仅包含传入的字段)
const params: any = {};
// 仅当字段存在时才添加到更新参数中
@ -816,7 +819,7 @@ export class ShopyyAdapter implements ISiteAdapter {
params.inventory_quantity = data.stock_status === 'instock' ? (data.stock_quantity || 1) : 0;
}
// 添加变体信息(如果存在)
// 添加变体信息(如果存在)
if (data.variations && data.variations.length > 0) {
params.variants = data.variations.map((variation: UnifiedProductVariationDTO) => {
const variationParams: any = {};
@ -836,7 +839,7 @@ export class ShopyyAdapter implements ISiteAdapter {
});
}
// 添加图片信息(如果存在)
// 添加图片信息(如果存在)
if (data.images && data.images.length > 0) {
params.images = data.images.map((image: any) => ({
id: image.id,
@ -846,12 +849,12 @@ export class ShopyyAdapter implements ISiteAdapter {
}));
}
// 添加标签信息(如果存在)
// 添加标签信息(如果存在)
if (data.tags && data.tags.length > 0) {
params.tags = data.tags.map((tag: any) => tag.name || '');
}
// 添加分类信息(如果存在)
// 添加分类信息(如果存在)
if (data.categories && data.categories.length > 0) {
params.collections = data.categories.map((category: any) => ({
id: category.id,
@ -977,6 +980,18 @@ export class ShopyyAdapter implements ISiteAdapter {
return product
}
// 通过sku获取产品详情的私有方法
private async getProductBySku(sku: string): Promise<UnifiedProductDTO> {
// 使用Shopyy API的搜索功能通过sku查询产品
const response = await this.getAllProducts({ where: {sku} });
console.log('getProductBySku', response)
const product = response?.[0]
if (!product) {
throw new Error(`未找到sku为${sku}的产品`);
}
return product
}
async batchProcessProducts(
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
): Promise<any> {
@ -1121,7 +1136,7 @@ export class ShopyyAdapter implements ISiteAdapter {
}
mapUpdateVariationParams(data: Partial<UnifiedProductVariationDTO>): any {
// 构建 ShopYY 变体更新参数(仅包含传入的字段)
// 构建 ShopYY 变体更新参数(仅包含传入的字段)
const params: any = {};
// 仅当字段存在时才添加到更新参数中
@ -1179,7 +1194,10 @@ export class ShopyyAdapter implements ISiteAdapter {
}
// ========== Webhook映射方法 ==========
<<<<<<< HEAD
=======
>>>>>>> 89d7d78 (refactor(api): )
mapUnifiedToPlatformWebhook(data: Partial<UnifiedWebhookDTO>) {
return data
@ -1292,12 +1310,12 @@ export class ShopyyAdapter implements ISiteAdapter {
return stockStatus === 'instock' ? 1 : 0;
};
shopyyOrderStatusMap = {//订单状态 100 未完成110 待处理180 已完成(确认收货); 190 取消;
shopyyOrderStatusMap = {//订单状态 100 未完成110 待处理180 已完成(确认收货); 190 取消;
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
// 已发货
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
}

View File

@ -55,6 +55,15 @@ export class WooCommerceAdapter implements ISiteAdapter {
mapUpdateVariationParams(data: UpdateVariationDTO) {
throw new Error('Method not implemented.');
}
batchProcessProducts?(data: BatchOperationDTO): Promise<BatchOperationResultDTO> {
throw new Error('Method not implemented.');
}
mapCreateVariationParams(data: CreateVariationDTO) {
throw new Error('Method not implemented.');
}
mapUpdateVariationParams(data: UpdateVariationDTO) {
throw new Error('Method not implemented.');
}
// ========== 客户映射方法 ==========
@ -394,53 +403,30 @@ export class WooCommerceAdapter implements ISiteAdapter {
// 将 WooCommerce 订单数据映射为统一订单DTO
// 包含账单地址与收货地址以及创建与更新时间
// 映射物流追踪信息,将后端格式转换为前端期望的格式
const fulfillments = (item.fulfillments || []).map((track: any) => ({
tracking_number: track.tracking_number || '',
shipping_provider: track.shipping_provider || '',
shipping_method: track.shipping_method || '',
status: track.status || '',
date_created: track.date_created || '',
items: track.items || [],
}));
// 产品操作方法
async getProduct(id: string | number): Promise<UnifiedProductDTO> {
// 获取单个产品详情并映射为统一产品DTO
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
const res = await api.get(`products/${id}`);
const product = res.data;
return {
id: item.id,
number: item.number,
status: item.status,
currency: item.currency,
total: item.total,
customer_id: item.customer_id,
customer_email: item.billing?.email || '', // TODO 与 email 重复 保留一个即可
email: item.billing?.email || '',
customer_name: `${item.billing?.first_name || ''} ${item.billing?.last_name || ''}`.trim(),
refunds: item.refunds?.map?.(refund => ({
id: refund.id,
reason: refund.reason,
total: refund.total,
})),
line_items: (item.line_items as any[]).map(li => ({
...li,
productId: li.product_id,
})),
customer_ip_address: item.customer_ip_address ?? '',
date_paid: item.date_paid ?? '',
utm_source: item?.meta_data?.find(el => el.key === '_wc_order_attribution_utm_source')?.value || '',
device_type: item?.meta_data?.find(el => el.key === '_wc_order_attribution_device_type')?.value || '',
source_type: item?.meta_data?.find(el => el.key === '_wc_order_attribution_source_type')?.value || '',
billing: item.billing,
shipping: item.shipping,
billing_full_address: this.buildFullAddress(item.billing),
shipping_full_address: this.buildFullAddress(item.shipping),
payment_method: item.payment_method_title,
date_created: item.date_created,
date_modified: item.date_modified,
shipping_lines: item.shipping_lines,
fee_lines: item.fee_lines,
coupon_lines: item.coupon_lines,
fulfillments,
raw: item,
};
// 如果产品类型是 variable 且有变体 ID 列表,则加载完整的变体数据
if (product.type === 'variable' && product.variations && Array.isArray(product.variations) && product.variations.length > 0) {
try {
// 批量获取该产品的所有变体数据
const variations = await this.wpService.sdkGetAll(
api,
`products/${product.id}/variations`
);
// 将完整的变体数据添加到产品对象中
product.variations = variations;
} catch (error) {
// 如果获取变体失败,保持原有的 ID 数组
console.error(`获取产品 ${product.id} 的变体数据失败:`, error);
}
}
return this.mapProduct(product);
}
// 订单操作方法
@ -609,7 +595,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
// await api.put(`orders/${orderId}`, { status: 'processing' });
// // 添加取消履行的备注
// const note = `订单履行已取消${data.reason ? `,原因${data.reason}` : ''}`;
// const note = `订单履行已取消${data.reason ? `,原因:${data.reason}` : ''}`;
// await api.post(`orders/${orderId}/notes`, { note, customer_note: true });
// return {
@ -698,14 +684,14 @@ export class WooCommerceAdapter implements ISiteAdapter {
}));
}
// 映射变体数据注意WooCommerce API 中变体通常通过单独的端点处理)
// 映射变体数据(注意:WooCommerce API 中变体通常通过单独的端点处理)
// 这里只映射变体的基本信息,具体创建/更新变体需要额外处理
if (data.variations && Array.isArray(data.variations)) {
// 对于WooProduct类型variations字段只存储变体ID
mapped.variations = data.variations.map(variation => variation.id as number);
}
// 映射下载数据(如果产品是可下载的)
// 映射下载数据(如果产品是可下载的)
// if (data.downloads && Array.isArray(data.downloads)) {
// mapped.downloads = data.downloads.map(download => ({
// id: download.id as number,

View File

@ -7,8 +7,11 @@ export default {
// dataSource: {
// default: {
// host: '13.212.62.127',
// port: "3306",
// username: 'root',
// password: 'Yoone!@.2025',
// database: 'inventory_v2',
// synchronize: true,
// },
// },
// },
@ -19,7 +22,7 @@ export default {
port: "3306",
username: 'root',
password: 'root',
database: 'inventory',
database: 'inventory'
},
},
},

View File

@ -99,7 +99,7 @@ export class MainConfiguration {
}
/**
*
* ()
*/
private async initializeDatabase(): Promise<void> {
// 使用注入的数据库配置

View File

@ -299,7 +299,7 @@ export class SiteApiController {
}
}
// 平台特性产品导出特殊CSV走平台服务
// 平台特性:产品导出(特殊CSV走平台服务)
@Get('/:siteId/links')
async getLinks(
@Param('siteId') siteId: number
@ -429,7 +429,7 @@ export class SiteApiController {
}
}
// 平台特性产品导入特殊CSV走平台服务
// 平台特性:产品导入(特殊CSV走平台服务)
@Post('/:siteId/products/import-special')
@ApiOkResponse({ type: Object })
async importProductsSpecial(
@ -443,7 +443,7 @@ export class SiteApiController {
const created: any[] = [];
const failed: any[] = [];
if (site.type === 'woocommerce') {
// 解析 CSV 为对象数组(若传入 items 则优先 items
// 解析 CSV 为对象数组(若传入 items 则优先 items)
let payloads = items;
if (!payloads.length && csvText) {
const lines = csvText.split(/\r?\n/).filter(Boolean);

View File

@ -126,7 +126,7 @@ const flavorsData = [
{ name: 'arctic-mint', title: 'arctic mint', titleCn: '北极薄荷', shortName: 'AR' },
{ name: 'baddie-blueberries', title: 'baddie blueberries', titleCn: '时髦蓝莓', shortName: 'BA' },
{ name: 'banana', title: 'banana', titleCn: '香蕉', shortName: 'BA' },
{ name: 'banana-(solid)', title: 'banana (solid)', titleCn: '香蕉(固体)', shortName: 'BA' },
{ name: 'banana-(solid)', title: 'banana (solid)', titleCn: '香蕉(固体)', shortName: 'BA' },
{ name: 'banana-berry', title: 'banana berry', titleCn: '香蕉莓果', shortName: 'BA' },
{ name: 'banana-berry-melon-ice', title: 'banana berry melon ice', titleCn: '香蕉莓果瓜冰', shortName: 'BA' },
{ name: 'banana-blackberry', title: 'banana blackberry', titleCn: '香蕉黑莓', shortName: 'BA' },
@ -137,7 +137,7 @@ const flavorsData = [
{ name: 'bangin-blood-orange-iced', title: 'bangin blood orange iced', titleCn: '爆炸血橙冰', shortName: 'BA' },
{ name: 'berries-in-the-6ix', title: 'berries in the 6ix', titleCn: '多伦多莓果', shortName: 'BE' },
{ name: 'berry-burst', title: 'berry burst', titleCn: '浆果爆发', shortName: 'BE' },
{ name: 'berry-burst-(thermal)', title: 'berry burst (thermal)', titleCn: '浆果爆发(热感)', shortName: 'BE' },
{ name: 'berry-burst-(thermal)', title: 'berry burst (thermal)', titleCn: '浆果爆发(热感)', shortName: 'BE' },
{ name: 'berry-ice', title: 'berry ice', titleCn: '浆果冰', shortName: 'BE' },
{ name: 'berry-lime-ice', title: 'berry lime ice', titleCn: '浆果青柠冰', shortName: 'BE' },
{ name: 'berry-trio-ice', title: 'berry trio ice', titleCn: '三重浆果冰', shortName: 'BE' },
@ -145,7 +145,7 @@ const flavorsData = [
{ name: 'black-cherry', title: 'black cherry', titleCn: '黑樱桃', shortName: 'BL' },
{ name: 'blackcherry', title: 'blackcherry', titleCn: '黑樱桃混合', shortName: 'BL' },
{ name: 'blackcurrant-ice', title: 'blackcurrant ice', titleCn: '黑加仑冰', shortName: 'BL' },
{ name: 'black-currant-ice', title: 'black currant ice', titleCn: '黑加仑冰(空格版)', shortName: 'BL' },
{ name: 'black-currant-ice', title: 'black currant ice', titleCn: '黑加仑冰(空格版)', shortName: 'BL' },
{ name: 'black-licorice', title: 'black licorice', titleCn: '黑甘草', shortName: 'BL' },
{ name: 'black-tea', title: 'black tea', titleCn: '红茶', shortName: 'BL' },
{ name: 'blackberry-ice', title: 'blackberry ice', titleCn: '黑莓冰', shortName: 'BL' },
@ -168,7 +168,7 @@ const flavorsData = [
{ name: 'blue-razz', title: 'blue razz', titleCn: '蓝覆盆子', shortName: 'BL' },
{ name: 'blue-razz-hype', title: 'blue razz hype', titleCn: '蓝覆盆子热情', shortName: 'BL' },
{ name: 'blue-razz-ice', title: 'blue razz ice', titleCn: '蓝覆盆子冰', shortName: 'BL' },
{ name: 'blue-razz-ice-(solid)', title: 'blue razz ice (solid)', titleCn: '蓝覆盆子冰(固体)', shortName: 'BL' },
{ name: 'blue-razz-ice-(solid)', title: 'blue razz ice (solid)', titleCn: '蓝覆盆子冰(固体)', shortName: 'BL' },
{ name: 'blue-razz-ice-glace', title: 'blue razz ice glace', titleCn: '蓝覆盆子冰格', shortName: 'BL' },
{ name: 'blue-razz-lemon-ice', title: 'blue razz lemon ice', titleCn: '蓝覆盆子柠檬冰', shortName: 'BL' },
{ name: 'blue-razz-lemonade', title: 'blue razz lemonade', titleCn: '蓝覆盆子柠檬水', shortName: 'BL' },
@ -196,7 +196,7 @@ const flavorsData = [
{ name: 'bumpin-blackcurrant-iced', title: 'bumpin blackcurrant iced', titleCn: '黑加仑热烈冰', shortName: 'BU' },
{ name: 'burst-ice', title: 'burst ice', titleCn: '爆炸冰', shortName: 'BU' },
{ name: 'bussin-banana-iced', title: 'bussin banana iced', titleCn: '香蕉热烈冰', shortName: 'BU' },
{ name: 'bussin-banana-iced', title: 'bussin banana iced', titleCn: '香蕉热烈冰(重复)', shortName: 'BU' },
{ name: 'bussin-banana-iced', title: 'bussin banana iced', titleCn: '香蕉热烈冰(重复)', shortName: 'BU' },
{ name: 'california-cherry', title: 'california cherry', titleCn: '加州樱桃', shortName: 'CA' },
{ name: 'cantaloupe-mango-banana', title: 'cantaloupe mango banana', titleCn: '香瓜芒果香蕉', shortName: 'CA' },
{ name: 'caramel', title: 'caramel', titleCn: '焦糖', shortName: 'CA' },
@ -230,7 +230,7 @@ const flavorsData = [
{ name: 'citrus-chill', title: 'citrus chill', titleCn: '柑橘清凉', shortName: 'CI' },
{ name: 'citrus-smash-ice', title: 'citrus smash ice', titleCn: '柑橘冲击冰', shortName: 'CI' },
{ name: 'citrus-sunrise', title: 'citrus sunrise', titleCn: '柑橘日出', shortName: 'CI' },
{ name: 'citrus-sunrise-(thermal)', title: 'citrus sunrise (thermal)', titleCn: '柑橘日出(热感)', shortName: 'CI' },
{ name: 'citrus-sunrise-(thermal)', title: 'citrus sunrise (thermal)', titleCn: '柑橘日出(热感)', shortName: 'CI' },
{ name: 'classic', title: 'classic', titleCn: '经典', shortName: 'CL' },
{ name: 'classic-ice', title: 'classic ice', titleCn: '经典冰', shortName: 'CL' },
{ name: 'classic-mint-ice', title: 'classic mint ice', titleCn: '经典薄荷冰', shortName: 'CL' },
@ -310,7 +310,7 @@ const flavorsData = [
{ name: 'fizzy', title: 'fizzy', titleCn: '汽水', shortName: 'FI' },
{ name: 'flavourless', title: 'flavourless', titleCn: '无味', shortName: 'FL' },
{ name: 'flippin-fruit-flash', title: 'flippin fruit flash', titleCn: '翻转水果闪电', shortName: 'FL' },
{ name: 'flippin-fruit-flash-(rainbow-burst)', title: 'flippin fruit flash (rainbow burst)', titleCn: '翻转水果闪电(彩虹爆发)', shortName: 'FL' },
{ name: 'flippin-fruit-flash-(rainbow-burst)', title: 'flippin fruit flash (rainbow burst)', titleCn: '翻转水果闪电(彩虹爆发)', shortName: 'FL' },
{ name: 'forest-fruits', title: 'forest fruits', titleCn: '森林水果', shortName: 'FO' },
{ name: 'fragrant-grapefruit', title: 'fragrant grapefruit', titleCn: '香气葡萄柚', shortName: 'FR' },
{ name: 'freeze', title: 'freeze', titleCn: '冰冻', shortName: 'FR' },
@ -340,14 +340,14 @@ const flavorsData = [
{ name: 'fuji-melon-ice', title: 'fuji melon ice', titleCn: '富士瓜冰', shortName: 'FU' },
{ name: 'full-charge', title: 'full charge', titleCn: '满电', shortName: 'FU' },
{ name: 'gb', title: 'gb', titleCn: '软糖', shortName: 'GB' },
{ name: 'gb(gummy-bear)', title: 'gb(gummy bear)', titleCn: '软糖Gummy Bear', shortName: 'GB' },
{ name: 'gb(gummy-bear)', title: 'gb(gummy bear)', titleCn: '软糖(Gummy Bear)', shortName: 'GB' },
{ name: 'gentle-mint', title: 'gentle mint', titleCn: '温和薄荷', shortName: 'GE' },
{ name: 'ghost-cola-&-vanilla', title: 'ghost cola & vanilla', titleCn: '幽灵可乐香草', shortName: 'GH' },
{ name: 'ghost-cola-ice', title: 'ghost cola ice', titleCn: '幽灵可乐冰', shortName: 'GH' },
{ name: 'ghost-mango', title: 'ghost mango', titleCn: '幽灵芒果', shortName: 'GH' },
{ name: 'ghost-original', title: 'ghost original', titleCn: '幽灵原味', shortName: 'GH' },
{ name: 'ghost-watermelon-ice', title: 'ghost watermelon ice', titleCn: '幽灵西瓜冰', shortName: 'GH' },
{ name: 'gnarly-green-d-(green-dew)', title: 'gnarly green d (green dew)', titleCn: '狂野绿 D(绿色露水)', shortName: 'GN' },
{ name: 'gnarly-green-d-(green-dew)', title: 'gnarly green d (green dew)', titleCn: '狂野绿 D(绿色露水)', shortName: 'GN' },
{ name: 'gold-edition', title: 'gold edition', titleCn: '金版', shortName: 'GO' },
{ name: 'grape', title: 'grape', titleCn: '葡萄', shortName: 'GR' },
{ name: 'grape-cherry', title: 'grape cherry', titleCn: '葡萄樱桃', shortName: 'GR' },
@ -492,13 +492,13 @@ const flavorsData = [
{ name: 'mixed-fruit', title: 'mixed fruit', titleCn: '混合水果', shortName: 'MI' },
{ name: 'mocha-ice', title: 'mocha ice', titleCn: '摩卡冰', shortName: 'MO' },
{ name: 'morocco-mint', title: 'morocco mint', titleCn: '摩洛哥薄荷', shortName: 'MO' },
{ name: 'morocco-mint-(thermal)', title: 'morocco mint (thermal)', titleCn: '摩洛哥薄荷(热感)', shortName: 'MO' },
{ name: 'morocco-mint-(thermal)', title: 'morocco mint (thermal)', titleCn: '摩洛哥薄荷(热感)', shortName: 'MO' },
{ name: 'mung-beans', title: 'mung beans', titleCn: '绿豆', shortName: 'MU' },
{ name: 'nasty-tropic', title: 'nasty tropic', titleCn: '恶搞热带', shortName: 'NA' },
{ name: 'nectarine-ice', title: 'nectarine ice', titleCn: '油桃冰', shortName: 'NE' },
{ name: 'night-rider', title: 'night rider', titleCn: '夜骑', shortName: 'NI' },
{ name: 'nirvana', title: 'nirvana', titleCn: '宁静蓝莓', shortName: 'NI' },
{ name: 'north-american-style(root-beer)', title: 'north american style(root beer)', titleCn: '北美风格(根啤)', shortName: 'NO' },
{ name: 'north-american-style(root-beer)', title: 'north american style(root beer)', titleCn: '北美风格(根啤)', shortName: 'NO' },
{ name: 'northern-blue-razz', title: 'northern blue razz', titleCn: '北方蓝覆盆子', shortName: 'NO' },
{ name: 'nutty-virginia', title: 'nutty virginia', titleCn: '坚果弗吉尼亚', shortName: 'NU' },
{ name: 'orange', title: 'orange', titleCn: '橙子', shortName: 'OR' },
@ -508,12 +508,12 @@ const flavorsData = [
{ name: 'orange-mango-guava', title: 'orange mango guava', titleCn: '橙子芒果番石榴', shortName: 'OR' },
{ name: 'orange-mango-pineapple-ice', title: 'orange mango pineapple ice', titleCn: '橙子芒果菠萝冰', shortName: 'OR' },
{ name: 'orange-p', title: 'orange p', titleCn: '橙子 P', shortName: 'OR' },
{ name: 'orange-p(fanta)', title: 'orange p(fanta)', titleCn: '橙子 P(芬达)', shortName: 'OR' },
{ name: 'orange-p(fanta)', title: 'orange p(fanta)', titleCn: '橙子 P(芬达)', shortName: 'OR' },
{ name: 'orange-spark', title: 'orange spark', titleCn: '橙色火花', shortName: 'OR' },
{ name: 'orange-tangerine', title: 'orange tangerine', titleCn: '橙子柑橘', shortName: 'OR' },
{ name: 'original', title: 'original', titleCn: '原味', shortName: 'OR' },
{ name: 'packin-peach-berry', title: 'packin peach berry', titleCn: '装满桃浆果', shortName: 'PA' },
{ name: 'packin-peach-berry-(popn-peach-berry)', title: 'packin peach berry (popn peach berry)', titleCn: '装满桃浆果Popn 桃浆果)', shortName: 'PA' },
{ name: 'packin-peach-berry-(popn-peach-berry)', title: 'packin peach berry (popn peach berry)', titleCn: '装满桃浆果(Popn 桃浆果)', shortName: 'PA' },
{ name: 'papio', title: 'papio', titleCn: 'Papio', shortName: 'PA' },
{ name: 'paradise', title: 'paradise', titleCn: '天堂', shortName: 'PA' },
{ name: 'paradise-iced', title: 'paradise iced', titleCn: '天堂冰', shortName: 'PA' },
@ -603,7 +603,7 @@ const flavorsData = [
{ name: 'red-fruits', title: 'red fruits', titleCn: '红色水果', shortName: 'RE' },
{ name: 'red-lightning', title: 'red lightning', titleCn: '红色闪电', shortName: 'RE' },
{ name: 'red-line', title: 'red line', titleCn: '红线', shortName: 'RE' },
{ name: 'red-line-(energy-drink)', title: 'red line (energy drink)', titleCn: '红线(能量饮料)', shortName: 'RE' },
{ name: 'red-line-(energy-drink)', title: 'red line (energy drink)', titleCn: '红线(能量饮料)', shortName: 'RE' },
{ name: 'red-magic', title: 'red magic', titleCn: '红魔', shortName: 'RE' },
{ name: 'rich-tobacco', title: 'rich tobacco', titleCn: '浓烈烟草', shortName: 'RI' },
{ name: 'root-beer', title: 'root beer', titleCn: '根啤', shortName: 'RO' },
@ -625,8 +625,8 @@ const flavorsData = [
{ name: 'sic-strawberry-iced', title: 'sic strawberry iced', titleCn: '意大利草莓冰', shortName: 'SI' },
{ name: 'simply-spearmint', title: 'simply spearmint', titleCn: '清爽留兰香', shortName: 'SI' },
{ name: 'skc', title: 'skc', titleCn: 'SKC', shortName: 'SK' },
{ name: 'skc(skittles-candy)', title: 'skc(skittles candy)', titleCn: 'SKC(彩虹糖)', shortName: 'SK' },
{ name: 'slammin-sts-(sour-snap)', title: 'slammin sts (sour snap)', titleCn: '热烈 STS(酸糖)', shortName: 'SL' },
{ name: 'skc(skittles-candy)', title: 'skc(skittles candy)', titleCn: 'SKC(彩虹糖)', shortName: 'SK' },
{ name: 'slammin-sts-(sour-snap)', title: 'slammin sts (sour snap)', titleCn: '热烈 STS(酸糖)', shortName: 'SL' },
{ name: 'slammin-sts-iced', title: 'slammin sts iced', titleCn: '热烈 STS 冰', shortName: 'SL' },
{ name: 'smooth', title: 'smooth', titleCn: '顺滑', shortName: 'SM' },
{ name: 'smooth-mint', title: 'smooth mint', titleCn: '顺滑薄荷', shortName: 'SM' },
@ -664,7 +664,7 @@ const flavorsData = [
{ name: 'strawberry-jasmine-t', title: 'strawberry jasmine t', titleCn: '草莓茉莉茶', shortName: 'ST' },
{ name: 'strawberry-jasmine-tea', title: 'strawberry jasmine tea', titleCn: '草莓茉莉茶', shortName: 'ST' },
{ name: 'strawberry-kiwi', title: 'strawberry kiwi', titleCn: '草莓奇异果', shortName: 'ST' },
{ name: 'strawberry-kiwi-(solid)', title: 'strawberry kiwi (solid)', titleCn: '草莓奇异果(固体)', shortName: 'ST' },
{ name: 'strawberry-kiwi-(solid)', title: 'strawberry kiwi (solid)', titleCn: '草莓奇异果(固体)', shortName: 'ST' },
{ name: 'strawberry-kiwi-banana-ice', title: 'strawberry kiwi banana ice', titleCn: '草莓奇异果香蕉冰', shortName: 'ST' },
{ name: 'strawberry-kiwi-guava-ice', title: 'strawberry kiwi guava ice', titleCn: '草莓奇异果番石榴冰', shortName: 'ST' },
{ name: 'strawberry-kiwi-ice', title: 'strawberry kiwi ice', titleCn: '草莓奇异果冰', shortName: 'ST' },
@ -680,10 +680,10 @@ const flavorsData = [
{ name: 'strawberry-watermelon', title: 'strawberry watermelon', titleCn: '草莓西瓜', shortName: 'ST' },
{ name: 'strawberry-watermelon-ice', title: 'strawberry watermelon ice', titleCn: '草莓西瓜冰', shortName: 'ST' },
{ name: 'strawmelon-peach', title: 'strawmelon peach', titleCn: '草莓桃', shortName: 'ST' },
{ name: 'strawmelon-peach-(solid)', title: 'strawmelon peach (solid)', titleCn: '草莓桃(固体)', shortName: 'ST' },
{ name: 'strawmelon-peach-(solid)', title: 'strawmelon peach (solid)', titleCn: '草莓桃(固体)', shortName: 'ST' },
{ name: 'strawnana-orange', title: 'strawnana orange', titleCn: '草莓香蕉橙', shortName: 'ST' },
{ name: 'summer-grape', title: 'summer grape', titleCn: '夏日葡萄', shortName: 'SU' },
{ name: 'summer-grape-(thermal)', title: 'summer grape (thermal)', titleCn: '夏日葡萄(热感)', shortName: 'SU' },
{ name: 'summer-grape-(thermal)', title: 'summer grape (thermal)', titleCn: '夏日葡萄(热感)', shortName: 'SU' },
{ name: 'super-sour-blueberry-iced', title: 'super sour blueberry iced', titleCn: '超级酸蓝莓冰', shortName: 'SU' },
{ name: 'super-spearmint', title: 'super spearmint', titleCn: '超级留兰香', shortName: 'SU' },
{ name: 'super-spearmint-iced', title: 'super spearmint iced', titleCn: '超级留兰香冰', shortName: 'SU' },
@ -704,7 +704,7 @@ const flavorsData = [
{ name: 'tropical-orang-ice', title: 'tropical orang ice', titleCn: '热带橙冰', shortName: 'TR' },
{ name: 'tropical-prism-blast', title: 'tropical prism blast', titleCn: '热带棱镜爆炸', shortName: 'TR' },
{ name: 'tropical-splash', title: 'tropical splash', titleCn: '热带飞溅', shortName: 'TR' },
{ name: 'tropical-splash-(solid)', title: 'tropical splash (solid)', titleCn: '热带飞溅(固体)', shortName: 'TR' },
{ name: 'tropical-splash-(solid)', title: 'tropical splash (solid)', titleCn: '热带飞溅(固体)', shortName: 'TR' },
{ name: 'tropical-storm-ice', title: 'tropical storm ice', titleCn: '热带风暴冰', shortName: 'TR' },
{ name: 'tropical-summer', title: 'tropical summer', titleCn: '热带夏日', shortName: 'TR' },
{ name: 'tropika', title: 'tropika', titleCn: '热带果', shortName: 'TR' },
@ -728,7 +728,7 @@ const flavorsData = [
{ name: 'watermelon-cantaloupe-honeydew-ice', title: 'watermelon cantaloupe honeydew ice', titleCn: '西瓜香瓜蜜瓜冰', shortName: 'WA' },
{ name: 'watermelon-g', title: 'watermelon g', titleCn: '西瓜 G', shortName: 'WA' },
{ name: 'watermelon-ice', title: 'watermelon ice', titleCn: '西瓜冰', shortName: 'WA' },
{ name: 'watermelon-ice-(solid)', title: 'watermelon ice (solid)', titleCn: '西瓜冰(固体)', shortName: 'WA' },
{ name: 'watermelon-ice-(solid)', title: 'watermelon ice (solid)', titleCn: '西瓜冰(固体)', shortName: 'WA' },
{ name: 'watermelon-lime-ice', title: 'watermelon lime ice', titleCn: '西瓜青柠冰', shortName: 'WA' },
{ name: 'watermelon-mango-tango', title: 'watermelon mango tango', titleCn: '西瓜芒果探戈', shortName: 'WA' },
{ name: 'watermelona-cg', title: 'watermelona cg', titleCn: '西瓜 CG', shortName: 'WA' },
@ -750,7 +750,7 @@ const flavorsData = [
{ name: 'wild-strawberry-watermelon', title: 'wild strawberry watermelon', titleCn: '野生草莓西瓜', shortName: 'WI' },
{ name: 'wild-white-grape', title: 'wild white grape', titleCn: '野生白葡萄', shortName: 'WI' },
{ name: 'wild-white-grape-ice', title: 'wild white grape ice', titleCn: '野生白葡萄冰', shortName: 'WI' },
{ name: 'wild-white-grape-iced', title: 'wild white grape iced', titleCn: '野生白葡萄冰(冷饮)', shortName: 'WI' },
{ name: 'wild-white-grape-iced', title: 'wild white grape iced', titleCn: '野生白葡萄冰(冷饮)', shortName: 'WI' },
{ name: 'winter-berry-ice', title: 'winter berry ice', titleCn: '冬季浆果冰', shortName: 'WI' },
{ name: 'winter-green', title: 'winter green', titleCn: '冬青', shortName: 'WI' },
{ name: 'wintergreen', title: 'wintergreen', titleCn: '冬青薄荷', shortName: 'WI' },

View File

@ -56,7 +56,7 @@ export class UnifiedSearchParamsDTO<Where=Record<string, any>> {
*
*/
export interface BatchErrorItem {
// 错误项标识可以是ID、邮箱等
// 错误项标识(可以是ID、邮箱等)
identifier: string;
// 错误信息
error: string;
@ -76,7 +76,7 @@ export interface BatchOperationResult {
updated?: number;
// 删除数量
deleted?: number;
// 跳过的数量(如数据已存在或无需处理)
// 跳过的数量(如数据已存在或无需处理)
skipped?: number;
// 错误列表
errors: BatchErrorItem[];
@ -101,7 +101,7 @@ export class SyncOperationResult implements BatchOperationResult {
* DTO
*/
export class BatchErrorItemDTO {
@ApiProperty({ description: '错误项标识如ID、邮箱等', type: String })
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
@Rule(RuleType.string().required())
identifier: string;
@ -164,7 +164,7 @@ export class SyncParamsDTO {
@Rule(RuleType.string().optional())
endDate?: string;
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
@Rule(RuleType.boolean().optional())
force?: boolean = false;
}
@ -183,7 +183,7 @@ export class BatchQueryDTO {
}
/**
*
* ()
*/
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
@ApiProperty({ description: '操作成功的数据列表', type: Array })
@ -191,7 +191,7 @@ export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
}
/**
*
* ()
*/
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
@ApiProperty({ description: '同步成功的数据列表', type: Array })

View File

@ -5,7 +5,7 @@ import { Rule, RuleType } from '@midwayjs/validate';
*
*/
export interface BatchErrorItem {
// 错误项标识可以是ID、邮箱等
// 错误项标识(可以是ID、邮箱等)
identifier: string;
// 错误信息
error: string;
@ -25,7 +25,7 @@ export interface BatchOperationResult {
updated?: number;
// 删除数量
deleted?: number;
// 跳过的数量(如数据已存在或无需处理)
// 跳过的数量(如数据已存在或无需处理)
skipped?: number;
// 错误列表
errors: BatchErrorItem[];
@ -43,7 +43,7 @@ export interface SyncOperationResult extends BatchOperationResult {
* DTO
*/
export class BatchErrorItemDTO {
@ApiProperty({ description: '错误项标识如ID、邮箱等', type: String })
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
@Rule(RuleType.string().required())
identifier: string;
@ -114,7 +114,7 @@ export class BatchDeleteDTO {
}
/**
* DTO
* DTO()
*/
export class BatchOperationDTO<T = any> {
@ApiProperty({ description: '要创建的数据列表', type: Array, required: false })
@ -175,7 +175,7 @@ export class SyncParamsDTO {
@Rule(RuleType.string().optional())
endDate?: string;
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
@Rule(RuleType.boolean().optional())
force?: boolean = false;
}
@ -194,7 +194,7 @@ export class BatchQueryDTO {
}
/**
*
* ()
*/
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
@ApiProperty({ description: '操作成功的数据列表', type: Array })
@ -202,7 +202,7 @@ export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
}
/**
*
* ()
*/
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
@ApiProperty({ description: '同步成功的数据列表', type: Array })

View File

@ -2,7 +2,7 @@ import { ApiProperty } from '@midwayjs/swagger';
import { UnifiedSearchParamsDTO } from './api.dto';
import { Customer } from '../entity/customer.entity';
// 客户基本信息DTO(用于响应)
// 客户基本信息DTO(用于响应)
export class CustomerDTO extends Customer{
@ApiProperty({ description: '客户ID' })
id: number;
@ -163,11 +163,11 @@ export class UpdateCustomerDTO {
tags?: string[];
}
// 查询单个客户响应DTO(继承基本信息)
// 查询单个客户响应DTO(继承基本信息)
export class GetCustomerDTO extends CustomerDTO {
// 可以添加额外的详细信息字段
}
// 客户统计信息DTO(包含订单统计)
// 客户统计信息DTO(包含订单统计)
export class CustomerStatisticDTO extends CustomerDTO {
@ApiProperty({ description: '创建日期' })
date_created: Date;
@ -209,7 +209,7 @@ export class CustomerStatisticWhereDTO {
customerId?: number;
}
// 客户统计查询参数DTO(继承通用查询参数)
// 客户统计查询参数DTO(继承通用查询参数)
export type CustomerStatisticQueryParamsDTO = UnifiedSearchParamsDTO<CustomerStatisticWhereDTO>;
// 客户统计列表响应DTO
@ -259,7 +259,7 @@ export class BatchDeleteCustomerDTO {
// ====================== 查询操作 ======================
// 客户查询条件DTO用于UnifiedSearchParamsDTO的where参数
// 客户查询条件DTO(用于UnifiedSearchParamsDTO的where参数)
export class CustomerWhereDTO {
@ApiProperty({ description: '邮箱筛选', required: false })
email?: string;
@ -284,10 +284,10 @@ export class CustomerWhereDTO {
role?: string;
}
// 客户查询参数DTO(继承通用查询参数)
// 客户查询参数DTO(继承通用查询参数)
export type CustomerQueryParamsDTO = UnifiedSearchParamsDTO<CustomerWhereDTO>;
// 客户列表响应DTO参考site-api.dto.ts中的分页格式
// 客户列表响应DTO(参考site-api.dto.ts中的分页格式)
export class CustomerListResponseDTO {
@ApiProperty({ description: '客户列表', type: [CustomerDTO] })
items: CustomerDTO[];
@ -359,6 +359,6 @@ export class SyncCustomersDTO {
@ApiProperty({ description: '站点ID' })
siteId: number;
@ApiProperty({ description: '查询参数支持where和orderBy', type: UnifiedSearchParamsDTO, required: false })
@ApiProperty({ description: '查询参数(支持where和orderBy)', type: UnifiedSearchParamsDTO, required: false })
params?: UnifiedSearchParamsDTO<CustomerWhereDTO>;
}

View File

@ -22,7 +22,7 @@ export class ShopyyAllProductQuery {
id?: string;
/** 商品标题,支持模糊查询 */
title?: string;
/** 商品状态,例如:上架、下架、删除等(具体值参考 Shopyy 接口文档) */
/** 商品状态,例如:上架、下架、删除等(具体值参考 Shopyy 接口文档) */
status?: string;
/** 商品SKU编码库存保有单位精确或模糊匹配 */
sku?: string;
@ -34,21 +34,21 @@ export class ShopyyAllProductQuery {
variant_price_min?: string;
/** 变体价格最大值,筛选变体价格小于等于该值的商品 */
variant_price_max?: string;
/** 变体划线价(原价)最小值,筛选变体划线价大于等于该值的商品 */
/** 变体划线价(原价)最小值,筛选变体划线价大于等于该值的商品 */
variant_compare_at_price_min?: string;
/** 变体划线价(原价)最大值,筛选变体划线价小于等于该值的商品 */
/** 变体划线价(原价)最大值,筛选变体划线价小于等于该值的商品 */
variant_compare_at_price_max?: string;
/** 变体重量最小值,筛选变体重量大于等于该值的商品(单位参考接口文档) */
/** 变体重量最小值,筛选变体重量大于等于该值的商品(单位参考接口文档) */
variant_weight_min?: string;
/** 变体重量最大值,筛选变体重量小于等于该值的商品(单位参考接口文档) */
/** 变体重量最大值,筛选变体重量小于等于该值的商品(单位参考接口文档) */
variant_weight_max?: string;
/** 商品创建时间最小值,格式参考接口文档YYYY-MM-DD HH:mm:ss */
/** 商品创建时间最小值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
created_at_min?: string;
/** 商品创建时间最大值,格式参考接口文档YYYY-MM-DD HH:mm:ss */
/** 商品创建时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
created_at_max?: string;
/** 商品更新时间最小值,格式参考接口文档YYYY-MM-DD HH:mm:ss */
/** 商品更新时间最小值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
updated_at_min?: string;
/** 商品更新时间最大值,格式参考接口文档YYYY-MM-DD HH:mm:ss */
/** 商品更新时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
updated_at_max?: string;
}
// 产品类型
@ -133,9 +133,9 @@ export interface ShopyyVariant {
//
// 订单查询参数类型
export interface ShopyyOrderQuery {
// 订单ID集合 多个ID用','联接 例1,2,3
// 订单ID集合 多个ID用','联接 例:1,2,3
ids?: string;
// 订单状态 100 未完成110 待处理180 已完成(确认收货); 190 取消;
// 订单状态 100 未完成110 待处理180 已完成(确认收货); 190 取消;
status?: string;
// 物流状态 300 未发货310 部分发货320 已发货330(确认收货)
fulfillment_status?: string;
@ -159,9 +159,9 @@ export interface ShopyyOrderQuery {
page?: string;
// 每页条数
limit?: string;
// 排序字段默认id id=订单ID updated_at=最后更新时间 pay_at=支付时间
// 排序字段(默认id) id=订单ID updated_at=最后更新时间 pay_at=支付时间
order_field?: string;
// 排序方式默认desc desc=降序 asc=升序
// 排序方式(默认desc) desc=降序 asc=升序
order_by?: string;
// 订单列表类型
group?: string;
@ -513,7 +513,7 @@ export class ShopyyFulfillmentDTO {
"tracking_number": string;
"courier_code": number;
"note": string;
"mode": "replace" | 'cover' | null// 模式 replace(替换) cover (覆盖) 空(新增)
"mode": "replace" | 'cover' | null// 模式 replace(替换) cover (覆盖) 空(新增)
}
// https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
export class ShopyPartFulfillmentDTO {

View File

@ -141,7 +141,7 @@ export class UnifiedProductAttributeDTO {
@ApiProperty({ description: '属性选项', type: [String] })
options: string[];
@ApiProperty({ description: '变体属性值(单个值)', required: false })
@ApiProperty({ description: '变体属性值(单个值)', required: false })
option?: string;
// 这个是属性的父级字典项
dict?: Dict;

View File

@ -152,7 +152,7 @@ export class QuerySiteDTO {
@Rule(RuleType.boolean().optional())
isDisabled?: boolean;
@ApiProperty({ description: '站点ID列表(逗号分隔)', required: false })
@ApiProperty({ description: '站点ID列表(逗号分隔)', required: false })
@Rule(RuleType.string().optional())
ids?: string;
}

View File

@ -8,11 +8,11 @@ export interface WooProduct {
id: number;
// 创建时间
date_created: string;
// 创建时间GMT
// 创建时间(GMT)
date_created_gmt: string;
// 更新时间
date_modified: string;
// 更新时间GMT
// 更新时间(GMT)
date_modified_gmt: string;
// 产品类型 simple grouped external variable
type: string;
@ -20,7 +20,7 @@ export interface WooProduct {
status: string;
// 是否为特色产品
featured: boolean;
// 目录可见性选项visible, catalog, search and hidden. Default is visible.
// 目录可见性选项:visible, catalog, search and hidden. Default is visible.
catalog_visibility: string;
// 常规价格
@ -130,11 +130,11 @@ export interface WooVariation {
id: number;
// 创建时间
date_created: string;
// 创建时间GMT
// 创建时间(GMT)
date_created_gmt: string;
// 更新时间
date_modified: string;
// 更新时间GMT
// 更新时间(GMT)
date_modified_gmt: string;
// 变体描述
description: string;
@ -150,11 +150,11 @@ export interface WooVariation {
price_html?: string;
// 促销开始日期
date_on_sale_from?: string;
// 促销开始日期GMT
// 促销开始日期(GMT)
date_on_sale_from_gmt?: string;
// 促销结束日期
date_on_sale_to?: string;
// 促销结束日期GMT
// 促销结束日期(GMT)
date_on_sale_to_gmt?: string;
// 是否在促销中
on_sale: boolean;

View File

@ -42,7 +42,7 @@ export enum OrderStatus {
REFUNDED = 'refunded', // 已退款
FAILED = 'failed', // 失败订单
DRAFT = 'draft', // 草稿
AUTO_DRAFT = 'auto-draft', // 自动草稿TODO:不知道为什么出现)
AUTO_DRAFT = 'auto-draft', // 自动草稿(TODO:不知道为什么出现)
// TRASH = 'trash',
// refund 也就是退款相关的状态

View File

@ -53,7 +53,7 @@ export interface IPlatformService {
getOrder(siteId: number, orderId: string): Promise<any>;
/**
*
* ()
* @param siteId ID
* @returns
*/

View File

@ -70,6 +70,7 @@ export interface ISiteAdapter {
/**
*
*/
batchProcessCustomers?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
// ========== 媒体映射方法 ==========
@ -102,93 +103,66 @@ export interface ISiteAdapter {
*/
createMedia(file: any): Promise<UnifiedMediaDTO>;
// ========== 订单映射方法 ==========
// ========== 订单映射方法 ==========
/**
*
* @param data
* @returns
*
* @param data
* @returns
*/
mapPlatformToUnifiedOrder(data: any): UnifiedOrderDTO;
mapPlatformToUnifiedOrder(data: any): UnifiedOrderDTO;
/**
*
* @param data
* @returns
*
* @param data
* @returns
*/
mapUnifiedToPlatformOrder(data: Partial<UnifiedOrderDTO>): any;
mapUnifiedToPlatformOrder(data: Partial<UnifiedOrderDTO>): any;
/**
*
* @param data
* @returns
*
* @param data
* @returns
*/
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): any;
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): any;
/**
*
* @param data
* @returns
*
* @param data
* @returns
*/
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): any;
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): any;
/**
*
*
*/
getOrder(where: Partial<Pick<UnifiedOrderDTO, 'id'>>): Promise<UnifiedOrderDTO>;
/**
*
*
*/
getOrders(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>>;
getOrders(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>>;
/**
*
*
*/
getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]>;
getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]>;
/**
*
*
*/
countOrders(params: Record<string, any>): Promise<number>;
/**
*
*
*/
createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO>;
createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO>;
/**
*
*
*/
updateOrder(where: Partial<Pick<UnifiedOrderDTO, 'id'>>, data: Partial<UnifiedOrderDTO>): Promise<boolean>;
/**
*
*
*/
deleteOrder(where: Partial<Pick<UnifiedOrderDTO, 'id'>>): Promise<boolean>;

View File

@ -66,7 +66,7 @@ export class CustomerService {
}
if (typeof dateValue === 'number') {
// 处理Unix时间戳(秒或毫秒)
// 处理Unix时间戳(秒或毫秒)
return new Date(dateValue > 9999999999 ? dateValue : dateValue * 1000);
}
@ -95,7 +95,7 @@ export class CustomerService {
}
/**
* upsert
* (upsert)
*
*/
async upsertCustomer(
@ -157,24 +157,24 @@ export class CustomerService {
/**
*
* adapter获取站点客户数据
* upsertManyCustomers保存这些客户
* 第一步:调用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客户数据
// 第三步:批量upsert客户数据
const upsertResult = await this.upsertManyCustomers(customersData);
return {
total: siteCustomers.length,
@ -192,7 +192,7 @@ export class CustomerService {
}
/**
*
* ()
*
* 使SQL查询实现复杂的统计逻辑
*/
@ -363,7 +363,7 @@ export class CustomerService {
}
/**
*
* ()
*
* 使TypeORM查询构建器实现
*/

View File

@ -239,7 +239,7 @@ export class DictService {
}
// 更新或创建字典项 (Upsert)
// 如果字典项已存在(根据 name 和 dictId 判断),则更新;否则创建新的
// 如果字典项已存在(根据 name 和 dictId 判断),则更新;否则创建新的
async upsertDictItem(dictId: number, itemData: {
name: string;
title: string;
@ -252,7 +252,7 @@ export class DictService {
// 格式化 name
const formattedName = this.formatName(itemData.name);
// 查找是否已存在该字典项(根据 name 和 dictId
// 查找是否已存在该字典项(根据 name 和 dictId)
const existingItem = await this.dictItemModel.findOne({
where: {
name: formattedName,

View File

@ -479,7 +479,7 @@ export class OrderService {
// 如果不能更新 ERP 状态,则保留原有的 orderStatus
entity.orderStatus = existingOrder.orderStatus;
}
// 更新订单数据(包括 shipping、billing 等字段)
// 更新订单数据(包括 shipping、billing 等字段)
await this.orderModel.update(existingOrder.id, entity);
entity.id = existingOrder.id;
return entity;
@ -2567,8 +2567,8 @@ export class OrderService {
* CSV格式
* @param {any[]} data
* @param {Object} options
* @param {string} [options.type='string'] 'string' | 'buffer'
* @param {string} [options.fileName] 使
* @param {string} [options.type='string'] :'string' | 'buffer'
* @param {string} [options.fileName] (使)
* @param {boolean} [options.writeFile=false]
* @returns {string|Buffer} type返回字符串或Buffer
*/
@ -2617,7 +2617,7 @@ async exportToCsv(data: any[], options: { type?: 'string' | 'buffer'; fileName?:
// 获取当前用户目录
const userHomeDir = os.homedir();
// 构建目标路径(下载目录)
// 构建目标路径(下载目录)
const downloadsDir = path.join(userHomeDir, 'Downloads');
// 确保下载目录存在

View File

@ -235,7 +235,7 @@ export class ProductService {
.leftJoinAndSelect('product.attributes', 'attribute')
.leftJoinAndSelect('attribute.dict', 'dict')
.leftJoinAndSelect('product.category', 'category');
// 处理分页参数(支持新旧两种格式)
// 处理分页参数(支持新旧两种格式)
const page = query.page || 1;
const pageSize = query.per_page || 10;
@ -393,7 +393,7 @@ export class ProductService {
qb.andWhere('product.updatedAt <= :whereUpdatedAtEnd', { whereUpdatedAtEnd: new Date(query.where.updatedAtEnd) });
}
// 品牌过滤(向后兼容)
// 品牌过滤(向后兼容)
if (brandId) {
qb.andWhere(qb => {
const subQuery = qb
@ -423,7 +423,7 @@ export class ProductService {
});
}
// 分类过滤(向后兼容)
// 分类过滤(向后兼容)
if (categoryId) {
qb.andWhere('product.categoryId = :categoryId', { categoryId });
}
@ -443,7 +443,7 @@ export class ProductService {
qb.andWhere('product.categoryId IN (:...whereCategoryIds)', { whereCategoryIds: query.where.categoryIds });
}
// 处理排序(支持新旧两种格式)
// 处理排序(支持新旧两种格式)
if (orderBy) {
if (typeof orderBy === 'string') {
// 如果orderBy是字符串尝试解析JSON
@ -1765,7 +1765,7 @@ export class ProductService {
}
// 根据ID获取产品详情包含站点SKU
// 根据ID获取产品详情(包含站点SKU)
async getProductById(id: number): Promise<Product> {
const product = await this.productModel.findOne({
where: { id },

View File

@ -128,7 +128,7 @@ export class ShopyyService {
* @returns URL
*/
private buildURL(baseUrl: string, endpoint: string): string {
// ShopYY API URL格式https://{shop}.shopyy.com/openapi/{version}/{endpoint}
// ShopYY API URL格式:https://{shop}.shopyy.com/openapi/{version}/{endpoint}
const base = baseUrl.replace(/\/$/, '');
const end = endpoint.replace(/^\//, '');
return `${base}/${end}`;

View File

@ -152,7 +152,7 @@ export class SiteApiService {
const result = await this.upsertProduct(siteId, product);
// 判断是创建还是更新
if (result && result.id) {
// 简单判断如果产品原本没有ID而现在有了说明是创建的
// 简单判断:如果产品原本没有ID而现在有了说明是创建的
if (!product.id || !product.id.toString().trim()) {
results.created.push(result);
} else {

View File

@ -254,7 +254,7 @@ export class WPService implements IPlatformService {
}
// 导出 WooCommerce 产品为特殊CSV(平台特性)
// 导出 WooCommerce 产品为特殊CSV(平台特性)
async exportProductsCsvSpecial(site: any, page: number = 1, pageSize: number = 100): Promise<string> {
const list = await this.getProducts(site, { page, per_page: pageSize });
const header = ['id','name','type','status','sku','regular_price','sale_price','stock_status','stock_quantity'];

View File

@ -1,105 +0,0 @@
# 产品站点SKU查询方法测试文档
## 新增和更新的API接口
### 1. 根据产品ID获取站点SKU列表
**接口**: `GET /product/:id/site-skus`
**功能**: 获取指定产品的所有站点SKU列表
**返回**: 站点SKU对象数组按创建时间升序排列
### 2. 根据站点SKU查询产品
**接口**: `GET /product/site-sku/:siteSku`
**功能**: 根据站点SKU代码查询对应的产品信息
**返回**: 完整的产品对象包含站点SKU、分类、属性等关联数据
### 3. 根据产品ID获取产品详情
**接口**: `GET /product/:id`
**功能**: 获取产品的完整详情信息
**返回**: 完整的产品对象包含站点SKU、分类、属性、组成等关联数据
### 4. 现有接口的增强
#### 4.1 根据SKU查询产品
**接口**: `GET /product/sku/:sku`
**增强**: 现在返回的产品信息包含关联的站点SKU数据
#### 4.2 搜索产品
**接口**: `GET /product/search?name=:name`
**增强**: 搜索结果现在包含每个产品的站点SKU数据
#### 4.3 获取产品列表
**接口**: `GET /product/list`
**增强**: 产品列表中的每个产品现在都包含站点SKU数据
## 服务层新增方法
### ProductService新增方法
1. **getProductSiteSkus(productId: number)**: Promise<ProductSiteSku[]>
- 获取指定产品的所有站点SKU
- 包含产品关联信息
- 按创建时间排序
2. **getProductById(id: number)**: Promise<Product>
- 根据产品ID获取完整产品信息
- 包含站点SKU、分类、属性、组成等所有关联数据
- 自动处理单品和混装商品的组成信息
3. **findProductBySiteSku(siteSku: string)**: Promise<Product>
- 根据站点SKU查询对应的产品
- 返回完整的产品信息
- 如果站点SKU不存在则抛出错误
### 现有方法增强
1. **findProductsByName(name: string)**: 现在包含站点SKU数据
2. **findProductBySku(sku: string)**: 现在包含站点SKU数据
3. **getProductList**: 已经包含站点SKU数据无需更改
## 使用示例
### 获取产品的站点SKU列表
```javascript
// GET /product/123/site-skus
// 返回:
[
{
"id": 1,
"siteSku": "SITE-SKU-001",
"productId": 123,
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}
]
```
### 根据站点SKU查询产品
```javascript
// GET /product/site-sku/SITE-SKU-001
// 返回完整的产品对象,包含:
// - 基本信息SKU、名称、价格等
// - 分类信息
// - 属性信息
// - 站点SKU列表
// - 组成信息
```
### 获取产品详情
```javascript
// GET /product/123
// 返回完整的产品对象与站点SKU查询类似
```
## 数据库查询优化
所有新增和更新的方法都使用了TypeORM的关联查询确保
- 一次查询获取所有需要的数据
- 避免N+1查询问题
- 包含必要的关联关系分类、属性、站点SKU、组成
## 错误处理
所有方法都包含适当的错误处理:
- 产品不存在时抛出明确的错误信息
- 站点SKU不存在时抛出明确的错误信息
- 控制器层统一处理错误并返回适当的HTTP响应