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 方法 新增多个数据映射方法以支持统一数据格式转换 refactor(api): 统一接口参数为对象形式并支持多条件查询 重构所有接口方法,将直接传递id参数改为接受where条件对象 支持通过id、sku、email等多条件查询实体 优化产品服务逻辑,支持通过sku直接查询产品 统一各适配器实现,确保接口一致性 feat: 增强产品同步功能并优化SKU生成逻辑 添加字典排序字段支持 优化产品同步流程,支持通过SKU同步 重构SKU模板生成逻辑,支持分类属性排序 完善产品导入导出功能,增加分类字段处理 统一产品操作方法,提升代码可维护性 fix(sync_shipment): 捕获运单状态更新时的异常并记录日志 添加try-catch块来捕获updateShipmentState过程中可能出现的错误 使用logger记录错误信息以便后续排查 feat(shopyy): 实现全量商品查询功能并优化产品相关逻辑 - 新增ShopyyAllProductQuery类支持全量商品查询参数 - 实现getAllProducts方法支持带条件查询 - 优化getProductBySku方法使用新查询接口 - 公开request方法便于子类调用 - 增加错误日志记录产品查找失败情况 - 修复产品permalink生成逻辑 refactor(adapter): 清理重复代码并统一订单映射方法命名 移除shopyy和woocommerce适配器中的重复代码 将mapOrder重命名为mapPlatformToUnifiedOrder以保持命名一致性 feat(订单): 添加获取订单总数功能 实现订单总数统计接口,包括: 1. 在ISiteAdapter接口添加countOrders方法 2. 在WooCommerce和Shopyy适配器中实现该方法 3. 添加控制器端点暴露该功能 4. 优化订单查询参数映射逻辑 refactor(Shopyy): 重构搜索参数映射逻辑 将通用的搜索参数映射逻辑提取为独立方法,提高代码复用性 refactor(interface): 重构站点适配器接口,按功能模块组织方法 重构 ISiteAdapter 接口,将相关方法按功能模块(客户、媒体、订单、产品等)分组 移除废弃的 fulfillOrder 方法 新增多个数据映射方法以支持统一数据格式转换 feat(订单): 添加获取订单总数功能 实现订单总数统计接口,包括: 1. 在ISiteAdapter接口添加countOrders方法 2. 在WooCommerce和Shopyy适配器中实现该方法 3. 添加控制器端点暴露该功能 4. 优化订单查询参数映射逻辑 refactor(Shopyy): 重构搜索参数映射逻辑 将通用的搜索参数映射逻辑提取为独立方法,提高代码复用性 refactor(interface): 重构站点适配器接口,按功能模块组织方法 重构 ISiteAdapter 接口,将相关方法按功能模块(客户、媒体、订单、产品等)分组 移除废弃的 fulfillOrder 方法 新增多个数据映射方法以支持统一数据格式转换 docs: 统一中文括号格式为全角括号 将代码中的中文括号格式从半角"()"统一修改为全角"()",并删除测试文档文件test-site-sku-methods.md chore: config.local 还原 docs(dto): 修正注释中的中文括号格式 docs(dto): 修正注释中的括号格式 docs: 修正中文标点符号和注释格式 统一将中文注释和文档中的全角括号和冒号改为半角格式 修正部分TODO注释的标点符号 统一接口文档中的描述符号格式 refactor(shopyy): 移除重复的订单相关方法
This commit is contained in:
parent
983ba47dbf
commit
883a3d02af
|
|
@ -523,23 +523,6 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@faker-js/faker": {
|
|
||||||
"version": "10.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.1.0.tgz",
|
|
||||||
"integrity": "sha512-C3mrr3b5dRVlKPJdfrAXS8+dq+rq8Qm5SNRazca0JKgw1HQERFmrVb0towvMmw5uu8hHKNiQasMaR/tydf3Zsg==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fakerjs"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0",
|
|
||||||
"npm": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@hapi/bourne": {
|
"node_modules/@hapi/bourne": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@hapi/bourne/-/bourne-3.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@hapi/bourne/-/bourne-3.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -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. 验证现有产品匹配是否正确
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -7,8 +7,11 @@ export default {
|
||||||
// dataSource: {
|
// dataSource: {
|
||||||
// default: {
|
// default: {
|
||||||
// host: '13.212.62.127',
|
// host: '13.212.62.127',
|
||||||
|
// port: "3306",
|
||||||
// username: 'root',
|
// username: 'root',
|
||||||
// password: 'Yoone!@.2025',
|
// password: 'Yoone!@.2025',
|
||||||
|
// database: 'inventory_v2',
|
||||||
|
// synchronize: true,
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
|
|
@ -19,7 +22,7 @@ export default {
|
||||||
port: "3306",
|
port: "3306",
|
||||||
username: 'root',
|
username: 'root',
|
||||||
password: 'root',
|
password: 'root',
|
||||||
database: 'inventory',
|
database: 'inventory'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ export class MainConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化数据库(如果不存在则创建)
|
* 初始化数据库(如果不存在则创建)
|
||||||
*/
|
*/
|
||||||
private async initializeDatabase(): Promise<void> {
|
private async initializeDatabase(): Promise<void> {
|
||||||
// 使用注入的数据库配置
|
// 使用注入的数据库配置
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
import { Controller, Get, Inject, Query, Post, Del, Param, Files, Fields, Body } from '@midwayjs/core';
|
|
||||||
import { WPService } from '../service/wp.service';
|
|
||||||
import { successResponse, errorResponse } from '../utils/response.util';
|
|
||||||
|
|
||||||
@Controller('/media')
|
|
||||||
export class MediaController {
|
|
||||||
@Inject()
|
|
||||||
wpService: WPService;
|
|
||||||
|
|
||||||
@Get('/list')
|
|
||||||
async list(
|
|
||||||
@Query('siteId') siteId: number,
|
|
||||||
@Query('page') page: number = 1,
|
|
||||||
@Query('pageSize') pageSize: number = 20
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (!siteId) {
|
|
||||||
return errorResponse('siteId is required');
|
|
||||||
}
|
|
||||||
const result = await this.wpService.getMedia(siteId, page, pageSize);
|
|
||||||
return successResponse(result);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/upload')
|
|
||||||
async upload(@Fields() fields, @Files() files) {
|
|
||||||
try {
|
|
||||||
const siteId = fields.siteId;
|
|
||||||
if (!siteId) {
|
|
||||||
return errorResponse('siteId is required');
|
|
||||||
}
|
|
||||||
if (!files || files.length === 0) {
|
|
||||||
return errorResponse('file is required');
|
|
||||||
}
|
|
||||||
const file = files[0];
|
|
||||||
const result = await this.wpService.createMedia(siteId, file);
|
|
||||||
return successResponse(result);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/update/:id')
|
|
||||||
async update(@Param('id') id: number, @Body() body) {
|
|
||||||
try {
|
|
||||||
const siteId = body.siteId;
|
|
||||||
if (!siteId) {
|
|
||||||
return errorResponse('siteId is required');
|
|
||||||
}
|
|
||||||
// 过滤出需要更新的字段
|
|
||||||
const { title, caption, description, alt_text } = body;
|
|
||||||
const data: any = {};
|
|
||||||
if (title !== undefined) data.title = title;
|
|
||||||
if (caption !== undefined) data.caption = caption;
|
|
||||||
if (description !== undefined) data.description = description;
|
|
||||||
if (alt_text !== undefined) data.alt_text = alt_text;
|
|
||||||
|
|
||||||
const result = await this.wpService.updateMedia(siteId, id, data);
|
|
||||||
return successResponse(result);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Del('/:id')
|
|
||||||
async delete(@Param('id') id: number, @Query('siteId') siteId: number, @Query('force') force: boolean = true) {
|
|
||||||
try {
|
|
||||||
if (!siteId) {
|
|
||||||
return errorResponse('siteId is required');
|
|
||||||
}
|
|
||||||
const result = await this.wpService.deleteMedia(siteId, id, force);
|
|
||||||
return successResponse(result);
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -698,10 +698,10 @@ export class ProductController {
|
||||||
// 从站点同步产品到本地
|
// 从站点同步产品到本地
|
||||||
@ApiOkResponse({ description: '从站点同步产品到本地', type: ProductRes })
|
@ApiOkResponse({ description: '从站点同步产品到本地', type: ProductRes })
|
||||||
@Post('/sync-from-site')
|
@Post('/sync-from-site')
|
||||||
async syncProductFromSite(@Body() body: { siteId: number; siteProductId: string | number }) {
|
async syncProductFromSite(@Body() body: { siteId: number; siteProductId: string | number ,sku: string}) {
|
||||||
try {
|
try {
|
||||||
const { siteId, siteProductId } = body;
|
const { siteId, siteProductId, sku } = body;
|
||||||
const product = await this.productService.syncProductFromSite(siteId, siteProductId);
|
const product = await this.productService.syncProductFromSite(siteId, siteProductId, sku);
|
||||||
return successResponse(product);
|
return successResponse(product);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -713,25 +713,26 @@ export class ProductController {
|
||||||
@Post('/batch-sync-from-site')
|
@Post('/batch-sync-from-site')
|
||||||
async batchSyncFromSite(@Body() body: { siteId: number; siteProductIds: (string | number)[] }) {
|
async batchSyncFromSite(@Body() body: { siteId: number; siteProductIds: (string | number)[] }) {
|
||||||
try {
|
try {
|
||||||
const { siteId, siteProductIds } = body;
|
throw new Error('批量同步产品到本地暂未实现');
|
||||||
const result = await this.productService.batchSyncFromSite(siteId, siteProductIds);
|
// const { siteId, siteProductIds } = body;
|
||||||
// 将服务层返回的结果转换为统一格式
|
// const result = await this.productService.batchSyncFromSite(siteId, siteProductIds.map((id) => ({ siteProductId: id, sku: '' })));
|
||||||
const errors = result.errors.map((error: string) => {
|
// // 将服务层返回的结果转换为统一格式
|
||||||
// 提取产品ID部分作为标识符
|
// const errors = result.errors.map((error: string) => {
|
||||||
const match = error.match(/站点产品ID (\d+) /);
|
// // 提取产品ID部分作为标识符
|
||||||
const identifier = match ? match[1] : 'unknown';
|
// const match = error.match(/站点产品ID (\d+) /);
|
||||||
return {
|
// const identifier = match ? match[1] : 'unknown';
|
||||||
identifier: identifier,
|
// return {
|
||||||
error: error
|
// identifier: identifier,
|
||||||
};
|
// error: error
|
||||||
});
|
// };
|
||||||
|
// });
|
||||||
|
|
||||||
return successResponse({
|
// return successResponse({
|
||||||
total: siteProductIds.length,
|
// total: siteProductIds.length,
|
||||||
processed: result.synced + errors.length,
|
// processed: result.synced + errors.length,
|
||||||
synced: result.synced,
|
// synced: result.synced,
|
||||||
errors: errors
|
// errors: errors
|
||||||
});
|
// });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import {
|
||||||
CancelFulfillmentDTO,
|
CancelFulfillmentDTO,
|
||||||
CreateReviewDTO,
|
CreateReviewDTO,
|
||||||
CreateWebhookDTO,
|
CreateWebhookDTO,
|
||||||
FulfillmentDTO,
|
|
||||||
UnifiedCustomerDTO,
|
UnifiedCustomerDTO,
|
||||||
UnifiedCustomerPaginationDTO,
|
UnifiedCustomerPaginationDTO,
|
||||||
UnifiedMediaPaginationDTO,
|
UnifiedMediaPaginationDTO,
|
||||||
|
|
@ -106,7 +105,7 @@ export class SiteApiController {
|
||||||
this.logger.debug(`[Site API] 更新评论开始, siteId: ${siteId}, id: ${id}, body: ${JSON.stringify(body)}`);
|
this.logger.debug(`[Site API] 更新评论开始, siteId: ${siteId}, id: ${id}, body: ${JSON.stringify(body)}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.updateReview(id, body);
|
const data = await adapter.updateReview({ id }, body);
|
||||||
this.logger.debug(`[Site API] 更新评论成功, siteId: ${siteId}, id: ${id}`);
|
this.logger.debug(`[Site API] 更新评论成功, siteId: ${siteId}, id: ${id}`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -124,7 +123,7 @@ export class SiteApiController {
|
||||||
this.logger.debug(`[Site API] 删除评论开始, siteId: ${siteId}, id: ${id}`);
|
this.logger.debug(`[Site API] 删除评论开始, siteId: ${siteId}, id: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.deleteReview(id);
|
const data = await adapter.deleteReview({ id });
|
||||||
this.logger.debug(`[Site API] 删除评论成功, siteId: ${siteId}, id: ${id}`);
|
this.logger.debug(`[Site API] 删除评论成功, siteId: ${siteId}, id: ${id}`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -160,7 +159,7 @@ export class SiteApiController {
|
||||||
this.logger.debug(`[Site API] 获取单个webhook开始, siteId: ${siteId}, id: ${id}`);
|
this.logger.debug(`[Site API] 获取单个webhook开始, siteId: ${siteId}, id: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getWebhook(id);
|
const data = await adapter.getWebhook({ id });
|
||||||
this.logger.debug(`[Site API] 获取单个webhook成功, siteId: ${siteId}, id: ${id}`);
|
this.logger.debug(`[Site API] 获取单个webhook成功, siteId: ${siteId}, id: ${id}`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -199,7 +198,7 @@ export class SiteApiController {
|
||||||
this.logger.debug(`[Site API] 更新webhook开始, siteId: ${siteId}, id: ${id}, body: ${JSON.stringify(body)}`);
|
this.logger.debug(`[Site API] 更新webhook开始, siteId: ${siteId}, id: ${id}, body: ${JSON.stringify(body)}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.updateWebhook(id, body);
|
const data = await adapter.updateWebhook({ id }, body);
|
||||||
this.logger.debug(`[Site API] 更新webhook成功, siteId: ${siteId}, id: ${id}`);
|
this.logger.debug(`[Site API] 更新webhook成功, siteId: ${siteId}, id: ${id}`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -217,7 +216,7 @@ export class SiteApiController {
|
||||||
this.logger.debug(`[Site API] 删除webhook开始, siteId: ${siteId}, id: ${id}`);
|
this.logger.debug(`[Site API] 删除webhook开始, siteId: ${siteId}, id: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.deleteWebhook(id);
|
const data = await adapter.deleteWebhook({ id });
|
||||||
this.logger.debug(`[Site API] 删除webhook成功, siteId: ${siteId}, id: ${id}`);
|
this.logger.debug(`[Site API] 删除webhook成功, siteId: ${siteId}, id: ${id}`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -300,7 +299,7 @@ export class SiteApiController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 平台特性:产品导出(特殊CSV,走平台服务)
|
// 平台特性:产品导出(特殊CSV,走平台服务)
|
||||||
@Get('/:siteId/links')
|
@Get('/:siteId/links')
|
||||||
async getLinks(
|
async getLinks(
|
||||||
@Param('siteId') siteId: number
|
@Param('siteId') siteId: number
|
||||||
|
|
@ -327,7 +326,7 @@ export class SiteApiController {
|
||||||
if (site.type === 'woocommerce') {
|
if (site.type === 'woocommerce') {
|
||||||
const page = query.page || 1;
|
const page = query.page || 1;
|
||||||
const perPage = (query.per_page) || 100;
|
const perPage = (query.per_page) || 100;
|
||||||
const res = await this.siteApiService.wpService.getProducts(site, page, perPage);
|
const res = await this.siteApiService.wpService.getProducts(site, { page, per_page: perPage });
|
||||||
const header = ['id', 'name', 'type', 'status', 'sku', 'regular_price', 'sale_price', 'stock_status', 'stock_quantity'];
|
const header = ['id', 'name', 'type', 'status', 'sku', 'regular_price', 'sale_price', 'stock_status', 'stock_quantity'];
|
||||||
const rows = (res.items || []).map((p: any) => [p.id, p.name, p.type, p.status, p.sku, p.regular_price, p.sale_price, p.stock_status, p.stock_quantity]);
|
const rows = (res.items || []).map((p: any) => [p.id, p.name, p.type, p.status, p.sku, p.regular_price, p.sale_price, p.stock_status, p.stock_quantity]);
|
||||||
const toCsvValue = (val: any) => {
|
const toCsvValue = (val: any) => {
|
||||||
|
|
@ -360,7 +359,7 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 获取单个产品开始, siteId: ${siteId}, productId: ${id}`);
|
this.logger.info(`[Site API] 获取单个产品开始, siteId: ${siteId}, productId: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getProduct(id);
|
const data = await adapter.getProduct({ id });
|
||||||
|
|
||||||
// 如果获取到商品数据,则增强ERP产品信息
|
// 如果获取到商品数据,则增强ERP产品信息
|
||||||
if (data) {
|
if (data) {
|
||||||
|
|
@ -430,7 +429,7 @@ export class SiteApiController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 平台特性:产品导入(特殊CSV,走平台服务)
|
// 平台特性:产品导入(特殊CSV,走平台服务)
|
||||||
@Post('/:siteId/products/import-special')
|
@Post('/:siteId/products/import-special')
|
||||||
@ApiOkResponse({ type: Object })
|
@ApiOkResponse({ type: Object })
|
||||||
async importProductsSpecial(
|
async importProductsSpecial(
|
||||||
|
|
@ -444,7 +443,7 @@ export class SiteApiController {
|
||||||
const created: any[] = [];
|
const created: any[] = [];
|
||||||
const failed: any[] = [];
|
const failed: any[] = [];
|
||||||
if (site.type === 'woocommerce') {
|
if (site.type === 'woocommerce') {
|
||||||
// 解析 CSV 为对象数组(若传入 items 则优先 items)
|
// 解析 CSV 为对象数组(若传入 items 则优先 items)
|
||||||
let payloads = items;
|
let payloads = items;
|
||||||
if (!payloads.length && csvText) {
|
if (!payloads.length && csvText) {
|
||||||
const lines = csvText.split(/\r?\n/).filter(Boolean);
|
const lines = csvText.split(/\r?\n/).filter(Boolean);
|
||||||
|
|
@ -485,7 +484,7 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 更新产品开始, siteId: ${siteId}, productId: ${id}`);
|
this.logger.info(`[Site API] 更新产品开始, siteId: ${siteId}, productId: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.updateProduct(id, body);
|
const data = await adapter.updateProduct({ id }, body);
|
||||||
this.logger.info(`[Site API] 更新产品成功, siteId: ${siteId}, productId: ${id}`);
|
this.logger.info(`[Site API] 更新产品成功, siteId: ${siteId}, productId: ${id}`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -540,7 +539,7 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 删除产品开始, siteId: ${siteId}, productId: ${id}`);
|
this.logger.info(`[Site API] 删除产品开始, siteId: ${siteId}, productId: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const success = await adapter.deleteProduct(id);
|
const success = await adapter.deleteProduct({ id });
|
||||||
this.logger.info(`[Site API] 删除产品成功, siteId: ${siteId}, productId: ${id}`);
|
this.logger.info(`[Site API] 删除产品成功, siteId: ${siteId}, productId: ${id}`);
|
||||||
return successResponse(success);
|
return successResponse(success);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -585,7 +584,7 @@ export class SiteApiController {
|
||||||
for (const item of body.update) {
|
for (const item of body.update) {
|
||||||
try {
|
try {
|
||||||
const id = item.id;
|
const id = item.id;
|
||||||
const data = await adapter.updateProduct(id, item);
|
const data = await adapter.updateProduct({ id }, item);
|
||||||
updated.push(data);
|
updated.push(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errors.push({
|
errors.push({
|
||||||
|
|
@ -598,7 +597,7 @@ export class SiteApiController {
|
||||||
if (body.delete?.length) {
|
if (body.delete?.length) {
|
||||||
for (const id of body.delete) {
|
for (const id of body.delete) {
|
||||||
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 errors.push({
|
else errors.push({
|
||||||
identifier: String(id),
|
identifier: String(id),
|
||||||
|
|
@ -672,6 +671,26 @@ export class SiteApiController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('/:siteId/orders/count')
|
||||||
|
@ApiOkResponse({ type: Object })
|
||||||
|
async countOrders(
|
||||||
|
@Param('siteId') siteId: number,
|
||||||
|
@Query() query: any
|
||||||
|
) {
|
||||||
|
this.logger.info(`[Site API] 获取订单总数开始, siteId: ${siteId}`);
|
||||||
|
try {
|
||||||
|
|
||||||
|
|
||||||
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
|
const total = await adapter.countOrders(query);
|
||||||
|
this.logger.info(`[Site API] 获取订单总数成功, siteId: ${siteId}, total: ${total}`);
|
||||||
|
return successResponse({ total });
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`[Site API] 获取订单总数失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Get('/:siteId/customers/:customerId/orders')
|
@Get('/:siteId/customers/:customerId/orders')
|
||||||
@ApiOkResponse({ type: UnifiedOrderPaginationDTO })
|
@ApiOkResponse({ type: UnifiedOrderPaginationDTO })
|
||||||
async getCustomerOrders(
|
async getCustomerOrders(
|
||||||
|
|
@ -752,7 +771,7 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 获取单个订单开始, siteId: ${siteId}, orderId: ${id}`);
|
this.logger.info(`[Site API] 获取单个订单开始, siteId: ${siteId}, orderId: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getOrder(id);
|
const data = await adapter.getOrder({ id });
|
||||||
this.logger.info(`[Site API] 获取单个订单成功, siteId: ${siteId}, orderId: ${id}`);
|
this.logger.info(`[Site API] 获取单个订单成功, siteId: ${siteId}, orderId: ${id}`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -824,7 +843,7 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 更新订单开始, siteId: ${siteId}, orderId: ${id}`);
|
this.logger.info(`[Site API] 更新订单开始, siteId: ${siteId}, orderId: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const ok = await adapter.updateOrder(id, body);
|
const ok = await adapter.updateOrder({ id }, body);
|
||||||
this.logger.info(`[Site API] 更新订单成功, siteId: ${siteId}, orderId: ${id}`);
|
this.logger.info(`[Site API] 更新订单成功, siteId: ${siteId}, orderId: ${id}`);
|
||||||
return successResponse(ok);
|
return successResponse(ok);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -842,7 +861,7 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 删除订单开始, siteId: ${siteId}, orderId: ${id}`);
|
this.logger.info(`[Site API] 删除订单开始, siteId: ${siteId}, orderId: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const ok = await adapter.deleteOrder(id);
|
const ok = await adapter.deleteOrder({ id });
|
||||||
this.logger.info(`[Site API] 删除订单成功, siteId: ${siteId}, orderId: ${id}`);
|
this.logger.info(`[Site API] 删除订单成功, siteId: ${siteId}, orderId: ${id}`);
|
||||||
return successResponse(ok);
|
return successResponse(ok);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -882,7 +901,7 @@ export class SiteApiController {
|
||||||
for (const item of body.update) {
|
for (const item of body.update) {
|
||||||
try {
|
try {
|
||||||
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 errors.push({
|
else errors.push({
|
||||||
identifier: String(item.id || 'unknown'),
|
identifier: String(item.id || 'unknown'),
|
||||||
|
|
@ -899,7 +918,7 @@ export class SiteApiController {
|
||||||
if (body.delete?.length) {
|
if (body.delete?.length) {
|
||||||
for (const id of body.delete) {
|
for (const id of body.delete) {
|
||||||
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 errors.push({
|
else errors.push({
|
||||||
identifier: String(id),
|
identifier: String(id),
|
||||||
|
|
@ -966,25 +985,6 @@ export class SiteApiController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/:siteId/orders/:id/fulfill')
|
|
||||||
@ApiOkResponse({ type: Object })
|
|
||||||
async fulfillOrder(
|
|
||||||
@Param('siteId') siteId: number,
|
|
||||||
@Param('id') id: string,
|
|
||||||
@Body() body: FulfillmentDTO
|
|
||||||
) {
|
|
||||||
this.logger.info(`[Site API] 订单履约开始, siteId: ${siteId}, orderId: ${id}`);
|
|
||||||
try {
|
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
|
||||||
const data = await adapter.fulfillOrder(id, body);
|
|
||||||
this.logger.info(`[Site API] 订单履约成功, siteId: ${siteId}, orderId: ${id}`);
|
|
||||||
return successResponse(data);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`[Site API] 订单履约失败, siteId: ${siteId}, orderId: ${id}, 错误信息: ${error.message}`);
|
|
||||||
return errorResponse(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/:siteId/orders/:id/cancel-fulfill')
|
@Post('/:siteId/orders/:id/cancel-fulfill')
|
||||||
@ApiOkResponse({ type: Object })
|
@ApiOkResponse({ type: Object })
|
||||||
async cancelFulfillment(
|
async cancelFulfillment(
|
||||||
|
|
@ -1050,13 +1050,13 @@ export class SiteApiController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/:siteId/orders/:orderId/trackings')
|
@Get('/:siteId/orders/:orderId/fulfillments')
|
||||||
@ApiOkResponse({ type: Object })
|
@ApiOkResponse({ type: Object })
|
||||||
async getOrderTrackings(
|
async getOrderFulfillments(
|
||||||
@Param('siteId') siteId: number,
|
@Param('siteId') siteId: number,
|
||||||
@Param('orderId') orderId: string
|
@Param('orderId') orderId: string
|
||||||
) {
|
) {
|
||||||
this.logger.info(`[Site API] 获取订单物流跟踪信息开始, siteId: ${siteId}, orderId: ${orderId}`);
|
this.logger.info(`[Site API] 获取订单履约信息开始, siteId: ${siteId}, orderId: ${orderId}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getOrderFulfillments(orderId);
|
const data = await adapter.getOrderFulfillments(orderId);
|
||||||
|
|
@ -1435,7 +1435,7 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 获取单个客户开始, siteId: ${siteId}, customerId: ${id}`);
|
this.logger.info(`[Site API] 获取单个客户开始, siteId: ${siteId}, customerId: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getCustomer(id);
|
const data = await adapter.getCustomer({ id });
|
||||||
this.logger.info(`[Site API] 获取单个客户成功, siteId: ${siteId}, customerId: ${id}`);
|
this.logger.info(`[Site API] 获取单个客户成功, siteId: ${siteId}, customerId: ${id}`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1507,7 +1507,7 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 更新客户开始, siteId: ${siteId}, customerId: ${id}`);
|
this.logger.info(`[Site API] 更新客户开始, siteId: ${siteId}, customerId: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.updateCustomer(id, body);
|
const data = await adapter.updateCustomer({ id }, body);
|
||||||
this.logger.info(`[Site API] 更新客户成功, siteId: ${siteId}, customerId: ${id}`);
|
this.logger.info(`[Site API] 更新客户成功, siteId: ${siteId}, customerId: ${id}`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1525,7 +1525,7 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 删除客户开始, siteId: ${siteId}, customerId: ${id}`);
|
this.logger.info(`[Site API] 删除客户开始, siteId: ${siteId}, customerId: ${id}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const success = await adapter.deleteCustomer(id);
|
const success = await adapter.deleteCustomer({ id });
|
||||||
this.logger.info(`[Site API] 删除客户成功, siteId: ${siteId}, customerId: ${id}`);
|
this.logger.info(`[Site API] 删除客户成功, siteId: ${siteId}, customerId: ${id}`);
|
||||||
return successResponse(success);
|
return successResponse(success);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1561,7 +1561,7 @@ export class SiteApiController {
|
||||||
for (const item of body.update) {
|
for (const item of body.update) {
|
||||||
try {
|
try {
|
||||||
const id = item.id;
|
const id = item.id;
|
||||||
const data = await adapter.updateCustomer(id, item);
|
const data = await adapter.updateCustomer({ id }, item);
|
||||||
updated.push(data);
|
updated.push(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
failed.push({ action: 'update', item, error: (e as any).message });
|
failed.push({ action: 'update', item, error: (e as any).message });
|
||||||
|
|
@ -1571,7 +1571,7 @@ export class SiteApiController {
|
||||||
if (body.delete?.length) {
|
if (body.delete?.length) {
|
||||||
for (const id of body.delete) {
|
for (const id of body.delete) {
|
||||||
try {
|
try {
|
||||||
const ok = await adapter.deleteCustomer(id);
|
const ok = await adapter.deleteCustomer({ id });
|
||||||
if (ok) deleted.push(id);
|
if (ok) deleted.push(id);
|
||||||
else failed.push({ action: 'delete', id, error: 'delete failed' });
|
else failed.push({ action: 'delete', id, error: 'delete failed' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export class WebhookController {
|
||||||
break;
|
break;
|
||||||
case 'order.created':
|
case 'order.created':
|
||||||
case 'order.updated':
|
case 'order.updated':
|
||||||
const order = adapter.mapOrder(body)
|
const order = adapter.mapPlatformToUnifiedOrder(body)
|
||||||
await this.orderService.syncSingleOrder(siteId, order);
|
await this.orderService.syncSingleOrder(siteId, order);
|
||||||
break;
|
break;
|
||||||
case 'order.deleted':
|
case 'order.deleted':
|
||||||
|
|
@ -162,7 +162,7 @@ export class WebhookController {
|
||||||
break;
|
break;
|
||||||
case 'orders/create':
|
case 'orders/create':
|
||||||
case 'orders/update':
|
case 'orders/update':
|
||||||
const order = adapter.mapOrder(body)
|
const order = adapter.mapPlatformToUnifiedOrder(body)
|
||||||
await this.orderService.syncSingleOrder(siteId, order);
|
await this.orderService.syncSingleOrder(siteId, order);
|
||||||
break;
|
break;
|
||||||
case 'orders/delete':
|
case 'orders/delete':
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ const flavorsData = [
|
||||||
{ name: 'arctic-mint', title: 'arctic mint', titleCn: '北极薄荷', shortName: 'AR' },
|
{ name: 'arctic-mint', title: 'arctic mint', titleCn: '北极薄荷', shortName: 'AR' },
|
||||||
{ name: 'baddie-blueberries', title: 'baddie blueberries', titleCn: '时髦蓝莓', shortName: 'BA' },
|
{ name: 'baddie-blueberries', title: 'baddie blueberries', titleCn: '时髦蓝莓', shortName: 'BA' },
|
||||||
{ name: 'banana', title: 'banana', 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', title: 'banana berry', titleCn: '香蕉莓果', shortName: 'BA' },
|
||||||
{ name: 'banana-berry-melon-ice', title: 'banana berry melon ice', 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' },
|
{ 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: '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: 'berries-in-the-6ix', title: 'berries in the 6ix', titleCn: '多伦多莓果', shortName: 'BE' },
|
||||||
{ name: 'berry-burst', title: 'berry burst', 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-ice', title: 'berry ice', titleCn: '浆果冰', shortName: 'BE' },
|
||||||
{ name: 'berry-lime-ice', title: 'berry lime 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' },
|
{ 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: 'black-cherry', title: 'black cherry', titleCn: '黑樱桃', shortName: 'BL' },
|
||||||
{ name: 'blackcherry', title: 'blackcherry', titleCn: '黑樱桃混合', shortName: 'BL' },
|
{ name: 'blackcherry', title: 'blackcherry', titleCn: '黑樱桃混合', shortName: 'BL' },
|
||||||
{ name: 'blackcurrant-ice', title: 'blackcurrant ice', 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-licorice', title: 'black licorice', titleCn: '黑甘草', shortName: 'BL' },
|
||||||
{ name: 'black-tea', title: 'black tea', titleCn: '红茶', shortName: 'BL' },
|
{ name: 'black-tea', title: 'black tea', titleCn: '红茶', shortName: 'BL' },
|
||||||
{ name: 'blackberry-ice', title: 'blackberry ice', 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', title: 'blue razz', titleCn: '蓝覆盆子', shortName: 'BL' },
|
||||||
{ name: 'blue-razz-hype', title: 'blue razz hype', 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', 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-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-lemon-ice', title: 'blue razz lemon ice', titleCn: '蓝覆盆子柠檬冰', shortName: 'BL' },
|
||||||
{ name: 'blue-razz-lemonade', title: 'blue razz lemonade', 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: 'bumpin-blackcurrant-iced', title: 'bumpin blackcurrant iced', titleCn: '黑加仑热烈冰', shortName: 'BU' },
|
||||||
{ name: 'burst-ice', title: 'burst ice', 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: 'bussin-banana-iced', title: 'bussin banana iced', titleCn: '香蕉热烈冰(重复)', shortName: 'BU' },
|
||||||
{ name: 'california-cherry', title: 'california cherry', titleCn: '加州樱桃', shortName: 'CA' },
|
{ name: 'california-cherry', title: 'california cherry', titleCn: '加州樱桃', shortName: 'CA' },
|
||||||
{ name: 'cantaloupe-mango-banana', title: 'cantaloupe mango banana', titleCn: '香瓜芒果香蕉', shortName: 'CA' },
|
{ name: 'cantaloupe-mango-banana', title: 'cantaloupe mango banana', titleCn: '香瓜芒果香蕉', shortName: 'CA' },
|
||||||
{ name: 'caramel', title: 'caramel', 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-chill', title: 'citrus chill', titleCn: '柑橘清凉', shortName: 'CI' },
|
||||||
{ name: 'citrus-smash-ice', title: 'citrus smash ice', 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', 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', title: 'classic', titleCn: '经典', shortName: 'CL' },
|
||||||
{ name: 'classic-ice', title: 'classic ice', titleCn: '经典冰', shortName: 'CL' },
|
{ name: 'classic-ice', title: 'classic ice', titleCn: '经典冰', shortName: 'CL' },
|
||||||
{ name: 'classic-mint-ice', title: 'classic mint 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: 'fizzy', title: 'fizzy', titleCn: '汽水', shortName: 'FI' },
|
||||||
{ name: 'flavourless', title: 'flavourless', titleCn: '无味', shortName: 'FL' },
|
{ name: 'flavourless', title: 'flavourless', titleCn: '无味', shortName: 'FL' },
|
||||||
{ name: 'flippin-fruit-flash', title: 'flippin fruit flash', 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: 'forest-fruits', title: 'forest fruits', titleCn: '森林水果', shortName: 'FO' },
|
||||||
{ name: 'fragrant-grapefruit', title: 'fragrant grapefruit', titleCn: '香气葡萄柚', shortName: 'FR' },
|
{ name: 'fragrant-grapefruit', title: 'fragrant grapefruit', titleCn: '香气葡萄柚', shortName: 'FR' },
|
||||||
{ name: 'freeze', title: 'freeze', 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: 'fuji-melon-ice', title: 'fuji melon ice', titleCn: '富士瓜冰', shortName: 'FU' },
|
||||||
{ name: 'full-charge', title: 'full charge', titleCn: '满电', shortName: 'FU' },
|
{ name: 'full-charge', title: 'full charge', titleCn: '满电', shortName: 'FU' },
|
||||||
{ name: 'gb', title: 'gb', titleCn: '软糖', shortName: 'GB' },
|
{ 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: 'gentle-mint', title: 'gentle mint', titleCn: '温和薄荷', shortName: 'GE' },
|
||||||
{ name: 'ghost-cola-&-vanilla', title: 'ghost cola & vanilla', titleCn: '幽灵可乐香草', shortName: 'GH' },
|
{ name: 'ghost-cola-&-vanilla', title: 'ghost cola & vanilla', titleCn: '幽灵可乐香草', shortName: 'GH' },
|
||||||
{ name: 'ghost-cola-ice', title: 'ghost cola ice', 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-mango', title: 'ghost mango', titleCn: '幽灵芒果', shortName: 'GH' },
|
||||||
{ name: 'ghost-original', title: 'ghost original', titleCn: '幽灵原味', shortName: 'GH' },
|
{ name: 'ghost-original', title: 'ghost original', titleCn: '幽灵原味', shortName: 'GH' },
|
||||||
{ name: 'ghost-watermelon-ice', title: 'ghost watermelon ice', 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: 'gold-edition', title: 'gold edition', titleCn: '金版', shortName: 'GO' },
|
||||||
{ name: 'grape', title: 'grape', titleCn: '葡萄', shortName: 'GR' },
|
{ name: 'grape', title: 'grape', titleCn: '葡萄', shortName: 'GR' },
|
||||||
{ name: 'grape-cherry', title: 'grape cherry', 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: 'mixed-fruit', title: 'mixed fruit', titleCn: '混合水果', shortName: 'MI' },
|
||||||
{ name: 'mocha-ice', title: 'mocha ice', titleCn: '摩卡冰', shortName: 'MO' },
|
{ name: 'mocha-ice', title: 'mocha ice', titleCn: '摩卡冰', shortName: 'MO' },
|
||||||
{ name: 'morocco-mint', title: 'morocco mint', 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: 'mung-beans', title: 'mung beans', titleCn: '绿豆', shortName: 'MU' },
|
||||||
{ name: 'nasty-tropic', title: 'nasty tropic', titleCn: '恶搞热带', shortName: 'NA' },
|
{ name: 'nasty-tropic', title: 'nasty tropic', titleCn: '恶搞热带', shortName: 'NA' },
|
||||||
{ name: 'nectarine-ice', title: 'nectarine ice', titleCn: '油桃冰', shortName: 'NE' },
|
{ name: 'nectarine-ice', title: 'nectarine ice', titleCn: '油桃冰', shortName: 'NE' },
|
||||||
{ name: 'night-rider', title: 'night rider', titleCn: '夜骑', shortName: 'NI' },
|
{ name: 'night-rider', title: 'night rider', titleCn: '夜骑', shortName: 'NI' },
|
||||||
{ name: 'nirvana', title: 'nirvana', 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: 'northern-blue-razz', title: 'northern blue razz', titleCn: '北方蓝覆盆子', shortName: 'NO' },
|
||||||
{ name: 'nutty-virginia', title: 'nutty virginia', titleCn: '坚果弗吉尼亚', shortName: 'NU' },
|
{ name: 'nutty-virginia', title: 'nutty virginia', titleCn: '坚果弗吉尼亚', shortName: 'NU' },
|
||||||
{ name: 'orange', title: 'orange', titleCn: '橙子', shortName: 'OR' },
|
{ 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-guava', title: 'orange mango guava', titleCn: '橙子芒果番石榴', shortName: 'OR' },
|
||||||
{ name: 'orange-mango-pineapple-ice', title: 'orange mango pineapple ice', 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', 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-spark', title: 'orange spark', titleCn: '橙色火花', shortName: 'OR' },
|
||||||
{ name: 'orange-tangerine', title: 'orange tangerine', titleCn: '橙子柑橘', shortName: 'OR' },
|
{ name: 'orange-tangerine', title: 'orange tangerine', titleCn: '橙子柑橘', shortName: 'OR' },
|
||||||
{ name: 'original', title: 'original', 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', title: 'packin peach berry', titleCn: '装满桃浆果', shortName: 'PA' },
|
||||||
{ name: 'packin-peach-berry-(popn-peach-berry)', title: 'packin peach berry (popn peach berry)', titleCn: '装满桃浆果(Pop’n 桃浆果)', shortName: 'PA' },
|
{ name: 'packin-peach-berry-(popn-peach-berry)', title: 'packin peach berry (popn peach berry)', titleCn: '装满桃浆果(Pop’n 桃浆果)', shortName: 'PA' },
|
||||||
{ name: 'papio', title: 'papio', titleCn: 'Papio', shortName: 'PA' },
|
{ name: 'papio', title: 'papio', titleCn: 'Papio', shortName: 'PA' },
|
||||||
{ name: 'paradise', title: 'paradise', titleCn: '天堂', shortName: 'PA' },
|
{ name: 'paradise', title: 'paradise', titleCn: '天堂', shortName: 'PA' },
|
||||||
{ name: 'paradise-iced', title: 'paradise iced', 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-fruits', title: 'red fruits', titleCn: '红色水果', shortName: 'RE' },
|
||||||
{ name: 'red-lightning', title: 'red lightning', titleCn: '红色闪电', shortName: 'RE' },
|
{ name: 'red-lightning', title: 'red lightning', titleCn: '红色闪电', shortName: 'RE' },
|
||||||
{ name: 'red-line', title: 'red line', 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: 'red-magic', title: 'red magic', titleCn: '红魔', shortName: 'RE' },
|
||||||
{ name: 'rich-tobacco', title: 'rich tobacco', titleCn: '浓烈烟草', shortName: 'RI' },
|
{ name: 'rich-tobacco', title: 'rich tobacco', titleCn: '浓烈烟草', shortName: 'RI' },
|
||||||
{ name: 'root-beer', title: 'root beer', titleCn: '根啤', shortName: 'RO' },
|
{ 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: 'sic-strawberry-iced', title: 'sic strawberry iced', titleCn: '意大利草莓冰', shortName: 'SI' },
|
||||||
{ name: 'simply-spearmint', title: 'simply spearmint', titleCn: '清爽留兰香', shortName: 'SI' },
|
{ name: 'simply-spearmint', title: 'simply spearmint', titleCn: '清爽留兰香', shortName: 'SI' },
|
||||||
{ name: 'skc', title: 'skc', titleCn: 'SKC', shortName: 'SK' },
|
{ name: 'skc', title: 'skc', titleCn: 'SKC', shortName: 'SK' },
|
||||||
{ name: 'skc(skittles-candy)', title: 'skc(skittles candy)', 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: '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: 'slammin-sts-iced', title: 'slammin sts iced', titleCn: '热烈 STS 冰', shortName: 'SL' },
|
||||||
{ name: 'smooth', title: 'smooth', titleCn: '顺滑', shortName: 'SM' },
|
{ name: 'smooth', title: 'smooth', titleCn: '顺滑', shortName: 'SM' },
|
||||||
{ name: 'smooth-mint', title: 'smooth mint', 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-t', title: 'strawberry jasmine t', titleCn: '草莓茉莉茶', shortName: 'ST' },
|
||||||
{ name: 'strawberry-jasmine-tea', title: 'strawberry jasmine tea', 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', 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-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-guava-ice', title: 'strawberry kiwi guava ice', titleCn: '草莓奇异果番石榴冰', shortName: 'ST' },
|
||||||
{ name: 'strawberry-kiwi-ice', title: 'strawberry kiwi 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', title: 'strawberry watermelon', titleCn: '草莓西瓜', shortName: 'ST' },
|
||||||
{ name: 'strawberry-watermelon-ice', title: 'strawberry watermelon ice', 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', 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: 'strawnana-orange', title: 'strawnana orange', titleCn: '草莓香蕉橙', shortName: 'ST' },
|
||||||
{ name: 'summer-grape', title: 'summer grape', titleCn: '夏日葡萄', shortName: 'SU' },
|
{ 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-sour-blueberry-iced', title: 'super sour blueberry iced', titleCn: '超级酸蓝莓冰', shortName: 'SU' },
|
||||||
{ name: 'super-spearmint', title: 'super spearmint', titleCn: '超级留兰香', shortName: 'SU' },
|
{ name: 'super-spearmint', title: 'super spearmint', titleCn: '超级留兰香', shortName: 'SU' },
|
||||||
{ name: 'super-spearmint-iced', title: 'super spearmint iced', 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-orang-ice', title: 'tropical orang ice', titleCn: '热带橙冰', shortName: 'TR' },
|
||||||
{ name: 'tropical-prism-blast', title: 'tropical prism blast', 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', 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-storm-ice', title: 'tropical storm ice', titleCn: '热带风暴冰', shortName: 'TR' },
|
||||||
{ name: 'tropical-summer', title: 'tropical summer', titleCn: '热带夏日', shortName: 'TR' },
|
{ name: 'tropical-summer', title: 'tropical summer', titleCn: '热带夏日', shortName: 'TR' },
|
||||||
{ name: 'tropika', title: 'tropika', 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-cantaloupe-honeydew-ice', title: 'watermelon cantaloupe honeydew ice', titleCn: '西瓜香瓜蜜瓜冰', shortName: 'WA' },
|
||||||
{ name: 'watermelon-g', title: 'watermelon g', titleCn: '西瓜 G', shortName: 'WA' },
|
{ name: 'watermelon-g', title: 'watermelon g', titleCn: '西瓜 G', shortName: 'WA' },
|
||||||
{ name: 'watermelon-ice', title: 'watermelon ice', titleCn: '西瓜冰', 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-lime-ice', title: 'watermelon lime ice', titleCn: '西瓜青柠冰', shortName: 'WA' },
|
||||||
{ name: 'watermelon-mango-tango', title: 'watermelon mango tango', titleCn: '西瓜芒果探戈', shortName: 'WA' },
|
{ name: 'watermelon-mango-tango', title: 'watermelon mango tango', titleCn: '西瓜芒果探戈', shortName: 'WA' },
|
||||||
{ name: 'watermelona-cg', title: 'watermelona cg', titleCn: '西瓜 CG', 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-strawberry-watermelon', title: 'wild strawberry watermelon', titleCn: '野生草莓西瓜', shortName: 'WI' },
|
||||||
{ name: 'wild-white-grape', title: 'wild white grape', 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-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-berry-ice', title: 'winter berry ice', titleCn: '冬季浆果冰', shortName: 'WI' },
|
||||||
{ name: 'winter-green', title: 'winter green', titleCn: '冬青', shortName: 'WI' },
|
{ name: 'winter-green', title: 'winter green', titleCn: '冬青', shortName: 'WI' },
|
||||||
{ name: 'wintergreen', title: 'wintergreen', titleCn: '冬青薄荷', shortName: 'WI' },
|
{ name: 'wintergreen', title: 'wintergreen', titleCn: '冬青薄荷', shortName: 'WI' },
|
||||||
|
|
|
||||||
|
|
@ -23,19 +23,38 @@ export default class TemplateSeeder implements Seeder {
|
||||||
const templates = [
|
const templates = [
|
||||||
{
|
{
|
||||||
name: 'product.sku',
|
name: 'product.sku',
|
||||||
value: "<%= [it.category.shortName].concat(it.attributes.map(a => a.shortName)).join('-') %>",
|
value: `<%
|
||||||
|
// 按分类判断属性排序逻辑
|
||||||
|
if (it.category.name === 'nicotine-pouches') {
|
||||||
|
// 1. 定义 nicotine-pouches 专属的属性固定顺序
|
||||||
|
const fixedOrder = ['brand','category', 'flavor', 'strength', 'humidity'];
|
||||||
|
sortedAttrShortNames = fixedOrder.map(attrKey => {
|
||||||
|
if(attrKey === 'category') return it.category.shortName
|
||||||
|
// 排序
|
||||||
|
const matchedAttr = it.attributes.find(a => a?.dict?.name === attrKey);
|
||||||
|
return matchedAttr ? matchedAttr.shortName : '';
|
||||||
|
}).filter(Boolean); // 移除空值,避免多余的 "-"
|
||||||
|
} else {
|
||||||
|
// 非目标分类,保留 attributes 原有顺序
|
||||||
|
sortedAttrShortNames = it.attributes.map(a => a.shortName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 拼接分类名 + 排序后的属性名
|
||||||
|
%><%= sortedAttrShortNames.join('-') %><%
|
||||||
|
%>`,
|
||||||
description: '产品SKU模板',
|
description: '产品SKU模板',
|
||||||
testData: JSON.stringify({
|
testData: JSON.stringify({
|
||||||
category: {
|
"category": {
|
||||||
shortName: 'CAT',
|
"name": "nicotine-pouches",
|
||||||
|
"shortName": "NP"
|
||||||
},
|
},
|
||||||
attributes: [
|
"attributes": [
|
||||||
{ shortName: 'BR' },
|
{ "dict": {"name": "brand"},"shortName": "YOONE" },
|
||||||
{ shortName: 'FL' },
|
{ "dict": {"name": "flavor"},"shortName": "FL" },
|
||||||
{ shortName: '10MG' },
|
{ "dict": {"name": "strength"},"shortName": "10MG" },
|
||||||
{ shortName: 'DRY' },
|
{ "dict": {"name": "humidity"},"shortName": "DRY" }
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'product.title',
|
name: 'product.title',
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export class UnifiedSearchParamsDTO<Where=Record<string, any>> {
|
||||||
* 批量操作错误项
|
* 批量操作错误项
|
||||||
*/
|
*/
|
||||||
export interface BatchErrorItem {
|
export interface BatchErrorItem {
|
||||||
// 错误项标识(可以是ID、邮箱等)
|
// 错误项标识(可以是ID、邮箱等)
|
||||||
identifier: string;
|
identifier: string;
|
||||||
// 错误信息
|
// 错误信息
|
||||||
error: string;
|
error: string;
|
||||||
|
|
@ -76,7 +76,7 @@ export interface BatchOperationResult {
|
||||||
updated?: number;
|
updated?: number;
|
||||||
// 删除数量
|
// 删除数量
|
||||||
deleted?: number;
|
deleted?: number;
|
||||||
// 跳过的数量(如数据已存在或无需处理)
|
// 跳过的数量(如数据已存在或无需处理)
|
||||||
skipped?: number;
|
skipped?: number;
|
||||||
// 错误列表
|
// 错误列表
|
||||||
errors: BatchErrorItem[];
|
errors: BatchErrorItem[];
|
||||||
|
|
@ -101,7 +101,7 @@ export class SyncOperationResult implements BatchOperationResult {
|
||||||
* 批量操作错误项DTO
|
* 批量操作错误项DTO
|
||||||
*/
|
*/
|
||||||
export class BatchErrorItemDTO {
|
export class BatchErrorItemDTO {
|
||||||
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
|
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
|
||||||
@Rule(RuleType.string().required())
|
@Rule(RuleType.string().required())
|
||||||
identifier: string;
|
identifier: string;
|
||||||
|
|
||||||
|
|
@ -164,7 +164,7 @@ export class SyncParamsDTO {
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
|
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
|
||||||
@Rule(RuleType.boolean().optional())
|
@Rule(RuleType.boolean().optional())
|
||||||
force?: boolean = false;
|
force?: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +183,7 @@ export class BatchQueryDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量操作结果类(泛型支持)
|
* 批量操作结果类(泛型支持)
|
||||||
*/
|
*/
|
||||||
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
||||||
@ApiProperty({ description: '操作成功的数据列表', type: Array })
|
@ApiProperty({ description: '操作成功的数据列表', type: Array })
|
||||||
|
|
@ -191,7 +191,7 @@ export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步操作结果类(泛型支持)
|
* 同步操作结果类(泛型支持)
|
||||||
*/
|
*/
|
||||||
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
|
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
|
||||||
@ApiProperty({ description: '同步成功的数据列表', type: Array })
|
@ApiProperty({ description: '同步成功的数据列表', type: Array })
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
* 批量操作错误项
|
* 批量操作错误项
|
||||||
*/
|
*/
|
||||||
export interface BatchErrorItem {
|
export interface BatchErrorItem {
|
||||||
// 错误项标识(可以是ID、邮箱等)
|
// 错误项标识(可以是ID、邮箱等)
|
||||||
identifier: string;
|
identifier: string;
|
||||||
// 错误信息
|
// 错误信息
|
||||||
error: string;
|
error: string;
|
||||||
|
|
@ -25,7 +25,7 @@ export interface BatchOperationResult {
|
||||||
updated?: number;
|
updated?: number;
|
||||||
// 删除数量
|
// 删除数量
|
||||||
deleted?: number;
|
deleted?: number;
|
||||||
// 跳过的数量(如数据已存在或无需处理)
|
// 跳过的数量(如数据已存在或无需处理)
|
||||||
skipped?: number;
|
skipped?: number;
|
||||||
// 错误列表
|
// 错误列表
|
||||||
errors: BatchErrorItem[];
|
errors: BatchErrorItem[];
|
||||||
|
|
@ -43,7 +43,7 @@ export interface SyncOperationResult extends BatchOperationResult {
|
||||||
* 批量操作错误项DTO
|
* 批量操作错误项DTO
|
||||||
*/
|
*/
|
||||||
export class BatchErrorItemDTO {
|
export class BatchErrorItemDTO {
|
||||||
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
|
@ApiProperty({ description: '错误项标识(如ID、邮箱等)', type: String })
|
||||||
@Rule(RuleType.string().required())
|
@Rule(RuleType.string().required())
|
||||||
identifier: string;
|
identifier: string;
|
||||||
|
|
||||||
|
|
@ -114,7 +114,7 @@ export class BatchDeleteDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量操作请求DTO(包含增删改)
|
* 批量操作请求DTO(包含增删改)
|
||||||
*/
|
*/
|
||||||
export class BatchOperationDTO<T = any> {
|
export class BatchOperationDTO<T = any> {
|
||||||
@ApiProperty({ description: '要创建的数据列表', type: Array, required: false })
|
@ApiProperty({ description: '要创建的数据列表', type: Array, required: false })
|
||||||
|
|
@ -175,7 +175,7 @@ export class SyncParamsDTO {
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
|
@ApiProperty({ description: '强制同步(忽略缓存)', type: Boolean, required: false, default: false })
|
||||||
@Rule(RuleType.boolean().optional())
|
@Rule(RuleType.boolean().optional())
|
||||||
force?: boolean = false;
|
force?: boolean = false;
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +194,7 @@ export class BatchQueryDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量操作结果类(泛型支持)
|
* 批量操作结果类(泛型支持)
|
||||||
*/
|
*/
|
||||||
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
||||||
@ApiProperty({ description: '操作成功的数据列表', type: Array })
|
@ApiProperty({ description: '操作成功的数据列表', type: Array })
|
||||||
|
|
@ -202,7 +202,7 @@ export class BatchOperationResultDTOGeneric<T> extends BatchOperationResultDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步操作结果类(泛型支持)
|
* 同步操作结果类(泛型支持)
|
||||||
*/
|
*/
|
||||||
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
|
export class SyncOperationResultDTOGeneric<T> extends SyncOperationResultDTO {
|
||||||
@ApiProperty({ description: '同步成功的数据列表', type: Array })
|
@ApiProperty({ description: '同步成功的数据列表', type: Array })
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { UnifiedSearchParamsDTO } from './api.dto';
|
import { UnifiedSearchParamsDTO } from './api.dto';
|
||||||
import { Customer } from '../entity/customer.entity';
|
import { Customer } from '../entity/customer.entity';
|
||||||
|
|
||||||
// 客户基本信息DTO(用于响应)
|
// 客户基本信息DTO(用于响应)
|
||||||
export class CustomerDTO extends Customer{
|
export class CustomerDTO extends Customer{
|
||||||
@ApiProperty({ description: '客户ID' })
|
@ApiProperty({ description: '客户ID' })
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -163,11 +163,11 @@ export class UpdateCustomerDTO {
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询单个客户响应DTO(继承基本信息)
|
// 查询单个客户响应DTO(继承基本信息)
|
||||||
export class GetCustomerDTO extends CustomerDTO {
|
export class GetCustomerDTO extends CustomerDTO {
|
||||||
// 可以添加额外的详细信息字段
|
// 可以添加额外的详细信息字段
|
||||||
}
|
}
|
||||||
// 客户统计信息DTO(包含订单统计)
|
// 客户统计信息DTO(包含订单统计)
|
||||||
export class CustomerStatisticDTO extends CustomerDTO {
|
export class CustomerStatisticDTO extends CustomerDTO {
|
||||||
@ApiProperty({ description: '创建日期' })
|
@ApiProperty({ description: '创建日期' })
|
||||||
date_created: Date;
|
date_created: Date;
|
||||||
|
|
@ -209,7 +209,7 @@ export class CustomerStatisticWhereDTO {
|
||||||
customerId?: number;
|
customerId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 客户统计查询参数DTO(继承通用查询参数)
|
// 客户统计查询参数DTO(继承通用查询参数)
|
||||||
export type CustomerStatisticQueryParamsDTO = UnifiedSearchParamsDTO<CustomerStatisticWhereDTO>;
|
export type CustomerStatisticQueryParamsDTO = UnifiedSearchParamsDTO<CustomerStatisticWhereDTO>;
|
||||||
|
|
||||||
// 客户统计列表响应DTO
|
// 客户统计列表响应DTO
|
||||||
|
|
@ -259,7 +259,7 @@ export class BatchDeleteCustomerDTO {
|
||||||
|
|
||||||
// ====================== 查询操作 ======================
|
// ====================== 查询操作 ======================
|
||||||
|
|
||||||
// 客户查询条件DTO(用于UnifiedSearchParamsDTO的where参数)
|
// 客户查询条件DTO(用于UnifiedSearchParamsDTO的where参数)
|
||||||
export class CustomerWhereDTO {
|
export class CustomerWhereDTO {
|
||||||
@ApiProperty({ description: '邮箱筛选', required: false })
|
@ApiProperty({ description: '邮箱筛选', required: false })
|
||||||
email?: string;
|
email?: string;
|
||||||
|
|
@ -284,10 +284,10 @@ export class CustomerWhereDTO {
|
||||||
role?: string;
|
role?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 客户查询参数DTO(继承通用查询参数)
|
// 客户查询参数DTO(继承通用查询参数)
|
||||||
export type CustomerQueryParamsDTO = UnifiedSearchParamsDTO<CustomerWhereDTO>;
|
export type CustomerQueryParamsDTO = UnifiedSearchParamsDTO<CustomerWhereDTO>;
|
||||||
|
|
||||||
// 客户列表响应DTO(参考site-api.dto.ts中的分页格式)
|
// 客户列表响应DTO(参考site-api.dto.ts中的分页格式)
|
||||||
export class CustomerListResponseDTO {
|
export class CustomerListResponseDTO {
|
||||||
@ApiProperty({ description: '客户列表', type: [CustomerDTO] })
|
@ApiProperty({ description: '客户列表', type: [CustomerDTO] })
|
||||||
items: CustomerDTO[];
|
items: CustomerDTO[];
|
||||||
|
|
@ -359,6 +359,6 @@ export class SyncCustomersDTO {
|
||||||
@ApiProperty({ description: '站点ID' })
|
@ApiProperty({ description: '站点ID' })
|
||||||
siteId: number;
|
siteId: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '查询参数(支持where和orderBy)', type: UnifiedSearchParamsDTO, required: false })
|
@ApiProperty({ description: '查询参数(支持where和orderBy)', type: UnifiedSearchParamsDTO, required: false })
|
||||||
params?: UnifiedSearchParamsDTO<CustomerWhereDTO>;
|
params?: UnifiedSearchParamsDTO<CustomerWhereDTO>;
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,53 @@ export interface ShopyyTag {
|
||||||
id?: number;
|
id?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
}
|
}
|
||||||
|
export interface ShopyyProductQuery {
|
||||||
|
page: string;
|
||||||
|
limit: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Shopyy 全量商品查询参数类
|
||||||
|
* 用于封装获取 Shopyy 商品列表时的各种筛选和分页条件
|
||||||
|
* 参考文档: https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
|
||||||
|
*/
|
||||||
|
export class ShopyyAllProductQuery {
|
||||||
|
/** 分页大小,限制返回的商品数量 */
|
||||||
|
limit?: string;
|
||||||
|
/** 起始ID,用于分页,返回ID大于该值的商品 */
|
||||||
|
since_id?: string;
|
||||||
|
/** 商品ID,精确匹配单个商品 */
|
||||||
|
id?: string;
|
||||||
|
/** 商品标题,支持模糊查询 */
|
||||||
|
title?: string;
|
||||||
|
/** 商品状态,例如:上架、下架、删除等(具体值参考 Shopyy 接口文档) */
|
||||||
|
status?: string;
|
||||||
|
/** 商品SKU编码,库存保有单位,精确或模糊匹配 */
|
||||||
|
sku?: string;
|
||||||
|
/** 商品SPU编码,标准化产品单元,用于归类同款商品 */
|
||||||
|
spu?: string;
|
||||||
|
/** 商品分类ID,筛选指定分类下的商品 */
|
||||||
|
collection_id?: string;
|
||||||
|
/** 变体价格最小值,筛选变体价格大于等于该值的商品 */
|
||||||
|
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) */
|
||||||
|
created_at_min?: string;
|
||||||
|
/** 商品创建时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||||
|
created_at_max?: string;
|
||||||
|
/** 商品更新时间最小值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||||
|
updated_at_min?: string;
|
||||||
|
/** 商品更新时间最大值,格式参考接口文档(如:YYYY-MM-DD HH:mm:ss) */
|
||||||
|
updated_at_max?: string;
|
||||||
|
}
|
||||||
// 产品类型
|
// 产品类型
|
||||||
export interface ShopyyProduct {
|
export interface ShopyyProduct {
|
||||||
// 产品主键
|
// 产品主键
|
||||||
|
|
@ -83,6 +130,42 @@ export interface ShopyyVariant {
|
||||||
position?: number | string;
|
position?: number | string;
|
||||||
sku_code?: string;
|
sku_code?: string;
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// 订单查询参数类型
|
||||||
|
export interface ShopyyOrderQuery {
|
||||||
|
// 订单ID集合 多个ID用','联接 例:1,2,3
|
||||||
|
ids?: string;
|
||||||
|
// 订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||||
|
status?: string;
|
||||||
|
// 物流状态 300 未发货;310 部分发货;320 已发货;330(确认收货)
|
||||||
|
fulfillment_status?: string;
|
||||||
|
// 支付状态 200 待支付;210 支付中;220 部分支付;230 已支付;240 支付失败;250 部分退款;260 已退款 ;290 已取消;
|
||||||
|
financial_status?: string;
|
||||||
|
// 支付时间 下限值
|
||||||
|
pay_at_min?: string;
|
||||||
|
// 支付时间 上限值
|
||||||
|
pay_at_max?: string;
|
||||||
|
// 创建开始时间
|
||||||
|
created_at_min?: number;
|
||||||
|
// 创建结束时间
|
||||||
|
created_at_max?: number;
|
||||||
|
// 更新时间开始
|
||||||
|
updated_at_min?: string;
|
||||||
|
// 更新时间结束
|
||||||
|
updated_at_max?: string;
|
||||||
|
// 起始ID
|
||||||
|
since_id?: string;
|
||||||
|
// 页码
|
||||||
|
page?: string;
|
||||||
|
// 每页条数
|
||||||
|
limit?: string;
|
||||||
|
// 排序字段(默认id) id=订单ID updated_at=最后更新时间 pay_at=支付时间
|
||||||
|
order_field?: string;
|
||||||
|
// 排序方式(默认desc) desc=降序 asc=升序
|
||||||
|
order_by?: string;
|
||||||
|
// 订单列表类型
|
||||||
|
group?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// 订单类型
|
// 订单类型
|
||||||
export interface ShopyyOrder {
|
export interface ShopyyOrder {
|
||||||
|
|
@ -219,7 +302,8 @@ export interface ShopyyOrder {
|
||||||
// 创建时间
|
// 创建时间
|
||||||
created_at?: number;
|
created_at?: number;
|
||||||
// 发货商品表 id
|
// 发货商品表 id
|
||||||
id?: number }>;
|
id?: number
|
||||||
|
}>;
|
||||||
}>;
|
}>;
|
||||||
shipping_zone_plans?: Array<{
|
shipping_zone_plans?: Array<{
|
||||||
shipping_price?: number | string;
|
shipping_price?: number | string;
|
||||||
|
|
@ -429,7 +513,7 @@ export class ShopyyFulfillmentDTO {
|
||||||
"tracking_number": string;
|
"tracking_number": string;
|
||||||
"courier_code": number;
|
"courier_code": number;
|
||||||
"note": string;
|
"note": string;
|
||||||
"mode": "replace" | 'cover' | null// 模式 replace(替换) cover (覆盖) 空(新增)
|
"mode": "replace" | 'cover' | null// 模式 replace(替换) cover (覆盖) 空(新增)
|
||||||
}
|
}
|
||||||
// https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
|
// https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
|
||||||
export class ShopyPartFulfillmentDTO {
|
export class ShopyPartFulfillmentDTO {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import {
|
import {
|
||||||
UnifiedPaginationDTO,
|
UnifiedPaginationDTO,
|
||||||
} from './api.dto';
|
} from './api.dto';
|
||||||
|
import { Dict } from '../entity/dict.entity';
|
||||||
// export class UnifiedOrderWhere{
|
// export class UnifiedOrderWhere{
|
||||||
// []
|
// []
|
||||||
// }
|
// }
|
||||||
|
|
@ -17,6 +18,11 @@ export enum OrderFulfillmentStatus {
|
||||||
// 确认发货
|
// 确认发货
|
||||||
CONFIRMED,
|
CONFIRMED,
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
export class UnifiedProductWhere {
|
||||||
|
sku?: string;
|
||||||
|
[prop:string]:any
|
||||||
|
}
|
||||||
export class UnifiedTagDTO {
|
export class UnifiedTagDTO {
|
||||||
// 标签DTO用于承载统一标签数据
|
// 标签DTO用于承载统一标签数据
|
||||||
@ApiProperty({ description: '标签ID' })
|
@ApiProperty({ description: '标签ID' })
|
||||||
|
|
@ -135,8 +141,10 @@ export class UnifiedProductAttributeDTO {
|
||||||
@ApiProperty({ description: '属性选项', type: [String] })
|
@ApiProperty({ description: '属性选项', type: [String] })
|
||||||
options: string[];
|
options: string[];
|
||||||
|
|
||||||
@ApiProperty({ description: '变体属性值(单个值)', required: false })
|
@ApiProperty({ description: '变体属性值(单个值)', required: false })
|
||||||
option?: string;
|
option?: string;
|
||||||
|
// 这个是属性的父级字典项
|
||||||
|
dict?: Dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedProductVariationDTO {
|
export class UnifiedProductVariationDTO {
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ export class QuerySiteDTO {
|
||||||
@Rule(RuleType.boolean().optional())
|
@Rule(RuleType.boolean().optional())
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
|
||||||
@ApiProperty({ description: '站点ID列表(逗号分隔)', required: false })
|
@ApiProperty({ description: '站点ID列表(逗号分隔)', required: false })
|
||||||
@Rule(RuleType.string().optional())
|
@Rule(RuleType.string().optional())
|
||||||
ids?: string;
|
ids?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ export interface WooProduct {
|
||||||
id: number;
|
id: number;
|
||||||
// 创建时间
|
// 创建时间
|
||||||
date_created: string;
|
date_created: string;
|
||||||
// 创建时间(GMT)
|
// 创建时间(GMT)
|
||||||
date_created_gmt: string;
|
date_created_gmt: string;
|
||||||
// 更新时间
|
// 更新时间
|
||||||
date_modified: string;
|
date_modified: string;
|
||||||
// 更新时间(GMT)
|
// 更新时间(GMT)
|
||||||
date_modified_gmt: string;
|
date_modified_gmt: string;
|
||||||
// 产品类型 simple grouped external variable
|
// 产品类型 simple grouped external variable
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -20,7 +20,7 @@ export interface WooProduct {
|
||||||
status: string;
|
status: string;
|
||||||
// 是否为特色产品
|
// 是否为特色产品
|
||||||
featured: boolean;
|
featured: boolean;
|
||||||
// 目录可见性选项:visible, catalog, search and hidden. Default is visible.
|
// 目录可见性选项:visible, catalog, search and hidden. Default is visible.
|
||||||
catalog_visibility: string;
|
catalog_visibility: string;
|
||||||
|
|
||||||
// 常规价格
|
// 常规价格
|
||||||
|
|
@ -117,9 +117,9 @@ export interface WooProduct {
|
||||||
// 购买备注
|
// 购买备注
|
||||||
purchase_note?: string;
|
purchase_note?: string;
|
||||||
// 分类列表
|
// 分类列表
|
||||||
categories?: Array<{ id: number; name?: string; slug?: string }>;
|
categories?: Array<{ id?: number; name?: string; slug?: string }>;
|
||||||
// 标签列表
|
// 标签列表
|
||||||
tags?: Array<{ id: number; name?: string; slug?: string }>;
|
tags?: Array<{ id?: number; name?: string; slug?: string }>;
|
||||||
// 菜单排序
|
// 菜单排序
|
||||||
menu_order?: number;
|
menu_order?: number;
|
||||||
// 元数据
|
// 元数据
|
||||||
|
|
@ -130,11 +130,11 @@ export interface WooVariation {
|
||||||
id: number;
|
id: number;
|
||||||
// 创建时间
|
// 创建时间
|
||||||
date_created: string;
|
date_created: string;
|
||||||
// 创建时间(GMT)
|
// 创建时间(GMT)
|
||||||
date_created_gmt: string;
|
date_created_gmt: string;
|
||||||
// 更新时间
|
// 更新时间
|
||||||
date_modified: string;
|
date_modified: string;
|
||||||
// 更新时间(GMT)
|
// 更新时间(GMT)
|
||||||
date_modified_gmt: string;
|
date_modified_gmt: string;
|
||||||
// 变体描述
|
// 变体描述
|
||||||
description: string;
|
description: string;
|
||||||
|
|
@ -150,11 +150,11 @@ export interface WooVariation {
|
||||||
price_html?: string;
|
price_html?: string;
|
||||||
// 促销开始日期
|
// 促销开始日期
|
||||||
date_on_sale_from?: string;
|
date_on_sale_from?: string;
|
||||||
// 促销开始日期(GMT)
|
// 促销开始日期(GMT)
|
||||||
date_on_sale_from_gmt?: string;
|
date_on_sale_from_gmt?: string;
|
||||||
// 促销结束日期
|
// 促销结束日期
|
||||||
date_on_sale_to?: string;
|
date_on_sale_to?: string;
|
||||||
// 促销结束日期(GMT)
|
// 促销结束日期(GMT)
|
||||||
date_on_sale_to_gmt?: string;
|
date_on_sale_to_gmt?: string;
|
||||||
// 是否在促销中
|
// 是否在促销中
|
||||||
on_sale: boolean;
|
on_sale: boolean;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,10 @@ export class Dict {
|
||||||
@OneToMany(() => DictItem, item => item.dict)
|
@OneToMany(() => DictItem, item => item.dict)
|
||||||
items: DictItem[];
|
items: DictItem[];
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
@Column({ default: 0, comment: '排序' })
|
||||||
|
sort: number;
|
||||||
|
|
||||||
// 是否可删除
|
// 是否可删除
|
||||||
@Column({ default: true, comment: '是否可删除' })
|
@Column({ default: true, comment: '是否可删除' })
|
||||||
deletable: boolean;
|
deletable: boolean;
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,6 @@ export class Product {
|
||||||
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||||
promotionPrice: number;
|
promotionPrice: number;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 分类关联
|
// 分类关联
|
||||||
@ManyToOne(() => Category, category => category.products)
|
@ManyToOne(() => Category, category => category.products)
|
||||||
@JoinColumn({ name: 'categoryId' })
|
@JoinColumn({ name: 'categoryId' })
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export enum OrderStatus {
|
||||||
REFUNDED = 'refunded', // 已退款
|
REFUNDED = 'refunded', // 已退款
|
||||||
FAILED = 'failed', // 失败订单
|
FAILED = 'failed', // 失败订单
|
||||||
DRAFT = 'draft', // 草稿
|
DRAFT = 'draft', // 草稿
|
||||||
AUTO_DRAFT = 'auto-draft', // 自动草稿(TODO:不知道为什么出现)
|
AUTO_DRAFT = 'auto-draft', // 自动草稿(TODO:不知道为什么出现)
|
||||||
|
|
||||||
// TRASH = 'trash',
|
// TRASH = 'trash',
|
||||||
// refund 也就是退款相关的状态
|
// refund 也就是退款相关的状态
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export interface IPlatformService {
|
||||||
getOrder(siteId: number, orderId: string): Promise<any>;
|
getOrder(siteId: number, orderId: string): Promise<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订阅列表(如果平台支持)
|
* 获取订阅列表(如果平台支持)
|
||||||
* @param siteId 站点ID
|
* @param siteId 站点ID
|
||||||
* @returns 订阅列表数据
|
* @returns 订阅列表数据
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -20,51 +20,72 @@ import { UnifiedPaginationDTO, UnifiedSearchParamsDTO } from '../dto/api.dto';
|
||||||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||||||
|
|
||||||
export interface ISiteAdapter {
|
export interface ISiteAdapter {
|
||||||
mapOrder(order: any): UnifiedOrderDTO;
|
// ========== 客户映射方法 ==========
|
||||||
mapWebhook(webhook:any):UnifiedWebhookDTO;
|
|
||||||
mapProduct(product:any): UnifiedProductDTO;
|
|
||||||
mapReview(data: any): UnifiedReviewDTO;
|
|
||||||
mapCustomer(data: any): UnifiedCustomerDTO;
|
|
||||||
mapMedia(data: any): UnifiedMediaDTO;
|
|
||||||
/**
|
/**
|
||||||
* 获取产品列表
|
* 将平台客户数据转换为统一客户数据格式
|
||||||
|
* @param data 平台特定客户数据
|
||||||
|
* @returns 统一客户数据格式
|
||||||
*/
|
*/
|
||||||
getProducts(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedProductDTO>>;
|
mapPlatformToUnifiedCustomer(data: any): UnifiedCustomerDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有产品
|
* 将统一客户数据格式转换为平台客户数据
|
||||||
|
* @param data 统一客户数据格式
|
||||||
|
* @returns 平台特定客户数据
|
||||||
*/
|
*/
|
||||||
getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]>;
|
mapUnifiedToPlatformCustomer(data: Partial<UnifiedCustomerDTO>): any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取单个产品
|
* 获取单个客户
|
||||||
|
* 获取单个客户
|
||||||
*/
|
*/
|
||||||
getProduct(id: string | number): Promise<UnifiedProductDTO>;
|
getCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>): Promise<UnifiedCustomerDTO>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订单列表
|
* 获取客户列表
|
||||||
|
* 获取客户列表
|
||||||
*/
|
*/
|
||||||
getOrders(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>>;
|
getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有订单
|
* 获取所有客户
|
||||||
*/
|
*/
|
||||||
getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]>;
|
getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取单个订单
|
* 创建客户
|
||||||
*/
|
*/
|
||||||
getOrder(id: string | number): Promise<UnifiedOrderDTO>;
|
createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订阅列表
|
* 更新客户
|
||||||
*/
|
*/
|
||||||
getSubscriptions(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>>;
|
updateCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有订阅
|
* 删除客户
|
||||||
*/
|
*/
|
||||||
getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]>;
|
deleteCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量处理客户
|
||||||
|
*/
|
||||||
|
batchProcessCustomers?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
|
||||||
|
|
||||||
|
// ========== 媒体映射方法 ==========
|
||||||
|
/**
|
||||||
|
* 将平台媒体数据转换为统一媒体数据格式
|
||||||
|
* @param data 平台特定媒体数据
|
||||||
|
* @returns 统一媒体数据格式
|
||||||
|
*/
|
||||||
|
mapPlatformToUnifiedMedia(data: any): UnifiedMediaDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一媒体数据格式转换为平台媒体数据
|
||||||
|
* @param data 统一媒体数据格式
|
||||||
|
* @returns 平台特定媒体数据
|
||||||
|
*/
|
||||||
|
mapUnifiedToPlatformMedia(data: Partial<UnifiedMediaDTO>): any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取媒体列表
|
* 获取媒体列表
|
||||||
|
|
@ -81,75 +102,96 @@ export interface ISiteAdapter {
|
||||||
*/
|
*/
|
||||||
createMedia(file: any): Promise<UnifiedMediaDTO>;
|
createMedia(file: any): Promise<UnifiedMediaDTO>;
|
||||||
|
|
||||||
|
// ========== 订单映射方法 ==========
|
||||||
|
// ========== 订单映射方法 ==========
|
||||||
/**
|
/**
|
||||||
* 获取评论列表
|
* 将平台订单数据转换为统一订单数据格式
|
||||||
|
* @param data 平台特定订单数据
|
||||||
|
* @returns 统一订单数据格式
|
||||||
|
* 将平台订单数据转换为统一订单数据格式
|
||||||
|
* @param data 平台特定订单数据
|
||||||
|
* @returns 统一订单数据格式
|
||||||
*/
|
*/
|
||||||
getReviews(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedReviewDTO>>;
|
mapPlatformToUnifiedOrder(data: any): UnifiedOrderDTO;
|
||||||
|
mapPlatformToUnifiedOrder(data: any): UnifiedOrderDTO;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有评论
|
* 将统一订单数据格式转换为平台订单数据
|
||||||
|
* @param data 统一订单数据格式
|
||||||
|
* @returns 平台特定订单数据
|
||||||
|
* 将统一订单数据格式转换为平台订单数据
|
||||||
|
* @param data 统一订单数据格式
|
||||||
|
* @returns 平台特定订单数据
|
||||||
*/
|
*/
|
||||||
getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]>;
|
mapUnifiedToPlatformOrder(data: Partial<UnifiedOrderDTO>): any;
|
||||||
|
mapUnifiedToPlatformOrder(data: Partial<UnifiedOrderDTO>): any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建评论
|
* 将统一订单创建参数转换为平台订单创建参数
|
||||||
|
* @param data 统一订单创建参数
|
||||||
|
* @returns 平台订单创建参数
|
||||||
|
* 将统一订单创建参数转换为平台订单创建参数
|
||||||
|
* @param data 统一订单创建参数
|
||||||
|
* @returns 平台订单创建参数
|
||||||
*/
|
*/
|
||||||
createReview(data: CreateReviewDTO): Promise<UnifiedReviewDTO>;
|
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): any;
|
||||||
|
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新评论
|
* 将统一订单更新参数转换为平台订单更新参数
|
||||||
|
* @param data 统一订单更新参数
|
||||||
|
* @returns 平台订单更新参数
|
||||||
|
* 将统一订单更新参数转换为平台订单更新参数
|
||||||
|
* @param data 统一订单更新参数
|
||||||
|
* @returns 平台订单更新参数
|
||||||
*/
|
*/
|
||||||
updateReview(id: number, data: UpdateReviewDTO): Promise<UnifiedReviewDTO>;
|
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): any;
|
||||||
|
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除评论
|
* 获取单个订单
|
||||||
|
* 获取单个订单
|
||||||
*/
|
*/
|
||||||
deleteReview(id: number): Promise<boolean>;
|
getOrder(where: Partial<Pick<UnifiedOrderDTO, 'id'>>): Promise<UnifiedOrderDTO>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建产品
|
* 获取订单列表
|
||||||
|
* 获取订单列表
|
||||||
*/
|
*/
|
||||||
createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO>;
|
getOrders(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>>;
|
||||||
|
getOrders(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新产品
|
* 获取所有订单
|
||||||
|
* 获取所有订单
|
||||||
*/
|
*/
|
||||||
updateProduct(id: string | number, data: Partial<UnifiedProductDTO>): Promise<boolean>;
|
getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]>;
|
||||||
|
getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除产品
|
* 获取订单总数
|
||||||
|
* 获取订单总数
|
||||||
*/
|
*/
|
||||||
deleteProduct(id: string | number): Promise<boolean>;
|
countOrders(params: Record<string, any>): Promise<number>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取产品变体列表
|
* 创建订单
|
||||||
|
* 创建订单
|
||||||
*/
|
*/
|
||||||
getVariations(productId: string | number, params: UnifiedSearchParamsDTO): Promise<UnifiedVariationPaginationDTO>;
|
createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO>;
|
||||||
|
createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有产品变体
|
* 更新订单
|
||||||
|
* 更新订单
|
||||||
*/
|
*/
|
||||||
getAllVariations(productId: string | number, params?: UnifiedSearchParamsDTO): Promise<UnifiedProductVariationDTO[]>;
|
updateOrder(where: Partial<Pick<UnifiedOrderDTO, 'id'>>, data: Partial<UnifiedOrderDTO>): Promise<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取单个产品变体
|
* 删除订单
|
||||||
|
* 删除订单
|
||||||
*/
|
*/
|
||||||
getVariation(productId: string | number, variationId: string | number): Promise<UnifiedProductVariationDTO>;
|
deleteOrder(where: Partial<Pick<UnifiedOrderDTO, 'id'>>): Promise<boolean>;
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建产品变体
|
|
||||||
*/
|
|
||||||
createVariation(productId: string | number, data: CreateVariationDTO): Promise<UnifiedProductVariationDTO>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新产品变体
|
|
||||||
*/
|
|
||||||
updateVariation(productId: string | number, variationId: string | number, data: UpdateVariationDTO): Promise<UnifiedProductVariationDTO>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除产品变体
|
|
||||||
*/
|
|
||||||
deleteVariation(productId: string | number, variationId: string | number): Promise<boolean>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取订单备注
|
* 获取订单备注
|
||||||
|
|
@ -161,71 +203,6 @@ export interface ISiteAdapter {
|
||||||
*/
|
*/
|
||||||
createOrderNote(orderId: string | number, data: any): Promise<any>;
|
createOrderNote(orderId: string | number, data: any): Promise<any>;
|
||||||
|
|
||||||
batchProcessProducts?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
|
|
||||||
|
|
||||||
createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO>;
|
|
||||||
updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean>;
|
|
||||||
deleteOrder(id: string | number): Promise<boolean>;
|
|
||||||
|
|
||||||
batchProcessOrders?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
|
|
||||||
|
|
||||||
getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>>;
|
|
||||||
getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]>;
|
|
||||||
getCustomer(id: string | number): Promise<UnifiedCustomerDTO>;
|
|
||||||
createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
|
|
||||||
updateCustomer(id: string | number, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
|
|
||||||
deleteCustomer(id: string | number): Promise<boolean>;
|
|
||||||
|
|
||||||
batchProcessCustomers?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取webhooks列表
|
|
||||||
*/
|
|
||||||
getWebhooks(params: UnifiedSearchParamsDTO): Promise<UnifiedWebhookPaginationDTO>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有webhooks
|
|
||||||
*/
|
|
||||||
getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单个webhook
|
|
||||||
*/
|
|
||||||
getWebhook(id: string | number): Promise<UnifiedWebhookDTO>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建webhook
|
|
||||||
*/
|
|
||||||
createWebhook(data: CreateWebhookDTO): Promise<UnifiedWebhookDTO>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新webhook
|
|
||||||
*/
|
|
||||||
updateWebhook(id: string | number, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除webhook
|
|
||||||
*/
|
|
||||||
deleteWebhook(id: string | number): Promise<boolean>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取站点链接列表
|
|
||||||
*/
|
|
||||||
getLinks(): Promise<Array<{title: string, url: string}>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 订单履行(发货)
|
|
||||||
*/
|
|
||||||
fulfillOrder(orderId: string | number, data: {
|
|
||||||
tracking_number?: string;
|
|
||||||
shipping_provider?: string;
|
|
||||||
shipping_method?: string;
|
|
||||||
items?: Array<{
|
|
||||||
order_item_id: number;
|
|
||||||
quantity: number;
|
|
||||||
}>;
|
|
||||||
}): Promise<any>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消订单履行
|
* 取消订单履行
|
||||||
*/
|
*/
|
||||||
|
|
@ -273,4 +250,276 @@ export interface ISiteAdapter {
|
||||||
* 删除订单履行信息
|
* 删除订单履行信息
|
||||||
*/
|
*/
|
||||||
deleteOrderFulfillment(orderId: string | number, fulfillmentId: string): Promise<boolean>;
|
deleteOrderFulfillment(orderId: string | number, fulfillmentId: string): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量处理订单
|
||||||
|
*/
|
||||||
|
batchProcessOrders?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
|
||||||
|
|
||||||
|
// ========== 产品映射方法 ==========
|
||||||
|
/**
|
||||||
|
* 将平台产品数据转换为统一产品数据格式
|
||||||
|
* @param data 平台特定产品数据
|
||||||
|
* @returns 统一产品数据格式
|
||||||
|
*/
|
||||||
|
mapPlatformToUnifiedProduct(data: any): UnifiedProductDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一产品数据格式转换为平台产品数据
|
||||||
|
* @param data 统一产品数据格式
|
||||||
|
* @returns 平台特定产品数据
|
||||||
|
*/
|
||||||
|
mapUnifiedToPlatformProduct(data: Partial<UnifiedProductDTO>): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一产品创建参数转换为平台产品创建参数
|
||||||
|
* @param data 统一产品创建参数
|
||||||
|
* @returns 平台产品创建参数
|
||||||
|
*/
|
||||||
|
mapCreateProductParams(data: Partial<UnifiedProductDTO>): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一产品更新参数转换为平台产品更新参数
|
||||||
|
* @param data 统一产品更新参数
|
||||||
|
* @returns 平台产品更新参数
|
||||||
|
*/
|
||||||
|
mapUpdateProductParams(data: Partial<UnifiedProductDTO>): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个产品
|
||||||
|
*/
|
||||||
|
getProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<UnifiedProductDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取产品列表
|
||||||
|
*/
|
||||||
|
getProducts(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedProductDTO>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有产品
|
||||||
|
*/
|
||||||
|
getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建产品
|
||||||
|
*/
|
||||||
|
createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新产品
|
||||||
|
*/
|
||||||
|
updateProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>, data: Partial<UnifiedProductDTO>): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除产品
|
||||||
|
*/
|
||||||
|
deleteProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量处理产品
|
||||||
|
*/
|
||||||
|
batchProcessProducts?(data: BatchOperationDTO): Promise<BatchOperationResultDTO>;
|
||||||
|
|
||||||
|
// ========== 评论映射方法 ==========
|
||||||
|
/**
|
||||||
|
* 将平台评论数据转换为统一评论数据格式
|
||||||
|
* @param data 平台特定评论数据
|
||||||
|
* @returns 统一评论数据格式
|
||||||
|
*/
|
||||||
|
mapPlatformToUnifiedReview(data: any): UnifiedReviewDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一评论数据格式转换为平台评论数据
|
||||||
|
* @param data 统一评论数据格式
|
||||||
|
* @returns 平台特定评论数据
|
||||||
|
*/
|
||||||
|
mapUnifiedToPlatformReview(data: Partial<UnifiedReviewDTO>): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一评论创建参数转换为平台评论创建参数
|
||||||
|
* @param data 统一评论创建参数
|
||||||
|
* @returns 平台评论创建参数
|
||||||
|
*/
|
||||||
|
mapCreateReviewParams(data: CreateReviewDTO): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一评论更新参数转换为平台评论更新参数
|
||||||
|
* @param data 统一评论更新参数
|
||||||
|
* @returns 平台评论更新参数
|
||||||
|
*/
|
||||||
|
mapUpdateReviewParams(data: UpdateReviewDTO): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论列表
|
||||||
|
*/
|
||||||
|
getReviews(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedReviewDTO>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有评论
|
||||||
|
*/
|
||||||
|
getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建评论
|
||||||
|
*/
|
||||||
|
createReview(data: CreateReviewDTO): Promise<UnifiedReviewDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新评论
|
||||||
|
*/
|
||||||
|
updateReview(where: Partial<Pick<UnifiedReviewDTO, 'id'>>, data: UpdateReviewDTO): Promise<UnifiedReviewDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除评论
|
||||||
|
*/
|
||||||
|
deleteReview(where: Partial<Pick<UnifiedReviewDTO, 'id'>>): Promise<boolean>;
|
||||||
|
|
||||||
|
// ========== 订阅映射方法 ==========
|
||||||
|
/**
|
||||||
|
* 将平台订阅数据转换为统一订阅数据格式
|
||||||
|
* @param data 平台特定订阅数据
|
||||||
|
* @returns 统一订阅数据格式
|
||||||
|
*/
|
||||||
|
mapPlatformToUnifiedSubscription(data: any): UnifiedSubscriptionDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一订阅数据格式转换为平台订阅数据
|
||||||
|
* @param data 统一订阅数据格式
|
||||||
|
* @returns 平台特定订阅数据
|
||||||
|
*/
|
||||||
|
mapUnifiedToPlatformSubscription(data: Partial<UnifiedSubscriptionDTO>): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订阅列表
|
||||||
|
*/
|
||||||
|
getSubscriptions(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有订阅
|
||||||
|
*/
|
||||||
|
getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]>;
|
||||||
|
|
||||||
|
// ========== 产品变体映射方法 ==========
|
||||||
|
/**
|
||||||
|
* 将平台产品变体数据转换为统一产品变体数据格式
|
||||||
|
* @param data 平台特定产品变体数据
|
||||||
|
* @returns 统一产品变体数据格式
|
||||||
|
*/
|
||||||
|
mapPlatformToUnifiedVariation(data: any): UnifiedProductVariationDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一产品变体数据格式转换为平台产品变体数据
|
||||||
|
* @param data 统一产品变体数据格式
|
||||||
|
* @returns 平台特定产品变体数据
|
||||||
|
*/
|
||||||
|
mapUnifiedToPlatformVariation(data: Partial<UnifiedProductVariationDTO>): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一产品变体创建参数转换为平台产品变体创建参数
|
||||||
|
* @param data 统一产品变体创建参数
|
||||||
|
* @returns 平台产品变体创建参数
|
||||||
|
*/
|
||||||
|
mapCreateVariationParams(data: CreateVariationDTO): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一产品变体更新参数转换为平台产品变体更新参数
|
||||||
|
* @param data 统一产品变体更新参数
|
||||||
|
* @returns 平台产品变体更新参数
|
||||||
|
*/
|
||||||
|
mapUpdateVariationParams(data: UpdateVariationDTO): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个产品变体
|
||||||
|
*/
|
||||||
|
getVariation(productId: string | number, variationId: string | number): Promise<UnifiedProductVariationDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取产品变体列表
|
||||||
|
*/
|
||||||
|
getVariations(productId: string | number, params: UnifiedSearchParamsDTO): Promise<UnifiedVariationPaginationDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有产品变体
|
||||||
|
*/
|
||||||
|
getAllVariations(productId: string | number, params?: UnifiedSearchParamsDTO): Promise<UnifiedProductVariationDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建产品变体
|
||||||
|
*/
|
||||||
|
createVariation(productId: string | number, data: CreateVariationDTO): Promise<UnifiedProductVariationDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新产品变体
|
||||||
|
*/
|
||||||
|
updateVariation(productId: string | number, variationId: string | number, data: UpdateVariationDTO): Promise<UnifiedProductVariationDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除产品变体
|
||||||
|
*/
|
||||||
|
deleteVariation(productId: string | number, variationId: string | number): Promise<boolean>;
|
||||||
|
|
||||||
|
// ========== Webhook映射方法 ==========
|
||||||
|
/**
|
||||||
|
* 将平台Webhook数据转换为统一Webhook数据格式
|
||||||
|
* @param data 平台特定Webhook数据
|
||||||
|
* @returns 统一Webhook数据格式
|
||||||
|
*/
|
||||||
|
mapPlatformToUnifiedWebhook(data: any): UnifiedWebhookDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一Webhook数据格式转换为平台Webhook数据
|
||||||
|
* @param data 统一Webhook数据格式
|
||||||
|
* @returns 平台特定Webhook数据
|
||||||
|
*/
|
||||||
|
mapUnifiedToPlatformWebhook(data: Partial<UnifiedWebhookDTO>): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一Webhook创建参数转换为平台Webhook创建参数
|
||||||
|
* @param data 统一Webhook创建参数
|
||||||
|
* @returns 平台Webhook创建参数
|
||||||
|
*/
|
||||||
|
mapCreateWebhookParams(data: CreateWebhookDTO): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将统一Webhook更新参数转换为平台Webhook更新参数
|
||||||
|
* @param data 统一Webhook更新参数
|
||||||
|
* @returns 平台Webhook更新参数
|
||||||
|
*/
|
||||||
|
mapUpdateWebhookParams(data: UpdateWebhookDTO): any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取单个webhook
|
||||||
|
*/
|
||||||
|
getWebhook(where: Partial<Pick<UnifiedWebhookDTO, 'id'>>): Promise<UnifiedWebhookDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取webhooks列表
|
||||||
|
*/
|
||||||
|
getWebhooks(params: UnifiedSearchParamsDTO): Promise<UnifiedWebhookPaginationDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有webhooks
|
||||||
|
*/
|
||||||
|
getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建webhook
|
||||||
|
*/
|
||||||
|
createWebhook(data: CreateWebhookDTO): Promise<UnifiedWebhookDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新webhook
|
||||||
|
*/
|
||||||
|
updateWebhook(where: Partial<Pick<UnifiedWebhookDTO, 'id'>>, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除webhook
|
||||||
|
*/
|
||||||
|
deleteWebhook(where: Partial<Pick<UnifiedWebhookDTO, 'id'>>): Promise<boolean>;
|
||||||
|
|
||||||
|
// ========== 站点/其他方法 ==========
|
||||||
|
/**
|
||||||
|
* 获取站点链接列表
|
||||||
|
*/
|
||||||
|
getLinks(): Promise<Array<{ title: string, url: string }>>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,10 +75,14 @@ export class SyncUniuniShipmentJob implements IJob{
|
||||||
'255': 'Gateway_To_Gateway_Transit'
|
'255': 'Gateway_To_Gateway_Transit'
|
||||||
};
|
};
|
||||||
async onTick() {
|
async onTick() {
|
||||||
const shipments:Shipment[] = await this.shipmentModel.findBy({ finished: false });
|
try {
|
||||||
shipments.forEach(shipment => {
|
const shipments:Shipment[] = await this.shipmentModel.findBy({ finished: false });
|
||||||
this.logisticsService.updateShipmentState(shipment);
|
shipments.forEach(shipment => {
|
||||||
});
|
this.logisticsService.updateShipmentState(shipment);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`更新运单状态失败 ${error.message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onComplete(result: any) {
|
onComplete(result: any) {
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export class CustomerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof dateValue === 'number') {
|
if (typeof dateValue === 'number') {
|
||||||
// 处理Unix时间戳(秒或毫秒)
|
// 处理Unix时间戳(秒或毫秒)
|
||||||
return new Date(dateValue > 9999999999 ? dateValue : dateValue * 1000);
|
return new Date(dateValue > 9999999999 ? dateValue : dateValue * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +95,7 @@ export class CustomerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建或更新客户(upsert)
|
* 创建或更新客户(upsert)
|
||||||
* 如果客户存在则更新,不存在则创建
|
* 如果客户存在则更新,不存在则创建
|
||||||
*/
|
*/
|
||||||
async upsertCustomer(
|
async upsertCustomer(
|
||||||
|
|
@ -157,24 +157,24 @@ export class CustomerService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从站点同步客户数据
|
* 从站点同步客户数据
|
||||||
* 第一步:调用adapter获取站点客户数据
|
* 第一步:调用adapter获取站点客户数据
|
||||||
* 第二步:通过upsertManyCustomers保存这些客户
|
* 第二步:通过upsertManyCustomers保存这些客户
|
||||||
*/
|
*/
|
||||||
async syncCustomersFromSite(
|
async syncCustomersFromSite(
|
||||||
siteId: number,
|
siteId: number,
|
||||||
params?: UnifiedSearchParamsDTO
|
params?: UnifiedSearchParamsDTO
|
||||||
): Promise<SyncOperationResult> {
|
): Promise<SyncOperationResult> {
|
||||||
try {
|
try {
|
||||||
// 第一步:获取适配器并从站点获取客户数据
|
// 第一步:获取适配器并从站点获取客户数据
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const siteCustomers = await adapter.getAllCustomers(params || {});
|
const siteCustomers = await adapter.getAllCustomers(params || {});
|
||||||
|
|
||||||
// 第二步:将站点客户数据转换为客户实体数据
|
// 第二步:将站点客户数据转换为客户实体数据
|
||||||
const customersData = siteCustomers.map(siteCustomer => {
|
const customersData = siteCustomers.map(siteCustomer => {
|
||||||
return this.mapSiteCustomerToCustomer(siteCustomer, siteId);
|
return this.mapSiteCustomerToCustomer(siteCustomer, siteId);
|
||||||
})
|
})
|
||||||
|
|
||||||
// 第三步:批量upsert客户数据
|
// 第三步:批量upsert客户数据
|
||||||
const upsertResult = await this.upsertManyCustomers(customersData);
|
const upsertResult = await this.upsertManyCustomers(customersData);
|
||||||
return {
|
return {
|
||||||
total: siteCustomers.length,
|
total: siteCustomers.length,
|
||||||
|
|
@ -192,7 +192,7 @@ export class CustomerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取客户统计列表(包含订单统计信息)
|
* 获取客户统计列表(包含订单统计信息)
|
||||||
* 支持分页、搜索和排序功能
|
* 支持分页、搜索和排序功能
|
||||||
* 使用原生SQL查询实现复杂的统计逻辑
|
* 使用原生SQL查询实现复杂的统计逻辑
|
||||||
*/
|
*/
|
||||||
|
|
@ -363,7 +363,7 @@ export class CustomerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取纯粹的客户列表(不包含订单统计信息)
|
* 获取纯粹的客户列表(不包含订单统计信息)
|
||||||
* 支持基本的分页、搜索和排序功能
|
* 支持基本的分页、搜索和排序功能
|
||||||
* 使用TypeORM查询构建器实现
|
* 使用TypeORM查询构建器实现
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ export class DictService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新或创建字典项 (Upsert)
|
// 更新或创建字典项 (Upsert)
|
||||||
// 如果字典项已存在(根据 name 和 dictId 判断),则更新;否则创建新的
|
// 如果字典项已存在(根据 name 和 dictId 判断),则更新;否则创建新的
|
||||||
async upsertDictItem(dictId: number, itemData: {
|
async upsertDictItem(dictId: number, itemData: {
|
||||||
name: string;
|
name: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|
@ -252,7 +252,7 @@ export class DictService {
|
||||||
// 格式化 name
|
// 格式化 name
|
||||||
const formattedName = this.formatName(itemData.name);
|
const formattedName = this.formatName(itemData.name);
|
||||||
|
|
||||||
// 查找是否已存在该字典项(根据 name 和 dictId)
|
// 查找是否已存在该字典项(根据 name 和 dictId)
|
||||||
const existingItem = await this.dictItemModel.findOne({
|
const existingItem = await this.dictItemModel.findOne({
|
||||||
where: {
|
where: {
|
||||||
name: formattedName,
|
name: formattedName,
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ export class OrderService {
|
||||||
try {
|
try {
|
||||||
// 调用 WooCommerce API 获取订单
|
// 调用 WooCommerce API 获取订单
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const order = await adapter.getOrder(orderId);
|
const order = await adapter.getOrder({ id: orderId });
|
||||||
|
|
||||||
// 检查订单是否已存在,以区分创建和更新
|
// 检查订单是否已存在,以区分创建和更新
|
||||||
const existingOrder = await this.orderModel.findOne({
|
const existingOrder = await this.orderModel.findOne({
|
||||||
|
|
@ -479,7 +479,7 @@ export class OrderService {
|
||||||
// 如果不能更新 ERP 状态,则保留原有的 orderStatus
|
// 如果不能更新 ERP 状态,则保留原有的 orderStatus
|
||||||
entity.orderStatus = existingOrder.orderStatus;
|
entity.orderStatus = existingOrder.orderStatus;
|
||||||
}
|
}
|
||||||
// 更新订单数据(包括 shipping、billing 等字段)
|
// 更新订单数据(包括 shipping、billing 等字段)
|
||||||
await this.orderModel.update(existingOrder.id, entity);
|
await this.orderModel.update(existingOrder.id, entity);
|
||||||
entity.id = existingOrder.id;
|
entity.id = existingOrder.id;
|
||||||
return entity;
|
return entity;
|
||||||
|
|
@ -2567,8 +2567,8 @@ export class OrderService {
|
||||||
* 导出数据为CSV格式
|
* 导出数据为CSV格式
|
||||||
* @param {any[]} data 数据数组
|
* @param {any[]} data 数据数组
|
||||||
* @param {Object} options 配置选项
|
* @param {Object} options 配置选项
|
||||||
* @param {string} [options.type='string'] 输出类型:'string' | 'buffer'
|
* @param {string} [options.type='string'] 输出类型:'string' | 'buffer'
|
||||||
* @param {string} [options.fileName] 文件名(仅当需要写入文件时使用)
|
* @param {string} [options.fileName] 文件名(仅当需要写入文件时使用)
|
||||||
* @param {boolean} [options.writeFile=false] 是否写入文件
|
* @param {boolean} [options.writeFile=false] 是否写入文件
|
||||||
* @returns {string|Buffer} 根据type返回字符串或Buffer
|
* @returns {string|Buffer} 根据type返回字符串或Buffer
|
||||||
*/
|
*/
|
||||||
|
|
@ -2617,7 +2617,7 @@ async exportToCsv(data: any[], options: { type?: 'string' | 'buffer'; fileName?:
|
||||||
// 获取当前用户目录
|
// 获取当前用户目录
|
||||||
const userHomeDir = os.homedir();
|
const userHomeDir = os.homedir();
|
||||||
|
|
||||||
// 构建目标路径(下载目录)
|
// 构建目标路径(下载目录)
|
||||||
const downloadsDir = path.join(userHomeDir, 'Downloads');
|
const downloadsDir = path.join(userHomeDir, 'Downloads');
|
||||||
|
|
||||||
// 确保下载目录存在
|
// 确保下载目录存在
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { StockPoint } from '../entity/stock_point.entity';
|
||||||
import { StockService } from './stock.service';
|
import { StockService } from './stock.service';
|
||||||
import { TemplateService } from './template.service';
|
import { TemplateService } from './template.service';
|
||||||
|
|
||||||
import { SyncOperationResultDTO, UnifiedSearchParamsDTO } from '../dto/api.dto';
|
import { BatchErrorItem, BatchOperationResult, SyncOperationResultDTO, UnifiedSearchParamsDTO } from '../dto/api.dto';
|
||||||
import { UnifiedProductDTO } from '../dto/site-api.dto';
|
import { UnifiedProductDTO } from '../dto/site-api.dto';
|
||||||
import { ProductSiteSkuDTO, SyncProductToSiteDTO } from '../dto/site-sync.dto';
|
import { ProductSiteSkuDTO, SyncProductToSiteDTO } from '../dto/site-sync.dto';
|
||||||
import { Category } from '../entity/category.entity';
|
import { Category } from '../entity/category.entity';
|
||||||
|
|
@ -225,7 +225,7 @@ export class ProductService {
|
||||||
where: {
|
where: {
|
||||||
sku,
|
sku,
|
||||||
},
|
},
|
||||||
relations: ['category', 'attributes', 'attributes.dict', 'siteSkus']
|
relations: ['category', 'attributes', 'attributes.dict']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,7 +235,7 @@ export class ProductService {
|
||||||
.leftJoinAndSelect('product.attributes', 'attribute')
|
.leftJoinAndSelect('product.attributes', 'attribute')
|
||||||
.leftJoinAndSelect('attribute.dict', 'dict')
|
.leftJoinAndSelect('attribute.dict', 'dict')
|
||||||
.leftJoinAndSelect('product.category', 'category');
|
.leftJoinAndSelect('product.category', 'category');
|
||||||
// 处理分页参数(支持新旧两种格式)
|
// 处理分页参数(支持新旧两种格式)
|
||||||
const page = query.page || 1;
|
const page = query.page || 1;
|
||||||
const pageSize = query.per_page || 10;
|
const pageSize = query.per_page || 10;
|
||||||
|
|
||||||
|
|
@ -393,7 +393,7 @@ export class ProductService {
|
||||||
qb.andWhere('product.updatedAt <= :whereUpdatedAtEnd', { whereUpdatedAtEnd: new Date(query.where.updatedAtEnd) });
|
qb.andWhere('product.updatedAt <= :whereUpdatedAtEnd', { whereUpdatedAtEnd: new Date(query.where.updatedAtEnd) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 品牌过滤(向后兼容)
|
// 品牌过滤(向后兼容)
|
||||||
if (brandId) {
|
if (brandId) {
|
||||||
qb.andWhere(qb => {
|
qb.andWhere(qb => {
|
||||||
const subQuery = qb
|
const subQuery = qb
|
||||||
|
|
@ -423,7 +423,7 @@ export class ProductService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分类过滤(向后兼容)
|
// 分类过滤(向后兼容)
|
||||||
if (categoryId) {
|
if (categoryId) {
|
||||||
qb.andWhere('product.categoryId = :categoryId', { categoryId });
|
qb.andWhere('product.categoryId = :categoryId', { categoryId });
|
||||||
}
|
}
|
||||||
|
|
@ -443,7 +443,7 @@ export class ProductService {
|
||||||
qb.andWhere('product.categoryId IN (:...whereCategoryIds)', { whereCategoryIds: query.where.categoryIds });
|
qb.andWhere('product.categoryId IN (:...whereCategoryIds)', { whereCategoryIds: query.where.categoryIds });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理排序(支持新旧两种格式)
|
// 处理排序(支持新旧两种格式)
|
||||||
if (orderBy) {
|
if (orderBy) {
|
||||||
if (typeof orderBy === 'string') {
|
if (typeof orderBy === 'string') {
|
||||||
// 如果orderBy是字符串,尝试解析JSON
|
// 如果orderBy是字符串,尝试解析JSON
|
||||||
|
|
@ -1455,6 +1455,9 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理分类字段
|
||||||
|
const category = val(rec.category);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sku,
|
sku,
|
||||||
name: val(rec.name),
|
name: val(rec.name),
|
||||||
|
|
@ -1464,6 +1467,7 @@ export class ProductService {
|
||||||
promotionPrice: num(rec.promotionPrice),
|
promotionPrice: num(rec.promotionPrice),
|
||||||
type: val(rec.type),
|
type: val(rec.type),
|
||||||
siteSkus: rec.siteSkus ? String(rec.siteSkus).split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
siteSkus: rec.siteSkus ? String(rec.siteSkus).split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
||||||
|
category, // 添加分类字段
|
||||||
|
|
||||||
attributes: attributes.length > 0 ? attributes : undefined,
|
attributes: attributes.length > 0 ? attributes : undefined,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
@ -1483,10 +1487,15 @@ export class ProductService {
|
||||||
if (data.price !== undefined) dto.price = Number(data.price);
|
if (data.price !== undefined) dto.price = Number(data.price);
|
||||||
if (data.promotionPrice !== undefined) dto.promotionPrice = Number(data.promotionPrice);
|
if (data.promotionPrice !== undefined) dto.promotionPrice = Number(data.promotionPrice);
|
||||||
|
|
||||||
if (data.categoryId !== undefined) dto.categoryId = Number(data.categoryId);
|
// 处理分类字段
|
||||||
|
if (data.categoryId !== undefined) {
|
||||||
|
dto.categoryId = Number(data.categoryId);
|
||||||
|
} else if (data.category) {
|
||||||
|
// 如果是字符串,需要后续在createProduct中处理
|
||||||
|
dto.attributes = [...(dto.attributes || []), { dictName: 'category', title: data.category }];
|
||||||
|
}
|
||||||
|
|
||||||
// 默认值和特殊处理
|
// 默认值和特殊处理
|
||||||
|
|
||||||
dto.attributes = Array.isArray(data.attributes) ? data.attributes : [];
|
dto.attributes = Array.isArray(data.attributes) ? data.attributes : [];
|
||||||
|
|
||||||
// 如果有组件信息,透传
|
// 如果有组件信息,透传
|
||||||
|
|
@ -1508,7 +1517,13 @@ export class ProductService {
|
||||||
if (data.price !== undefined) dto.price = Number(data.price);
|
if (data.price !== undefined) dto.price = Number(data.price);
|
||||||
if (data.promotionPrice !== undefined) dto.promotionPrice = Number(data.promotionPrice);
|
if (data.promotionPrice !== undefined) dto.promotionPrice = Number(data.promotionPrice);
|
||||||
|
|
||||||
if (data.categoryId !== undefined) dto.categoryId = Number(data.categoryId);
|
// 处理分类字段
|
||||||
|
if (data.categoryId !== undefined) {
|
||||||
|
dto.categoryId = Number(data.categoryId);
|
||||||
|
} else if (data.category) {
|
||||||
|
// 如果是字符串,需要后续在updateProduct中处理
|
||||||
|
dto.attributes = [...(dto.attributes || []), { dictName: 'category', title: data.category }];
|
||||||
|
}
|
||||||
|
|
||||||
if (data.type !== undefined) dto.type = data.type;
|
if (data.type !== undefined) dto.type = data.type;
|
||||||
if (data.attributes !== undefined) dto.attributes = data.attributes;
|
if (data.attributes !== undefined) dto.attributes = data.attributes;
|
||||||
|
|
@ -1548,8 +1563,8 @@ export class ProductService {
|
||||||
esc(p.price),
|
esc(p.price),
|
||||||
esc(p.promotionPrice),
|
esc(p.promotionPrice),
|
||||||
esc(p.type),
|
esc(p.type),
|
||||||
|
|
||||||
esc(p.description),
|
esc(p.description),
|
||||||
|
esc(p.category ? p.category.name || p.category.title : ''), // 添加分类字段
|
||||||
];
|
];
|
||||||
|
|
||||||
// 属性数据
|
// 属性数据
|
||||||
|
|
@ -1575,9 +1590,9 @@ export class ProductService {
|
||||||
|
|
||||||
// 导出所有产品为 CSV 文本
|
// 导出所有产品为 CSV 文本
|
||||||
async exportProductsCSV(): Promise<string> {
|
async exportProductsCSV(): Promise<string> {
|
||||||
// 查询所有产品及其属性(包含字典关系)和组成
|
// 查询所有产品及其属性(包含字典关系)、组成和分类
|
||||||
const products = await this.productModel.find({
|
const products = await this.productModel.find({
|
||||||
relations: ['attributes', 'attributes.dict', 'components'],
|
relations: ['attributes', 'attributes.dict', 'components', 'category'],
|
||||||
order: { id: 'ASC' },
|
order: { id: 'ASC' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1612,8 +1627,8 @@ export class ProductService {
|
||||||
'price',
|
'price',
|
||||||
'promotionPrice',
|
'promotionPrice',
|
||||||
'type',
|
'type',
|
||||||
|
|
||||||
'description',
|
'description',
|
||||||
|
'category',
|
||||||
];
|
];
|
||||||
|
|
||||||
// 动态属性表头
|
// 动态属性表头
|
||||||
|
|
@ -1640,7 +1655,7 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 CSV 导入产品;存在则更新,不存在则创建
|
// 从 CSV 导入产品;存在则更新,不存在则创建
|
||||||
async importProductsCSV(file: any): Promise<{ created: number; updated: number; errors: string[] }> {
|
async importProductsCSV(file: any): Promise<BatchOperationResult> {
|
||||||
let buffer: Buffer;
|
let buffer: Buffer;
|
||||||
if (Buffer.isBuffer(file)) {
|
if (Buffer.isBuffer(file)) {
|
||||||
buffer = file;
|
buffer = file;
|
||||||
|
|
@ -1676,19 +1691,19 @@ export class ProductService {
|
||||||
console.log('First record keys:', Object.keys(records[0]));
|
console.log('First record keys:', Object.keys(records[0]));
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return { created: 0, updated: 0, errors: [`CSV 解析失败:${e?.message || e}`] };
|
throw new Error(`CSV 解析失败:${e?.message || e}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
let created = 0;
|
let created = 0;
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
const errors: string[] = [];
|
const errors: BatchErrorItem[] = [];
|
||||||
|
|
||||||
// 逐条处理记录
|
// 逐条处理记录
|
||||||
for (const rec of records) {
|
for (const rec of records) {
|
||||||
try {
|
try {
|
||||||
const data = this.transformCsvRecordToData(rec);
|
const data = this.transformCsvRecordToData(rec);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
errors.push('缺少 SKU 的记录已跳过');
|
errors.push({ identifier: data.sku, error: '缺少 SKU 的记录已跳过'});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { sku } = data;
|
const { sku } = data;
|
||||||
|
|
@ -1708,11 +1723,11 @@ export class ProductService {
|
||||||
updated += 1;
|
updated += 1;
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
errors.push(`产品${rec?.sku}导入失败:${e?.message || String(e)}`);
|
errors.push({ identifier: '' + rec.sku, error: `产品${rec?.sku}导入失败:${e?.message || String(e)}`});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { created, updated, errors };
|
return { total: records.length, processed: records.length - errors.length, created, updated, errors };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将库存记录的 sku 添加到产品单品中
|
// 将库存记录的 sku 添加到产品单品中
|
||||||
|
|
@ -1750,7 +1765,7 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 根据ID获取产品详情(包含站点SKU)
|
// 根据ID获取产品详情(包含站点SKU)
|
||||||
async getProductById(id: number): Promise<Product> {
|
async getProductById(id: number): Promise<Product> {
|
||||||
const product = await this.productModel.findOne({
|
const product = await this.productModel.findOne({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
|
@ -1831,9 +1846,7 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将本地产品转换为站点API所需格式
|
// 将本地产品转换为站点API所需格式
|
||||||
const unifiedProduct = await this.convertLocalProductToUnifiedProduct(localProduct, params.siteSku);
|
const unifiedProduct = await this.mapLocalToUnifiedProduct(localProduct, params.siteSku);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 调用站点API的upsertProduct方法
|
// 调用站点API的upsertProduct方法
|
||||||
try {
|
try {
|
||||||
|
|
@ -1842,7 +1855,7 @@ export class ProductService {
|
||||||
await this.bindSiteSkus(localProduct.id, [unifiedProduct.sku]);
|
await this.bindSiteSkus(localProduct.id, [unifiedProduct.sku]);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`同步产品到站点失败: ${error.message}`);
|
throw new Error(`同步产品到站点失败: ${error?.response?.data?.message??error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1869,9 +1882,6 @@ export class ProductService {
|
||||||
siteSku: item.siteSku
|
siteSku: item.siteSku
|
||||||
});
|
});
|
||||||
|
|
||||||
// 然后绑定站点SKU
|
|
||||||
await this.bindSiteSkus(item.productId, [item.siteSku]);
|
|
||||||
|
|
||||||
results.synced++;
|
results.synced++;
|
||||||
results.processed++;
|
results.processed++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1892,30 +1902,23 @@ export class ProductService {
|
||||||
* @param siteProductId 站点产品ID
|
* @param siteProductId 站点产品ID
|
||||||
* @returns 同步后的本地产品
|
* @returns 同步后的本地产品
|
||||||
*/
|
*/
|
||||||
async syncProductFromSite(siteId: number, siteProductId: string | number): Promise<any> {
|
async syncProductFromSite(siteId: number, siteProductId: string | number, sku: string): Promise<any> {
|
||||||
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
|
const siteProduct = await adapter.getProduct({ id: siteProductId });
|
||||||
// 从站点获取产品信息
|
// 从站点获取产品信息
|
||||||
const siteProduct = await this.siteApiService.getProductFromSite(siteId, siteProductId);
|
|
||||||
if (!siteProduct) {
|
if (!siteProduct) {
|
||||||
throw new Error(`站点产品 ID ${siteProductId} 不存在`);
|
throw new Error(`站点产品 ID ${siteProductId} 不存在`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已存在相同SKU的本地产品
|
|
||||||
let localProduct = null;
|
|
||||||
if (siteProduct.sku) {
|
|
||||||
try {
|
|
||||||
localProduct = await this.findProductBySku(siteProduct.sku);
|
|
||||||
} catch (error) {
|
|
||||||
// 产品不存在,继续创建
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将站点产品转换为本地产品格式
|
// 将站点产品转换为本地产品格式
|
||||||
const productData = await this.convertSiteProductToLocalProduct(siteProduct);
|
const productData = await this.mapUnifiedToLocalProduct(siteProduct);
|
||||||
|
return await this.upsertProduct({sku}, productData);
|
||||||
if (localProduct) {
|
}
|
||||||
|
async upsertProduct(where: Partial<Pick<Product,'id'| 'sku'>>, productData: any) {
|
||||||
|
const existingProduct = await this.productModel.findOne({ where: where});
|
||||||
|
if (existingProduct) {
|
||||||
// 更新现有产品
|
// 更新现有产品
|
||||||
const updateData: UpdateProductDTO = productData;
|
const updateData: UpdateProductDTO = productData;
|
||||||
return await this.updateProduct(localProduct.id, updateData);
|
return await this.updateProduct(existingProduct.id, updateData);
|
||||||
} else {
|
} else {
|
||||||
// 创建新产品
|
// 创建新产品
|
||||||
const createData: CreateProductDTO = productData;
|
const createData: CreateProductDTO = productData;
|
||||||
|
|
@ -1929,18 +1932,18 @@ export class ProductService {
|
||||||
* @param siteProductIds 站点产品ID数组
|
* @param siteProductIds 站点产品ID数组
|
||||||
* @returns 批量同步结果
|
* @returns 批量同步结果
|
||||||
*/
|
*/
|
||||||
async batchSyncFromSite(siteId: number, siteProductIds: (string | number)[]): Promise<{ synced: number, errors: string[] }> {
|
async batchSyncFromSite(siteId: number, data: Array<{siteProductId:string, sku: string}>): Promise<{ synced: number, errors: string[] }> {
|
||||||
const results = {
|
const results = {
|
||||||
synced: 0,
|
synced: 0,
|
||||||
errors: []
|
errors: []
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const siteProductId of siteProductIds) {
|
for (const item of data) {
|
||||||
try {
|
try {
|
||||||
await this.syncProductFromSite(siteId, siteProductId);
|
await this.syncProductFromSite(siteId, item.siteProductId, item.sku);
|
||||||
results.synced++;
|
results.synced++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
results.errors.push(`站点产品ID ${siteProductId} 同步失败: ${error.message}`);
|
results.errors.push(`站点产品ID ${item.siteProductId} 同步失败: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1952,7 +1955,7 @@ export class ProductService {
|
||||||
* @param siteProduct 站点产品对象
|
* @param siteProduct 站点产品对象
|
||||||
* @returns 本地产品数据
|
* @returns 本地产品数据
|
||||||
*/
|
*/
|
||||||
private async convertSiteProductToLocalProduct(siteProduct: any): Promise<CreateProductDTO> {
|
private async mapUnifiedToLocalProduct(siteProduct: any): Promise<CreateProductDTO> {
|
||||||
const productData: any = {
|
const productData: any = {
|
||||||
sku: siteProduct.sku,
|
sku: siteProduct.sku,
|
||||||
name: siteProduct.name,
|
name: siteProduct.name,
|
||||||
|
|
@ -2015,18 +2018,20 @@ export class ProductService {
|
||||||
* @param localProduct 本地产品对象
|
* @param localProduct 本地产品对象
|
||||||
* @returns 统一产品对象
|
* @returns 统一产品对象
|
||||||
*/
|
*/
|
||||||
private async convertLocalProductToUnifiedProduct(localProduct: Product,siteSku?: string): Promise<Partial<UnifiedProductDTO>> {
|
private async mapLocalToUnifiedProduct(localProduct: Product,siteSku?: string): Promise<Partial<UnifiedProductDTO>> {
|
||||||
|
const tags = localProduct.attributes?.map(a => ({name: a.name})) || [];
|
||||||
// 将本地产品数据转换为UnifiedProductDTO格式
|
// 将本地产品数据转换为UnifiedProductDTO格式
|
||||||
const unifiedProduct: any = {
|
const unifiedProduct: any = {
|
||||||
id: localProduct.id ? String(localProduct.id) : undefined, // 如果产品已存在,使用现有ID
|
id: localProduct.id ? String(localProduct.id) : undefined, // 如果产品已存在,使用现有ID
|
||||||
name: localProduct.nameCn || localProduct.name || localProduct.sku,
|
name: localProduct.name,
|
||||||
type: 'simple', // 默认类型,可以根据实际需要调整
|
type: localProduct.type === 'single'? 'simple' : 'bundle', // 默认类型,可以根据实际需要调整
|
||||||
status: 'publish', // 默认状态,可以根据实际需要调整
|
status: 'publish', // 默认状态,可以根据实际需要调整
|
||||||
sku: siteSku || await this.templateService.render('site.product.sku', { sku: localProduct.sku }),
|
sku: siteSku || await this.templateService.render('site.product.sku', { product: localProduct, sku: localProduct.sku }),
|
||||||
regular_price: String(localProduct.price || 0),
|
regular_price: String(localProduct.price || 0),
|
||||||
sale_price: String(localProduct.promotionPrice || localProduct.price || 0),
|
sale_price: String(localProduct.promotionPrice || localProduct.price || 0),
|
||||||
price: String(localProduct.price || 0),
|
price: String(localProduct.price || 0),
|
||||||
// stock_status: localProduct.stockQuantity && localProduct.stockQuantity > 0 ? 'instock' : 'outofstock',
|
// TODO 库存暂时无法同步
|
||||||
|
// stock_status: localProduct.components && localProduct.stockQuantity > 0 ? 'instock' : 'outofstock',
|
||||||
// stock_quantity: localProduct.stockQuantity || 0,
|
// stock_quantity: localProduct.stockQuantity || 0,
|
||||||
// images: localProduct.images ? localProduct.images.map(img => ({
|
// images: localProduct.images ? localProduct.images.map(img => ({
|
||||||
// id: img.id,
|
// id: img.id,
|
||||||
|
|
@ -2034,25 +2039,24 @@ export class ProductService {
|
||||||
// name: img.name || '',
|
// name: img.name || '',
|
||||||
// alt: img.alt || ''
|
// alt: img.alt || ''
|
||||||
// })) : [],
|
// })) : [],
|
||||||
tags: [],
|
tags,
|
||||||
categories: localProduct.category ? [{
|
categories: localProduct.category ? [{
|
||||||
id: localProduct.category.id,
|
id: localProduct.category.id,
|
||||||
name: localProduct.category.name
|
name: localProduct.category.name
|
||||||
}] : [],
|
}] : [],
|
||||||
attributes: localProduct.attributes ? localProduct.attributes.map(attr => ({
|
attributes: localProduct.attributes ? localProduct.attributes.map(attr => ({
|
||||||
id: attr.id,
|
id: attr.dict.id,
|
||||||
name: attr.name,
|
name: attr.dict.name,
|
||||||
position: 0,
|
position: attr.dict.sort || 0,
|
||||||
visible: true,
|
visible: true,
|
||||||
variation: false,
|
variation: false,
|
||||||
options: [attr.value]
|
options: [attr.name]
|
||||||
})) : [],
|
})) : [],
|
||||||
variations: [],
|
variations: [],
|
||||||
date_created: localProduct.createdAt ? new Date(localProduct.createdAt).toISOString() : new Date().toISOString(),
|
date_created: localProduct.createdAt ? new Date(localProduct.createdAt).toISOString() : new Date().toISOString(),
|
||||||
date_modified: localProduct.updatedAt ? new Date(localProduct.updatedAt).toISOString() : new Date().toISOString(),
|
date_modified: localProduct.updatedAt ? new Date(localProduct.updatedAt).toISOString() : new Date().toISOString(),
|
||||||
raw: {
|
raw: {
|
||||||
localProductId: localProduct.id,
|
...localProduct
|
||||||
localProductSku: localProduct.sku
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
/**
|
||||||
|
* https://www.apizza.net/project/e114fb8e628e0f604379f5b26f0d8330/browse
|
||||||
|
*/
|
||||||
import { ILogger, Inject, Provide } from '@midwayjs/core';
|
import { ILogger, Inject, Provide } from '@midwayjs/core';
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
@ -125,7 +128,7 @@ export class ShopyyService {
|
||||||
* @returns 完整URL
|
* @returns 完整URL
|
||||||
*/
|
*/
|
||||||
private buildURL(baseUrl: string, endpoint: string): string {
|
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 base = baseUrl.replace(/\/$/, '');
|
||||||
const end = endpoint.replace(/^\//, '');
|
const end = endpoint.replace(/^\//, '');
|
||||||
return `${base}/${end}`;
|
return `${base}/${end}`;
|
||||||
|
|
@ -155,7 +158,7 @@ export class ShopyyService {
|
||||||
* @param params 请求参数
|
* @param params 请求参数
|
||||||
* @returns 响应数据
|
* @returns 响应数据
|
||||||
*/
|
*/
|
||||||
private async request(site: any, endpoint: string, method: string = 'GET', data: any = null, params: any = null): Promise<any> {
|
async request(site: any, endpoint: string, method: string = 'GET', data: any = null, params: any = null): Promise<any> {
|
||||||
const url = this.buildURL(site.apiUrl, endpoint);
|
const url = this.buildURL(site.apiUrl, endpoint);
|
||||||
const headers = this.buildHeaders(site);
|
const headers = this.buildHeaders(site);
|
||||||
|
|
||||||
|
|
@ -180,41 +183,19 @@ export class ShopyyService {
|
||||||
* 通用分页获取资源
|
* 通用分页获取资源
|
||||||
*/
|
*/
|
||||||
public async fetchResourcePaged<T>(site: any, endpoint: string, params: Record<string, any> = {}) {
|
public async fetchResourcePaged<T>(site: any, endpoint: string, params: Record<string, any> = {}) {
|
||||||
const page = Number(params.page || 1);
|
const response = await this.request(site, endpoint, 'GET', null, params);
|
||||||
const limit = Number(params.per_page ?? 20);
|
return this.mapPageResponse<T>(response,params);
|
||||||
const where = params.where && typeof params.where === 'object' ? params.where : {};
|
}
|
||||||
let orderby: string | undefined = params.orderby;
|
mapPageResponse<T>(response:any,query: Record<string, any>){
|
||||||
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];
|
|
||||||
orderby = field;
|
|
||||||
order = String(dir).toLowerCase() === 'desc' ? 'desc' : 'asc';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 映射统一入参到平台入参
|
|
||||||
const requestParams = {
|
|
||||||
...where,
|
|
||||||
...(params.search ? { search: params.search } : {}),
|
|
||||||
...(params.status ? { status: params.status } : {}),
|
|
||||||
...(orderby ? { orderby } : {}),
|
|
||||||
...(order ? { order } : {}),
|
|
||||||
page,
|
|
||||||
limit
|
|
||||||
};
|
|
||||||
this.logger.debug('ShopYY API请求分页参数:'+ JSON.stringify(requestParams));
|
|
||||||
const response = await this.request(site, endpoint, 'GET', null, requestParams);
|
|
||||||
if (response?.code !== 0) {
|
if (response?.code !== 0) {
|
||||||
throw new Error(response?.msg)
|
throw new Error(response?.msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: (response.data.list || []) as T[],
|
items: (response.data.list || []) as T[],
|
||||||
total: response.data?.paginate?.total || 0,
|
total: response.data?.paginate?.total || 0,
|
||||||
totalPages: response.data?.paginate?.pageTotal || 0,
|
totalPages: response.data?.paginate?.pageTotal || 0,
|
||||||
page: response.data?.paginate?.current || requestParams.page,
|
page: response.data?.paginate?.current || query.page,
|
||||||
per_page: response.data?.paginate?.pagesize || requestParams.limit,
|
per_page: response.data?.paginate?.pagesize || query.limit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,13 +206,13 @@ export class ShopyyService {
|
||||||
* @param pageSize 每页数量
|
* @param pageSize 每页数量
|
||||||
* @returns 分页产品列表
|
* @returns 分页产品列表
|
||||||
*/
|
*/
|
||||||
async getProducts(site: any, page: number = 1, pageSize: number = 100): Promise<any> {
|
async getProducts(site: any, page: number = 1, pageSize: number = 100, where: Record<string, any> = {}): Promise<any> {
|
||||||
// ShopYY API: GET /products
|
// ShopYY API: GET /products
|
||||||
// 通过 fields 参数指定需要返回的字段,确保 handle 等关键信息被包含
|
// 通过 fields 参数指定需要返回的字段,确保 handle 等关键信息被包含
|
||||||
const response = await this.request(site, 'products', 'GET', null, {
|
const response = await this.request(site, 'products', 'GET', null, {
|
||||||
page,
|
page,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
fields: 'id,name,sku,handle,status,type,stock_status,stock_quantity,images,regular_price,sale_price,tags,variations'
|
...where
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Inject, Provide } from '@midwayjs/core';
|
import { ILogger, Inject, Provide } from '@midwayjs/core';
|
||||||
import { ShopyyAdapter } from '../adapter/shopyy.adapter';
|
import { ShopyyAdapter } from '../adapter/shopyy.adapter';
|
||||||
import { WooCommerceAdapter } from '../adapter/woocommerce.adapter';
|
import { WooCommerceAdapter } from '../adapter/woocommerce.adapter';
|
||||||
import { ISiteAdapter } from '../interface/site-adapter.interface';
|
import { ISiteAdapter } from '../interface/site-adapter.interface';
|
||||||
|
|
@ -22,6 +22,9 @@ export class SiteApiService {
|
||||||
@Inject()
|
@Inject()
|
||||||
productService: ProductService;
|
productService: ProductService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
logger: ILogger;
|
||||||
|
|
||||||
async getAdapter(siteId: number): Promise<ISiteAdapter> {
|
async getAdapter(siteId: number): Promise<ISiteAdapter> {
|
||||||
const site = await this.siteService.get(siteId, true);
|
const site = await this.siteService.get(siteId, true);
|
||||||
if (!site) {
|
if (!site) {
|
||||||
|
|
@ -110,36 +113,25 @@ export class SiteApiService {
|
||||||
const adapter = await this.getAdapter(siteId);
|
const adapter = await this.getAdapter(siteId);
|
||||||
|
|
||||||
// 首先尝试查找产品
|
// 首先尝试查找产品
|
||||||
if (product.id) {
|
if (!product.sku) {
|
||||||
try {
|
throw new Error('产品SKU不能为空');
|
||||||
// 尝试获取产品以确认它是否存在
|
|
||||||
const existingProduct = await adapter.getProduct(product.id);
|
|
||||||
if (existingProduct) {
|
|
||||||
// 产品存在,执行更新
|
|
||||||
return await adapter.updateProduct(product.id, product);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 如果获取产品失败,可能是因为产品不存在,继续执行创建逻辑
|
|
||||||
console.log(`产品 ${product.id} 不存在,将创建新产品:`, error.message);
|
|
||||||
}
|
|
||||||
} else if (product.sku) {
|
|
||||||
// 如果没有提供ID但提供了SKU,尝试通过SKU查找产品
|
|
||||||
try {
|
|
||||||
// 尝试搜索具有相同SKU的产品
|
|
||||||
const searchResult = await adapter.getProducts({ where: { sku: product.sku } });
|
|
||||||
if (searchResult.items && searchResult.items.length > 0) {
|
|
||||||
const existingProduct = searchResult.items[0];
|
|
||||||
// 找到现有产品,更新它
|
|
||||||
return await adapter.updateProduct(existingProduct.id, product);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// 搜索失败,继续执行创建逻辑
|
|
||||||
console.log(`通过SKU搜索产品失败:`, error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 尝试搜索具有相同SKU的产品
|
||||||
|
let existingProduct
|
||||||
|
try {
|
||||||
|
|
||||||
|
existingProduct = await adapter.getProduct({ sku: product.sku });
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`[Site API] 查找产品失败, siteId: ${siteId}, sku: ${product.sku}, 错误信息: ${error.message}`);
|
||||||
|
existingProduct = null
|
||||||
|
}
|
||||||
|
if (existingProduct) {
|
||||||
|
// 找到现有产品,更新它
|
||||||
|
return await adapter.updateProduct({ id: existingProduct.id }, product);
|
||||||
|
}
|
||||||
// 产品不存在,执行创建
|
// 产品不存在,执行创建
|
||||||
return await adapter.createProduct(product);
|
return await adapter.createProduct(product);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -160,7 +152,7 @@ export class SiteApiService {
|
||||||
const result = await this.upsertProduct(siteId, product);
|
const result = await this.upsertProduct(siteId, product);
|
||||||
// 判断是创建还是更新
|
// 判断是创建还是更新
|
||||||
if (result && result.id) {
|
if (result && result.id) {
|
||||||
// 简单判断:如果产品原本没有ID而现在有了,说明是创建的
|
// 简单判断:如果产品原本没有ID而现在有了,说明是创建的
|
||||||
if (!product.id || !product.id.toString().trim()) {
|
if (!product.id || !product.id.toString().trim()) {
|
||||||
results.created.push(result);
|
results.created.push(result);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -189,17 +181,6 @@ export class SiteApiService {
|
||||||
return await adapter.getProducts(params);
|
return await adapter.getProducts(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从站点获取单个产品
|
|
||||||
* @param siteId 站点ID
|
|
||||||
* @param productId 产品ID
|
|
||||||
* @returns 站点产品
|
|
||||||
*/
|
|
||||||
async getProductFromSite(siteId: number, productId: string | number): Promise<any> {
|
|
||||||
const adapter = await this.getAdapter(siteId);
|
|
||||||
return await adapter.getProduct(productId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从站点获取所有产品
|
* 从站点获取所有产品
|
||||||
* @param siteId 站点ID
|
* @param siteId 站点ID
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export class WPService implements IPlatformService {
|
||||||
* @param site 站点配置
|
* @param site 站点配置
|
||||||
* @param namespace API 命名空间,默认 wc/v3;订阅推荐 wcs/v1
|
* @param namespace API 命名空间,默认 wc/v3;订阅推荐 wcs/v1
|
||||||
*/
|
*/
|
||||||
private createApi(site: any, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
public createApi(site: any, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
||||||
return new WooCommerceRestApi({
|
return new WooCommerceRestApi({
|
||||||
url: site.apiUrl,
|
url: site.apiUrl,
|
||||||
consumerKey: site.consumerKey,
|
consumerKey: site.consumerKey,
|
||||||
|
|
@ -240,9 +240,11 @@ export class WPService implements IPlatformService {
|
||||||
return allData;
|
return allData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProducts(site: any, page: number = 1, pageSize: number = 100): Promise<any> {
|
async getProducts(site: any, params: Record<string, any> = {}): Promise<any> {
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
return await this.sdkGetPage<WooProduct>(api, 'products', { page, per_page: pageSize });
|
const page = params.page ?? 1;
|
||||||
|
const per_page = params.per_page ?? params.pageSize ?? 100;
|
||||||
|
return await this.sdkGetPage<WooProduct>(api, 'products', { ...params, page, per_page });
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProduct(site: any, id: number): Promise<any> {
|
async getProduct(site: any, id: number): Promise<any> {
|
||||||
|
|
@ -252,9 +254,9 @@ export class WPService implements IPlatformService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 导出 WooCommerce 产品为特殊CSV(平台特性)
|
// 导出 WooCommerce 产品为特殊CSV(平台特性)
|
||||||
async exportProductsCsvSpecial(site: any, page: number = 1, pageSize: number = 100): Promise<string> {
|
async exportProductsCsvSpecial(site: any, page: number = 1, pageSize: number = 100): Promise<string> {
|
||||||
const list = await this.getProducts(site, page, pageSize);
|
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'];
|
const header = ['id','name','type','status','sku','regular_price','sale_price','stock_status','stock_quantity'];
|
||||||
const rows = (list.items || []).map((p: any) => [p.id,p.name,p.type,p.status,p.sku,p.regular_price,p.sale_price,p.stock_status,p.stock_quantity]);
|
const rows = (list.items || []).map((p: any) => [p.id,p.name,p.type,p.status,p.sku,p.regular_price,p.sale_price,p.stock_status,p.stock_quantity]);
|
||||||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
// 从 unified 到 数据库需要有个转换流程
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
// 文件转换
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { UnifiedOrderDTO } from "../dto/site-api.dto";
|
||||||
|
|
||||||
|
export class ShipmentAdapter {
|
||||||
|
// 用于导出物流需要的数据
|
||||||
|
mapFromOrder(order: UnifiedOrderDTO): any {
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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响应
|
|
||||||
Loading…
Reference in New Issue