forked from yoone/API
Compare commits
6 Commits
f7335d9717
...
c8236070b0
| Author | SHA1 | Date |
|---|---|---|
|
|
c8236070b0 | |
|
|
f5e4605cce | |
|
|
408ec8f424 | |
|
|
7d1671a513 | |
|
|
fbbb86ae37 | |
|
|
56deb447b3 |
|
|
@ -21,13 +21,16 @@ import {
|
||||||
CreateReviewDTO,
|
CreateReviewDTO,
|
||||||
CreateVariationDTO,
|
CreateVariationDTO,
|
||||||
UpdateReviewDTO,
|
UpdateReviewDTO,
|
||||||
|
OrderPaymentStatus,
|
||||||
} from '../dto/site-api.dto';
|
} from '../dto/site-api.dto';
|
||||||
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, ShopyyGetAllOrdersParams } from '../dto/api.dto';
|
import { UnifiedPaginationDTO, UnifiedSearchParamsDTO, ShopyyGetAllOrdersParams } from '../dto/api.dto';
|
||||||
import {
|
import {
|
||||||
ShopyyAllProductQuery,
|
ShopyyAllProductQuery,
|
||||||
ShopyyCustomer,
|
ShopyyCustomer,
|
||||||
ShopyyOrder,
|
ShopyyOrder,
|
||||||
|
ShopyyOrderCreateParams,
|
||||||
ShopyyOrderQuery,
|
ShopyyOrderQuery,
|
||||||
|
ShopyyOrderUpdateParams,
|
||||||
ShopyyProduct,
|
ShopyyProduct,
|
||||||
ShopyyProductQuery,
|
ShopyyProductQuery,
|
||||||
ShopyyVariant,
|
ShopyyVariant,
|
||||||
|
|
@ -39,15 +42,15 @@ import {
|
||||||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||||||
import dayjs = require('dayjs');
|
import dayjs = require('dayjs');
|
||||||
export class ShopyyAdapter implements ISiteAdapter {
|
export class ShopyyAdapter implements ISiteAdapter {
|
||||||
shopyyFinancialStatusMap= {
|
shopyyFinancialStatusMap = {
|
||||||
'200': '待支付',
|
'200': '待支付',
|
||||||
'210': "支付中",
|
'210': "支付中",
|
||||||
'220':"部分支付",
|
'220': "部分支付",
|
||||||
'230':"已支付",
|
'230': "已支付",
|
||||||
'240':"支付失败",
|
'240': "支付失败",
|
||||||
'250':"部分退款",
|
'250': "部分退款",
|
||||||
'260':"已退款",
|
'260': "已退款",
|
||||||
'290':"已取消",
|
'290': "已取消",
|
||||||
}
|
}
|
||||||
constructor(private site: any, private shopyyService: ShopyyService) {
|
constructor(private site: any, private shopyyService: ShopyyService) {
|
||||||
this.mapPlatformToUnifiedCustomer = this.mapPlatformToUnifiedCustomer.bind(this);
|
this.mapPlatformToUnifiedCustomer = this.mapPlatformToUnifiedCustomer.bind(this);
|
||||||
|
|
@ -124,8 +127,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCustomer(where: {id?: string | number,email?: string,phone?: string}): Promise<UnifiedCustomerDTO> {
|
async getCustomer(where: { id?: string | number, email?: string, phone?: string }): Promise<UnifiedCustomerDTO> {
|
||||||
if(!where.id && !where.email && !where.phone){
|
if (!where.id && !where.email && !where.phone) {
|
||||||
throw new Error('必须传入 id 或 email 或 phone')
|
throw new Error('必须传入 id 或 email 或 phone')
|
||||||
}
|
}
|
||||||
const customer = await this.shopyyService.getCustomer(this.site, where.id);
|
const customer = await this.shopyyService.getCustomer(this.site, where.id);
|
||||||
|
|
@ -154,12 +157,12 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return this.mapPlatformToUnifiedCustomer(createdCustomer);
|
return this.mapPlatformToUnifiedCustomer(createdCustomer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCustomer(where: {id: string | number}, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
async updateCustomer(where: { id: string | number }, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||||||
const updatedCustomer = await this.shopyyService.updateCustomer(this.site, where.id, data);
|
const updatedCustomer = await this.shopyyService.updateCustomer(this.site, where.id, data);
|
||||||
return this.mapPlatformToUnifiedCustomer(updatedCustomer);
|
return this.mapPlatformToUnifiedCustomer(updatedCustomer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteCustomer(where: {id: string | number}): Promise<boolean> {
|
async deleteCustomer(where: { id: string | number }): Promise<boolean> {
|
||||||
return await this.shopyyService.deleteCustomer(this.site, where.id);
|
return await this.shopyyService.deleteCustomer(this.site, where.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,12 +216,12 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return this.mapPlatformToUnifiedMedia(createdMedia);
|
return this.mapPlatformToUnifiedMedia(createdMedia);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMedia(where: {id: string | number}, data: any): Promise<UnifiedMediaDTO> {
|
async updateMedia(where: { id: string | number }, data: any): Promise<UnifiedMediaDTO> {
|
||||||
const updatedMedia = await this.shopyyService.updateMedia(this.site, where.id, data);
|
const updatedMedia = await this.shopyyService.updateMedia(this.site, where.id, data);
|
||||||
return this.mapPlatformToUnifiedMedia(updatedMedia);
|
return this.mapPlatformToUnifiedMedia(updatedMedia);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteMedia(where: {id: string | number}): Promise<boolean> {
|
async deleteMedia(where: { id: string | number }): Promise<boolean> {
|
||||||
return await this.shopyyService.deleteMedia(this.site, where.id);
|
return await this.shopyyService.deleteMedia(this.site, where.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,10 +232,10 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
// ========== 订单映射方法 ==========
|
// ========== 订单映射方法 ==========
|
||||||
mapPlatformToUnifiedOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
mapPlatformToUnifiedOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
||||||
// console.log(item)
|
// console.log(item)
|
||||||
if(!item) throw new Error('订单数据不能为空')
|
if (!item) throw new Error('订单数据不能为空')
|
||||||
// 提取账单和送货地址 如果不存在则为空对象
|
// 提取账单和送货地址 如果不存在则为空对象
|
||||||
const billing = (item).bill_address || {};
|
const billing = item.billing_address || {};
|
||||||
const shipping = (item as any).shipping_address || {};
|
const shipping = item.shipping_address || {};
|
||||||
|
|
||||||
// 构建账单地址对象
|
// 构建账单地址对象
|
||||||
const billingObj: UnifiedAddressDTO = {
|
const billingObj: UnifiedAddressDTO = {
|
||||||
|
|
@ -272,6 +275,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
state: shipping.province || item.shipping_zone || '',
|
state: shipping.province || item.shipping_zone || '',
|
||||||
postcode: shipping.zip || item.shipping_postcode || '',
|
postcode: shipping.zip || item.shipping_postcode || '',
|
||||||
method_title: item.payment_method || '',
|
method_title: item.payment_method || '',
|
||||||
|
phone: shipping.phone || (item as any).telephone || '',
|
||||||
country:
|
country:
|
||||||
shipping.country_name ||
|
shipping.country_name ||
|
||||||
shipping.country_code ||
|
shipping.country_code ||
|
||||||
|
|
@ -310,14 +314,14 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
};
|
};
|
||||||
|
|
||||||
const lineItems: UnifiedOrderLineItemDTO[] = (item.products || []).map(
|
const lineItems: UnifiedOrderLineItemDTO[] = (item.products || []).map(
|
||||||
(p: any) => ({
|
(product) => ({
|
||||||
id: p.id,
|
id: product.id,
|
||||||
name: p.product_title || p.name,
|
name:product.sku_value?.[0]?.value || product.product_title || product.name,
|
||||||
product_id: p.product_id,
|
product_id: product.product_id,
|
||||||
quantity: p.quantity,
|
quantity: product.quantity,
|
||||||
total: String(p.price ?? ''),
|
total: String(product.price ?? ''),
|
||||||
sku: p.sku_code || '',
|
sku: product.sku || product.sku_code || '',
|
||||||
price: String(p.price ?? ''),
|
price: String(product.price ?? ''),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// 货币符号
|
// 货币符号
|
||||||
|
|
@ -340,7 +344,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
const status = this.shopyyOrderStatusMap[item.status ?? item.order_status] || OrderStatus.PENDING;
|
const status = this.shopyyOrderStatusMap[item.status ?? item.order_status] || OrderStatus.PENDING;
|
||||||
const finalcial_status = this.shopyyFinancialStatusMap[item.financial_status]
|
const finalcial_status = this.shopyyFinancialStatusMap[item.financial_status]
|
||||||
// 发货状态
|
// 发货状态
|
||||||
const fulfillment_status = this.shopyyFulfillmentStatusMap[item.fulfillment_status];
|
const fulfillment_status = this.fulfillmentStatusMap[item.fulfillment_status];
|
||||||
return {
|
return {
|
||||||
id: item.id || item.order_id,
|
id: item.id || item.order_id,
|
||||||
number: item.order_number || item.order_sn,
|
number: item.order_number || item.order_sn,
|
||||||
|
|
@ -403,11 +407,11 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): any {
|
mapCreateOrderParams(data: Partial<UnifiedOrderDTO>): ShopyyOrderCreateParams {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): any {
|
mapUpdateOrderParams(data: Partial<UnifiedOrderDTO>): ShopyyOrderUpdateParams {
|
||||||
// 构建 ShopYY 订单更新参数(仅包含传入的字段)
|
// 构建 ShopYY 订单更新参数(仅包含传入的字段)
|
||||||
const params: any = {};
|
const params: any = {};
|
||||||
|
|
||||||
|
|
@ -537,9 +541,17 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrder(where: {id: string | number}): Promise<UnifiedOrderDTO> {
|
async getOrder(where: { id: string | number }): Promise<UnifiedOrderDTO> {
|
||||||
const data = await this.shopyyService.getOrder(this.site.id, String(where.id));
|
const data = await this.getOrders({
|
||||||
return this.mapPlatformToUnifiedOrder(data);
|
where: {
|
||||||
|
id: where.id,
|
||||||
|
},
|
||||||
|
page: 1,
|
||||||
|
per_page: 1,
|
||||||
|
})
|
||||||
|
return data.items[0] || null
|
||||||
|
// const data = await this.shopyyService.getOrder(this.site.id, String(where.id));
|
||||||
|
// return this.mapPlatformToUnifiedOrder(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrders(
|
async getOrders(
|
||||||
|
|
@ -562,14 +574,14 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
mapGetAllOrdersParams(params: UnifiedSearchParamsDTO) :ShopyyGetAllOrdersParams{
|
mapGetAllOrdersParams(params: UnifiedSearchParamsDTO) :ShopyyGetAllOrdersParams{
|
||||||
|
|
||||||
const pay_at_min = dayjs(params.after || '').valueOf().toString();
|
const pay_at_min = dayjs(params.after || '').unix().toString();
|
||||||
const pay_at_max = dayjs(params.before || '').valueOf().toString();
|
const pay_at_max = dayjs(params.before || '').unix().toString();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
page: params.page || 1,
|
per_page: params.per_page || 100,
|
||||||
per_page: params.per_page || 20,
|
|
||||||
pay_at_min: pay_at_min,
|
pay_at_min: pay_at_min,
|
||||||
pay_at_max: pay_at_max,
|
pay_at_max: pay_at_max,
|
||||||
|
order_field: 'pay_at',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
||||||
|
|
@ -578,7 +590,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return data.map(this.mapPlatformToUnifiedOrder.bind(this));
|
return data.map(this.mapPlatformToUnifiedOrder.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async countOrders(where: Record<string,any>): Promise<number> {
|
async countOrders(where: Record<string, any>): Promise<number> {
|
||||||
// 使用最小分页只获取总数
|
// 使用最小分页只获取总数
|
||||||
const searchParams = {
|
const searchParams = {
|
||||||
where,
|
where,
|
||||||
|
|
@ -596,13 +608,13 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return this.mapPlatformToUnifiedOrder(createdOrder);
|
return this.mapPlatformToUnifiedOrder(createdOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOrder(where: {id: string | number}, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
async updateOrder(where: { id: string | number }, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
||||||
// 使用映射方法转换参数
|
// 使用映射方法转换参数
|
||||||
const requestParams = this.mapUpdateOrderParams(data);
|
const requestParams = this.mapUpdateOrderParams(data);
|
||||||
return await this.shopyyService.updateOrder(this.site, String(where.id), requestParams);
|
return await this.shopyyService.updateOrder(this.site, String(where.id), requestParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOrder(where: {id: string | number}): Promise<boolean> {
|
async deleteOrder(where: { id: string | number }): Promise<boolean> {
|
||||||
return await this.shopyyService.deleteOrder(this.site, where.id);
|
return await this.shopyyService.deleteOrder(this.site, where.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -712,7 +724,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
name: item.name || item.title,
|
name: item.name || item.title,
|
||||||
type: String(item.product_type ?? ''),
|
type: String(item.product_type ?? ''),
|
||||||
status: mapProductStatus(item.status),
|
status: mapProductStatus(item.status),
|
||||||
sku: item.variant?.sku || '',
|
sku: item.variant?.sku || item.variant?.sku_code || '',
|
||||||
regular_price: String(item.variant?.price ?? ''),
|
regular_price: String(item.variant?.price ?? ''),
|
||||||
sale_price: String(item.special_price ?? ''),
|
sale_price: String(item.special_price ?? ''),
|
||||||
price: String(item.price ?? ''),
|
price: String(item.price ?? ''),
|
||||||
|
|
@ -741,7 +753,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
name: c.title || '',
|
name: c.title || '',
|
||||||
})),
|
})),
|
||||||
variations: item.variants?.map(this.mapPlatformToUnifiedVariation.bind(this)) || [],
|
variations: item.variants?.map(this.mapPlatformToUnifiedVariation.bind(this)) || [],
|
||||||
permalink: `${this.site.websiteUrl}/products/${item.handle}`,
|
permalink: `${this.site.websiteUrl}/products/${item.handle}`,
|
||||||
date_created:
|
date_created:
|
||||||
typeof item.created_at === 'number'
|
typeof item.created_at === 'number'
|
||||||
? new Date(item.created_at * 1000).toISOString()
|
? new Date(item.created_at * 1000).toISOString()
|
||||||
|
|
@ -877,8 +889,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProduct(where: {id?: string | number, sku?: string}): Promise<UnifiedProductDTO> {
|
async getProduct(where: { id?: string | number, sku?: string }): Promise<UnifiedProductDTO> {
|
||||||
if(!where.id && !where.sku){
|
if (!where.id && !where.sku) {
|
||||||
throw new Error('必须传入 id 或 sku')
|
throw new Error('必须传入 id 或 sku')
|
||||||
}
|
}
|
||||||
if (where.id) {
|
if (where.id) {
|
||||||
|
|
@ -914,11 +926,11 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
per_page,
|
per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
mapAllProductParams(params: UnifiedSearchParamsDTO): Partial<ShopyyAllProductQuery>{
|
mapAllProductParams(params: UnifiedSearchParamsDTO): Partial<ShopyyAllProductQuery> {
|
||||||
const mapped = {
|
const mapped = {
|
||||||
...params.where,
|
...params.where,
|
||||||
} as any
|
} as any
|
||||||
if(params.per_page){mapped.limit = params.per_page}
|
if (params.per_page) { mapped.limit = params.per_page }
|
||||||
return mapped
|
return mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -932,7 +944,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
null,
|
null,
|
||||||
requestParams
|
requestParams
|
||||||
);
|
);
|
||||||
if(response.code !==0){
|
if (response.code !== 0) {
|
||||||
throw new Error(response.msg || '获取产品列表失败')
|
throw new Error(response.msg || '获取产品列表失败')
|
||||||
}
|
}
|
||||||
const { data = [] } = response;
|
const { data = [] } = response;
|
||||||
|
|
@ -947,7 +959,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return this.mapPlatformToUnifiedProduct(res);
|
return this.mapPlatformToUnifiedProduct(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProduct(where: {id?: string | number, sku?: string}, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
async updateProduct(where: { id?: string | number, sku?: string }, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
||||||
let productId: string;
|
let productId: string;
|
||||||
if (where.id) {
|
if (where.id) {
|
||||||
productId = String(where.id);
|
productId = String(where.id);
|
||||||
|
|
@ -964,7 +976,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteProduct(where: {id?: string | number, sku?: string}): Promise<boolean> {
|
async deleteProduct(where: { id?: string | number, sku?: string }): Promise<boolean> {
|
||||||
let productId: string | number;
|
let productId: string | number;
|
||||||
if (where.id) {
|
if (where.id) {
|
||||||
productId = where.id;
|
productId = where.id;
|
||||||
|
|
@ -983,7 +995,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
// 通过sku获取产品详情的私有方法
|
// 通过sku获取产品详情的私有方法
|
||||||
private async getProductBySku(sku: string): Promise<UnifiedProductDTO> {
|
private async getProductBySku(sku: string): Promise<UnifiedProductDTO> {
|
||||||
// 使用Shopyy API的搜索功能通过sku查询产品
|
// 使用Shopyy API的搜索功能通过sku查询产品
|
||||||
const response = await this.getAllProducts({ where: {sku} });
|
const response = await this.getAllProducts({ where: { sku } });
|
||||||
console.log('getProductBySku', response)
|
console.log('getProductBySku', response)
|
||||||
const product = response?.[0]
|
const product = response?.[0]
|
||||||
if (!product) {
|
if (!product) {
|
||||||
|
|
@ -1047,12 +1059,12 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return this.mapPlatformToUnifiedReview(createdReview);
|
return this.mapPlatformToUnifiedReview(createdReview);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateReview(where: {id: string | number}, data: any): Promise<UnifiedReviewDTO> {
|
async updateReview(where: { id: string | number }, data: any): Promise<UnifiedReviewDTO> {
|
||||||
const updatedReview = await this.shopyyService.updateReview(this.site, where.id, data);
|
const updatedReview = await this.shopyyService.updateReview(this.site, where.id, data);
|
||||||
return this.mapPlatformToUnifiedReview(updatedReview);
|
return this.mapPlatformToUnifiedReview(updatedReview);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteReview(where: {id: string | number}): Promise<boolean> {
|
async deleteReview(where: { id: string | number }): Promise<boolean> {
|
||||||
return await this.shopyyService.deleteReview(this.site, where.id);
|
return await this.shopyyService.deleteReview(this.site, where.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1114,10 +1126,11 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
// ========== 产品变体映射方法 ==========
|
// ========== 产品变体映射方法 ==========
|
||||||
mapPlatformToUnifiedVariation(variant: ShopyyVariant): UnifiedProductVariationDTO {
|
mapPlatformToUnifiedVariation(variant: ShopyyVariant): UnifiedProductVariationDTO {
|
||||||
// 映射变体
|
// 映射变体
|
||||||
|
console.log('ivarianttem', variant)
|
||||||
return {
|
return {
|
||||||
id: variant.id,
|
id: variant.id,
|
||||||
name: variant.sku || '',
|
name: variant.title || '',
|
||||||
sku: variant.sku || '',
|
sku: variant.sku || variant.sku_code || '',
|
||||||
regular_price: String(variant.price ?? ''),
|
regular_price: String(variant.price ?? ''),
|
||||||
sale_price: String(variant.special_price ?? ''),
|
sale_price: String(variant.special_price ?? ''),
|
||||||
price: String(variant.price ?? ''),
|
price: String(variant.price ?? ''),
|
||||||
|
|
@ -1208,7 +1221,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWebhook(where: {id: string | number}): Promise<UnifiedWebhookDTO> {
|
async getWebhook(where: { id: string | number }): Promise<UnifiedWebhookDTO> {
|
||||||
const webhook = await this.shopyyService.getWebhook(this.site, where.id);
|
const webhook = await this.shopyyService.getWebhook(this.site, where.id);
|
||||||
return this.mapPlatformToUnifiedWebhook(webhook);
|
return this.mapPlatformToUnifiedWebhook(webhook);
|
||||||
}
|
}
|
||||||
|
|
@ -1234,12 +1247,12 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return this.mapPlatformToUnifiedWebhook(createdWebhook);
|
return this.mapPlatformToUnifiedWebhook(createdWebhook);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateWebhook(where: {id: string | number}, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
async updateWebhook(where: { id: string | number }, data: UpdateWebhookDTO): Promise<UnifiedWebhookDTO> {
|
||||||
const updatedWebhook = await this.shopyyService.updateWebhook(this.site, where.id, data);
|
const updatedWebhook = await this.shopyyService.updateWebhook(this.site, where.id, data);
|
||||||
return this.mapPlatformToUnifiedWebhook(updatedWebhook);
|
return this.mapPlatformToUnifiedWebhook(updatedWebhook);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteWebhook(where: {id: string | number}): Promise<boolean> {
|
async deleteWebhook(where: { id: string | number }): Promise<boolean> {
|
||||||
return await this.shopyyService.deleteWebhook(this.site, where.id);
|
return await this.shopyyService.deleteWebhook(this.site, where.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1289,7 +1302,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
page,
|
page,
|
||||||
limit,
|
limit,
|
||||||
}
|
}
|
||||||
if(params.orderBy){
|
if (params.orderBy) {
|
||||||
const [field, dir] = Object.entries(params.orderBy)[0];
|
const [field, dir] = Object.entries(params.orderBy)[0];
|
||||||
query.order_by = dir === 'desc' ? 'desc' : 'asc';
|
query.order_by = dir === 'desc' ? 'desc' : 'asc';
|
||||||
query.order_field = field
|
query.order_field = field
|
||||||
|
|
@ -1299,24 +1312,24 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
// 映射产品状态: publish -> 1, draft -> 0
|
// 映射产品状态: publish -> 1, draft -> 0
|
||||||
mapStatus = (status: string) => {
|
mapStatus = (status: string) => {
|
||||||
return status === 'publish' ? 1 : 0;
|
return status === 'publish' ? 1 : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 映射库存状态: instock -> 1, outofstock -> 0
|
// 映射库存状态: instock -> 1, outofstock -> 0
|
||||||
mapStockStatus = (stockStatus: string) => {
|
mapStockStatus = (stockStatus: string) => {
|
||||||
return stockStatus === 'instock' ? 1 : 0;
|
return stockStatus === 'instock' ? 1 : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
shopyyOrderStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
shopyyOrderStatusMap = {//订单状态 100 未完成;110 待处理;180 已完成(确认收货); 190 取消;
|
||||||
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending
|
[100]: OrderStatus.PENDING, // 100 未完成 转为 pending
|
||||||
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
|
[110]: OrderStatus.PROCESSING, // 110 待处理 转为 processing
|
||||||
// 已发货
|
// 已发货
|
||||||
|
|
||||||
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
|
[180]: OrderStatus.COMPLETED, // 180 已完成(确认收货) 转为 completed
|
||||||
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
|
[190]: OrderStatus.CANCEL // 190 取消 转为 cancelled
|
||||||
}
|
}
|
||||||
|
// 物流状态 300 未发货;310 部分发货;320 已发货;330(确认收货)
|
||||||
shopyyFulfillmentStatusMap = {
|
fulfillmentStatusMap = {
|
||||||
// 未发货
|
// 未发货
|
||||||
'300': OrderFulfillmentStatus.PENDING,
|
'300': OrderFulfillmentStatus.PENDING,
|
||||||
// 部分发货
|
// 部分发货
|
||||||
|
|
@ -1327,4 +1340,23 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
'330': OrderFulfillmentStatus.CANCELLED,
|
'330': OrderFulfillmentStatus.CANCELLED,
|
||||||
// 确认发货
|
// 确认发货
|
||||||
}
|
}
|
||||||
|
// 支付状态 200 待支付;210 支付中;220 部分支付;230 已支付;240 支付失败;250 部分退款;260 已退款 ;290 已取消;
|
||||||
|
financialStatusMap = {
|
||||||
|
// 待支付
|
||||||
|
'200': OrderPaymentStatus.PENDING,
|
||||||
|
// 支付中
|
||||||
|
'210': OrderPaymentStatus.PAYING,
|
||||||
|
// 部分支付
|
||||||
|
'220': OrderPaymentStatus.PARTIALLY_PAID,
|
||||||
|
// 已支付
|
||||||
|
'230': OrderPaymentStatus.PAID,
|
||||||
|
// 支付失败
|
||||||
|
'240': OrderPaymentStatus.FAILED,
|
||||||
|
// 部分退款
|
||||||
|
'250': OrderPaymentStatus.PARTIALLY_REFUNDED,
|
||||||
|
// 已退款
|
||||||
|
'260': OrderPaymentStatus.REFUNDED,
|
||||||
|
// 已取消
|
||||||
|
'290': OrderPaymentStatus.CANCELLED,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -12,14 +12,15 @@ export default {
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
typeorm: {
|
typeorm: {
|
||||||
dataSource: {
|
dataSource: {
|
||||||
default: {
|
default: {
|
||||||
host: 'localhost',
|
host: '13.212.62.127',
|
||||||
port: "23306",
|
port: "3306",
|
||||||
username: 'root',
|
username: 'root',
|
||||||
password: '12345678',
|
password: 'Yoone!@.2025',
|
||||||
database: 'inventory_v2',
|
database: 'inventory_v2',
|
||||||
|
synchronize: true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,7 @@ export class MainConfiguration {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.logger.info('正在检查数据库是否存在...');
|
this.logger.info(`正在检查数据库是否存在...`+ JSON.stringify(typeormConfig));
|
||||||
|
|
||||||
// 初始化临时数据源
|
// 初始化临时数据源
|
||||||
await tempDataSource.initialize();
|
await tempDataSource.initialize();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export class DictController {
|
||||||
// 从上传的文件列表中获取第一个文件
|
// 从上传的文件列表中获取第一个文件
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
// 调用服务层方法处理XLSX文件
|
// 调用服务层方法处理XLSX文件
|
||||||
const result = await this.dictService.importDictsFromXLSX(file.data);
|
const result = await this.dictService.importDictsFromTable(file.data);
|
||||||
// 返回导入结果
|
// 返回导入结果
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ export class ProductController {
|
||||||
const file = files?.[0];
|
const file = files?.[0];
|
||||||
if (!file) return errorResponse('未接收到上传文件');
|
if (!file) return errorResponse('未接收到上传文件');
|
||||||
|
|
||||||
const result = await this.productService.importProductsCSV(file);
|
const result = await this.productService.importProductsFromTable(file);
|
||||||
return successResponse(result);
|
return successResponse(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
|
||||||
|
|
@ -56,10 +56,7 @@ export class UnifiedSearchParamsDTO<Where=Record<string, any>> {
|
||||||
* Shopyy获取所有订单参数DTO
|
* Shopyy获取所有订单参数DTO
|
||||||
*/
|
*/
|
||||||
export class ShopyyGetAllOrdersParams {
|
export class ShopyyGetAllOrdersParams {
|
||||||
@ApiProperty({ description: '页码', example: 1, required: false })
|
@ApiProperty({ description: '每页数量', example: 100, required: false })
|
||||||
page?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '每页数量', example: 20, required: false })
|
|
||||||
per_page?: number;
|
per_page?: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '支付时间范围开始', example: '2023-01-01T00:00:00Z', required: false })
|
@ApiProperty({ description: '支付时间范围开始', example: '2023-01-01T00:00:00Z', required: false })
|
||||||
|
|
@ -67,6 +64,9 @@ export class ShopyyGetAllOrdersParams {
|
||||||
|
|
||||||
@ApiProperty({ description: '支付时间范围结束', example: '2023-01-01T23:59:59Z', required: false })
|
@ApiProperty({ description: '支付时间范围结束', example: '2023-01-01T23:59:59Z', required: false })
|
||||||
pay_at_max?: string;
|
pay_at_max?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序字段', example: 'id', required: false })
|
||||||
|
order_field?: string;//排序字段(默认id) id=订单ID updated_at=最后更新时间 pay_at=支付时间
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,10 @@ export class CreateProductDTO {
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
promotionPrice?: number;
|
promotionPrice?: number;
|
||||||
|
|
||||||
|
// 产品图片URL
|
||||||
|
@ApiProperty({ description: '产品图片URL', example: 'https://example.com/image.jpg', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
image?: string;
|
||||||
|
|
||||||
// 商品类型(默认 single; bundle 需手动设置组成)
|
// 商品类型(默认 single; bundle 需手动设置组成)
|
||||||
@ApiProperty({ description: '商品类型', enum: ['single', 'bundle'], default: 'single', required: false })
|
@ApiProperty({ description: '商品类型', enum: ['single', 'bundle'], default: 'single', required: false })
|
||||||
|
|
@ -153,7 +156,10 @@ export class UpdateProductDTO {
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
promotionPrice?: number;
|
promotionPrice?: number;
|
||||||
|
|
||||||
|
// 产品图片URL
|
||||||
|
@ApiProperty({ description: '产品图片URL', example: 'https://example.com/image.jpg', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
image?: string;
|
||||||
|
|
||||||
// 属性更新(可选, 支持增量替换指定字典的属性项)
|
// 属性更新(可选, 支持增量替换指定字典的属性项)
|
||||||
@ApiProperty({ description: '属性列表', type: 'array', required: false })
|
@ApiProperty({ description: '属性列表', type: 'array', required: false })
|
||||||
|
|
@ -228,6 +234,10 @@ export class BatchUpdateProductDTO {
|
||||||
@Rule(RuleType.number().optional())
|
@Rule(RuleType.number().optional())
|
||||||
promotionPrice?: number;
|
promotionPrice?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品图片URL', example: 'https://example.com/image.jpg', required: false })
|
||||||
|
@Rule(RuleType.string().optional())
|
||||||
|
image?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '属性列表', type: 'array', required: false })
|
@ApiProperty({ description: '属性列表', type: 'array', required: false })
|
||||||
@Rule(RuleType.array().optional())
|
@Rule(RuleType.array().optional())
|
||||||
attributes?: AttributeInputDTO[];
|
attributes?: AttributeInputDTO[];
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,6 +18,24 @@ export enum OrderFulfillmentStatus {
|
||||||
// 确认发货
|
// 确认发货
|
||||||
CONFIRMED,
|
CONFIRMED,
|
||||||
}
|
}
|
||||||
|
export enum OrderPaymentStatus {
|
||||||
|
// 待支付
|
||||||
|
PENDING,
|
||||||
|
// 支付中
|
||||||
|
PAYING,
|
||||||
|
// 部分支付
|
||||||
|
PARTIALLY_PAID,
|
||||||
|
// 已支付
|
||||||
|
PAID,
|
||||||
|
// 支付失败
|
||||||
|
FAILED,
|
||||||
|
// 部分退款
|
||||||
|
PARTIALLY_REFUNDED,
|
||||||
|
// 已退款
|
||||||
|
REFUNDED,
|
||||||
|
// 已取消
|
||||||
|
CANCELLED,
|
||||||
|
}
|
||||||
//
|
//
|
||||||
export class UnifiedProductWhere {
|
export class UnifiedProductWhere {
|
||||||
sku?: string;
|
sku?: string;
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,11 @@ export class OrderSale {
|
||||||
@Expose()
|
@Expose()
|
||||||
isPackage: boolean;
|
isPackage: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '商品品类', type: 'string',nullable: true})
|
||||||
|
@Expose()
|
||||||
|
@Column({ nullable: true })
|
||||||
|
category?: string;
|
||||||
|
// TODO 这个其实还是直接保存 product 比较好
|
||||||
@ApiProperty({ description: '品牌', type: 'string',nullable: true})
|
@ApiProperty({ description: '品牌', type: 'string',nullable: true})
|
||||||
@Expose()
|
@Expose()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@ export class Product {
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: '图片URL', description: '产品图片URL' })
|
||||||
|
@Column({ nullable: true })
|
||||||
|
image?: string;
|
||||||
// 商品价格
|
// 商品价格
|
||||||
@ApiProperty({ description: '价格', example: 99.99 })
|
@ApiProperty({ description: '价格', example: 99.99 })
|
||||||
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
import { Site } from './site.entity';
|
||||||
|
import { Product } from './product.entity';
|
||||||
|
|
||||||
|
@Entity('site_product')
|
||||||
|
export class SiteProduct {
|
||||||
|
@ApiProperty({
|
||||||
|
example: '12345',
|
||||||
|
description: '站点商品ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@Column({ primary: true })
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '站点ID' })
|
||||||
|
@Column()
|
||||||
|
siteId: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '商品ID' })
|
||||||
|
@Column({ nullable: true })
|
||||||
|
productId: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'sku'})
|
||||||
|
@Column()
|
||||||
|
sku: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '类型' })
|
||||||
|
@Column({ length: 16, default: 'single' })
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '产品名称',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@Column()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '产品图片' })
|
||||||
|
@Column({ default: '' })
|
||||||
|
image: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '父商品ID', example: '12345' })
|
||||||
|
@Column({ nullable: true })
|
||||||
|
parentId: string;
|
||||||
|
|
||||||
|
// 站点关联
|
||||||
|
@ManyToOne(() => Site, site => site.id)
|
||||||
|
@JoinColumn({ name: 'siteId' })
|
||||||
|
site: Site;
|
||||||
|
|
||||||
|
// 商品关联
|
||||||
|
@ManyToOne(() => Product, product => product.id)
|
||||||
|
@JoinColumn({ name: 'productId' })
|
||||||
|
product: Product;
|
||||||
|
|
||||||
|
// 父商品关联
|
||||||
|
@ManyToOne(() => SiteProduct, siteProduct => siteProduct.id)
|
||||||
|
@JoinColumn({ name: 'parentId' })
|
||||||
|
parent: SiteProduct;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2022-12-12 11:11:11',
|
||||||
|
description: '创建时间',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2022-12-12 11:11:11',
|
||||||
|
description: '更新时间',
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
@ -50,7 +50,7 @@ export class DictService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从XLSX文件导入字典
|
// 从XLSX文件导入字典
|
||||||
async importDictsFromXLSX(bufferOrPath: Buffer | string) {
|
async importDictsFromTable(bufferOrPath: Buffer | string) {
|
||||||
// 判断传入的是 Buffer 还是文件路径字符串
|
// 判断传入的是 Buffer 还是文件路径字符串
|
||||||
let buffer: Buffer;
|
let buffer: Buffer;
|
||||||
if (typeof bufferOrPath === 'string') {
|
if (typeof bufferOrPath === 'string') {
|
||||||
|
|
@ -93,7 +93,7 @@ export class DictService {
|
||||||
|
|
||||||
// 从XLSX文件导入字典项
|
// 从XLSX文件导入字典项
|
||||||
async importDictItemsFromXLSX(bufferOrPath: Buffer | string, dictId: number): Promise<BatchOperationResultDTO> {
|
async importDictItemsFromXLSX(bufferOrPath: Buffer | string, dictId: number): Promise<BatchOperationResultDTO> {
|
||||||
if(!dictId){
|
if (!dictId) {
|
||||||
throw new Error("引入失败, 请输入字典 ID")
|
throw new Error("引入失败, 请输入字典 ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,10 +216,10 @@ export class DictService {
|
||||||
|
|
||||||
// 如果提供了 dictId,则只返回该字典下的项
|
// 如果提供了 dictId,则只返回该字典下的项
|
||||||
if (params.dictId) {
|
if (params.dictId) {
|
||||||
return this.dictItemModel.find({ where });
|
return this.dictItemModel.find({ where, relations: ['dict'] });
|
||||||
}
|
}
|
||||||
// 否则,返回所有字典项
|
// 否则,返回所有字典项
|
||||||
return this.dictItemModel.find();
|
return this.dictItemModel.find({ relations: ['dict'] });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建新字典项
|
// 创建新字典项
|
||||||
|
|
|
||||||
|
|
@ -423,7 +423,7 @@ export class FreightwavesService {
|
||||||
// 设置必要的配置
|
// 设置必要的配置
|
||||||
this.setConfig({
|
this.setConfig({
|
||||||
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
|
appSecret: 'gELCHguGmdTLo!zfihfM91hae8G@9Sz23Mh6pHrt',
|
||||||
apiBaseUrl: 'https://console-mock.apipost.cn/mock/0',
|
apiBaseUrl: 'https://tms.freightwaves.ca',
|
||||||
partner: '25072621035200000060'
|
partner: '25072621035200000060'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Inject, Provide } from '@midwayjs/core';
|
import { Inject, Provide } from '@midwayjs/core';
|
||||||
import { parse } from 'csv-parse';
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
import * as xlsx from 'xlsx';
|
||||||
import { In, Like, Not, Repository } from 'typeorm';
|
import { In, Like, Not, Repository } from 'typeorm';
|
||||||
import { Product } from '../entity/product.entity';
|
import { Product } from '../entity/product.entity';
|
||||||
import { PaginationParams } from '../interface';
|
import { PaginationParams } from '../interface';
|
||||||
|
|
@ -637,7 +637,7 @@ export class ProductService {
|
||||||
if (sku) {
|
if (sku) {
|
||||||
product.sku = sku;
|
product.sku = sku;
|
||||||
} else {
|
} else {
|
||||||
product.sku = await this.templateService.render('product.sku', {product});
|
product.sku = await this.templateService.render('product.sku', { product });
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedProduct = await this.productModel.save(product);
|
const savedProduct = await this.productModel.save(product);
|
||||||
|
|
@ -774,7 +774,7 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 简单字段,直接批量更新以提高性能
|
// 简单字段,直接批量更新以提高性能
|
||||||
// UpdateProductDTO 里的简单字段: name, nameCn, description, price, promotionPrice, siteSkus
|
// UpdateProductDTO 里的简单字段: name, nameCn, description, shortDescription, price, promotionPrice, image, siteSkus
|
||||||
|
|
||||||
const simpleUpdate: any = {};
|
const simpleUpdate: any = {};
|
||||||
if (updateData.name !== undefined) simpleUpdate.name = updateData.name;
|
if (updateData.name !== undefined) simpleUpdate.name = updateData.name;
|
||||||
|
|
@ -783,6 +783,7 @@ export class ProductService {
|
||||||
if (updateData.shortDescription !== undefined) simpleUpdate.shortDescription = updateData.shortDescription;
|
if (updateData.shortDescription !== undefined) simpleUpdate.shortDescription = updateData.shortDescription;
|
||||||
if (updateData.price !== undefined) simpleUpdate.price = updateData.price;
|
if (updateData.price !== undefined) simpleUpdate.price = updateData.price;
|
||||||
if (updateData.promotionPrice !== undefined) simpleUpdate.promotionPrice = updateData.promotionPrice;
|
if (updateData.promotionPrice !== undefined) simpleUpdate.promotionPrice = updateData.promotionPrice;
|
||||||
|
if (updateData.image !== undefined) simpleUpdate.image = updateData.image;
|
||||||
if (updateData.siteSkus !== undefined) simpleUpdate.siteSkus = updateData.siteSkus;
|
if (updateData.siteSkus !== undefined) simpleUpdate.siteSkus = updateData.siteSkus;
|
||||||
|
|
||||||
if (Object.keys(simpleUpdate).length > 0) {
|
if (Object.keys(simpleUpdate).length > 0) {
|
||||||
|
|
@ -1468,9 +1469,9 @@ export class ProductService {
|
||||||
type: val(rec.type),
|
type: val(rec.type),
|
||||||
siteSkus: rec.siteSkus
|
siteSkus: rec.siteSkus
|
||||||
? String(rec.siteSkus)
|
? String(rec.siteSkus)
|
||||||
.split(/[;,]/) // 支持英文分号或英文逗号分隔
|
.split(/[;,]/) // 支持英文分号或英文逗号分隔
|
||||||
.map(s => s.trim())
|
.map(s => s.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
: undefined,
|
: undefined,
|
||||||
category, // 添加分类字段
|
category, // 添加分类字段
|
||||||
|
|
||||||
|
|
@ -1536,10 +1537,10 @@ export class ProductService {
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
getAttributesObject(attributes:DictItem[]){
|
getAttributesObject(attributes: DictItem[]) {
|
||||||
if(!attributes) return {}
|
if (!attributes) return {}
|
||||||
const obj:any = {}
|
const obj: any = {}
|
||||||
attributes.forEach(attr=>{
|
attributes.forEach(attr => {
|
||||||
obj[attr.dict.name] = attr
|
obj[attr.dict.name] = attr
|
||||||
})
|
})
|
||||||
return obj
|
return obj
|
||||||
|
|
@ -1663,59 +1664,60 @@ export class ProductService {
|
||||||
rows.push(rowData.join(','));
|
rows.push(rowData.join(','));
|
||||||
}
|
}
|
||||||
|
|
||||||
return rows.join('\n');
|
// 添加UTF-8 BOM以确保中文在Excel中正确显示
|
||||||
|
return '\ufeff' + rows.join('\n');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
async getRecordsFromTable(file: any) {
|
||||||
// 从 CSV 导入产品;存在则更新,不存在则创建
|
// 解析文件(使用 xlsx 包自动识别文件类型并解析)
|
||||||
async importProductsCSV(file: any): Promise<BatchOperationResult> {
|
|
||||||
let buffer: Buffer;
|
|
||||||
if (Buffer.isBuffer(file)) {
|
|
||||||
buffer = file;
|
|
||||||
} else if (file?.data) {
|
|
||||||
if (typeof file.data === 'string') {
|
|
||||||
buffer = fs.readFileSync(file.data);
|
|
||||||
} else {
|
|
||||||
buffer = file.data;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('无效的文件输入');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析 CSV(使用 csv-parse/sync 按表头解析)
|
|
||||||
let records: any[] = [];
|
|
||||||
try {
|
try {
|
||||||
records = await new Promise((resolve, reject) => {
|
let buffer: Buffer;
|
||||||
parse(buffer, {
|
|
||||||
columns: true,
|
// 处理文件输入,获取 buffer
|
||||||
skip_empty_lines: true,
|
if (Buffer.isBuffer(file)) {
|
||||||
trim: true,
|
buffer = file;
|
||||||
bom: true,
|
}
|
||||||
}, (err, data) => {
|
else if (file?.data) {
|
||||||
if (err) {
|
if (typeof file.data === 'string') {
|
||||||
reject(err);
|
buffer = fs.readFileSync(file.data);
|
||||||
} else {
|
} else {
|
||||||
resolve(data);
|
buffer = file.data;
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
})
|
throw new Error('无效的文件输入');
|
||||||
|
}
|
||||||
|
|
||||||
|
let records: any[] = []
|
||||||
|
// xlsx 包会自动根据文件内容识别文件类型(CSV 或 XLSX)
|
||||||
|
// 添加codepage: 65001以确保正确处理UTF-8编码的中文
|
||||||
|
const workbook = xlsx.read(buffer, { type: 'buffer', codepage: 65001 });
|
||||||
|
// 获取第一个工作表
|
||||||
|
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||||
|
// 将工作表转换为 JSON 数组
|
||||||
|
records = xlsx.utils.sheet_to_json(worksheet);
|
||||||
|
|
||||||
console.log('Parsed records count:', records.length);
|
console.log('Parsed records count:', records.length);
|
||||||
if (records.length > 0) {
|
if (records.length > 0) {
|
||||||
console.log('First record keys:', Object.keys(records[0]));
|
console.log('First record keys:', Object.keys(records[0]));
|
||||||
}
|
}
|
||||||
|
return records;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new Error(`CSV 解析失败:${e?.message || e}`)
|
throw new Error(`文件解析失败:${e?.message || e}`);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 CSV 导入产品;存在则更新,不存在则创建
|
||||||
|
async importProductsFromTable(file: any): Promise<BatchOperationResult> {
|
||||||
let created = 0;
|
let created = 0;
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
const errors: BatchErrorItem[] = [];
|
const errors: BatchErrorItem[] = [];
|
||||||
|
const records = await this.getRecordsFromTable(file);
|
||||||
// 逐条处理记录
|
// 逐条处理记录
|
||||||
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({ identifier: data.sku, error: '缺少 SKU 的记录已跳过'});
|
errors.push({ identifier: data.sku, error: '缺少 SKU 的记录已跳过' });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { sku } = data;
|
const { sku } = data;
|
||||||
|
|
@ -1735,7 +1737,7 @@ export class ProductService {
|
||||||
updated += 1;
|
updated += 1;
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
errors.push({ identifier: '' + rec.sku, error: `产品${rec?.sku}导入失败:${e?.message || String(e)}`});
|
errors.push({ identifier: '' + rec.sku, error: `产品${rec?.sku}导入失败:${e?.message || String(e)}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1867,7 +1869,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?.response?.data?.message??error.message}`);
|
throw new Error(`同步产品到站点失败: ${error?.response?.data?.message ?? error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1915,18 +1917,18 @@ export class ProductService {
|
||||||
* @returns 同步后的本地产品
|
* @returns 同步后的本地产品
|
||||||
*/
|
*/
|
||||||
async syncProductFromSite(siteId: number, siteProductId: string | number, sku: string): Promise<any> {
|
async syncProductFromSite(siteId: number, siteProductId: string | number, sku: string): Promise<any> {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const siteProduct = await adapter.getProduct({ id: siteProductId });
|
const siteProduct = await adapter.getProduct({ id: siteProductId });
|
||||||
// 从站点获取产品信息
|
// 从站点获取产品信息
|
||||||
if (!siteProduct) {
|
if (!siteProduct) {
|
||||||
throw new Error(`站点产品 ID ${siteProductId} 不存在`);
|
throw new Error(`站点产品 ID ${siteProductId} 不存在`);
|
||||||
}
|
}
|
||||||
// 将站点产品转换为本地产品格式
|
// 将站点产品转换为本地产品格式
|
||||||
const productData = await this.mapUnifiedToLocalProduct(siteProduct);
|
const productData = await this.mapUnifiedToLocalProduct(siteProduct);
|
||||||
return await this.upsertProduct({sku}, productData);
|
return await this.upsertProduct({ sku }, productData);
|
||||||
}
|
}
|
||||||
async upsertProduct(where: Partial<Pick<Product,'id'| 'sku'>>, productData: any) {
|
async upsertProduct(where: Partial<Pick<Product, 'id' | 'sku'>>, productData: any) {
|
||||||
const existingProduct = await this.productModel.findOne({ where: where});
|
const existingProduct = await this.productModel.findOne({ where: where });
|
||||||
if (existingProduct) {
|
if (existingProduct) {
|
||||||
// 更新现有产品
|
// 更新现有产品
|
||||||
const updateData: UpdateProductDTO = productData;
|
const updateData: UpdateProductDTO = productData;
|
||||||
|
|
@ -1944,7 +1946,7 @@ export class ProductService {
|
||||||
* @param siteProductIds 站点产品ID数组
|
* @param siteProductIds 站点产品ID数组
|
||||||
* @returns 批量同步结果
|
* @returns 批量同步结果
|
||||||
*/
|
*/
|
||||||
async batchSyncFromSite(siteId: number, data: Array<{siteProductId:string, sku: string}>): 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: []
|
||||||
|
|
@ -2030,13 +2032,13 @@ export class ProductService {
|
||||||
* @param localProduct 本地产品对象
|
* @param localProduct 本地产品对象
|
||||||
* @returns 统一产品对象
|
* @returns 统一产品对象
|
||||||
*/
|
*/
|
||||||
private async mapLocalToUnifiedProduct(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})) || [];
|
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.name,
|
name: localProduct.name,
|
||||||
type: localProduct.type === 'single'? 'simple' : 'bundle', // 默认类型,可以根据实际需要调整
|
type: localProduct.type === 'single' ? 'simple' : 'bundle', // 默认类型,可以根据实际需要调整
|
||||||
status: 'publish', // 默认状态,可以根据实际需要调整
|
status: 'publish', // 默认状态,可以根据实际需要调整
|
||||||
sku: siteSku || await this.templateService.render('site.product.sku', { product: localProduct, 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),
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ import * as FormData from 'form-data';
|
||||||
import { SiteService } from './site.service';
|
import { SiteService } from './site.service';
|
||||||
import { Site } from '../entity/site.entity';
|
import { Site } from '../entity/site.entity';
|
||||||
import { UnifiedReviewDTO } from '../dto/site-api.dto';
|
import { UnifiedReviewDTO } from '../dto/site-api.dto';
|
||||||
import { ShopyyReview } from '../dto/shopyy.dto';
|
import { ShopyyGetOneOrderResult, ShopyyReview } from '../dto/shopyy.dto';
|
||||||
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
import { BatchOperationDTO, BatchOperationResultDTO } from '../dto/batch.dto';
|
||||||
import { UnifiedSearchParamsDTO } from '../dto/api.dto';
|
import { UnifiedSearchParamsDTO,ShopyyGetAllOrdersParams } from '../dto/api.dto';
|
||||||
/**
|
/**
|
||||||
* ShopYY平台服务实现
|
* ShopYY平台服务实现
|
||||||
*/
|
*/
|
||||||
|
|
@ -288,7 +288,7 @@ export class ShopyyService {
|
||||||
* @param pageSize 每页数量
|
* @param pageSize 每页数量
|
||||||
* @returns 分页订单列表
|
* @returns 分页订单列表
|
||||||
*/
|
*/
|
||||||
async getOrders(site: any | number, page: number = 1, pageSize: number = 100, params: UnifiedSearchParamsDTO = {}): Promise<any> {
|
async getOrders(site: any | number, page: number = 1, pageSize: number = 3000, params: ShopyyGetAllOrdersParams = {}): Promise<any> {
|
||||||
// 如果传入的是站点ID,则获取站点配置
|
// 如果传入的是站点ID,则获取站点配置
|
||||||
const siteConfig = typeof site === 'number' ? await this.siteService.get(site) : site;
|
const siteConfig = typeof site === 'number' ? await this.siteService.get(site) : site;
|
||||||
|
|
||||||
|
|
@ -308,8 +308,8 @@ export class ShopyyService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllOrders(site: any | number, params: Record<string, any> = {}, maxPages: number = 10, concurrencyLimit: number = 100): Promise<any> {
|
async getAllOrders(site: any | number, params: ShopyyGetAllOrdersParams = {}, maxPages: number = 10, concurrencyLimit: number = 100): Promise<any> {
|
||||||
const firstPage = await this.getOrders(site, 1, 100);
|
const firstPage = await this.getOrders(site, 1, 100, params);
|
||||||
|
|
||||||
const { items: firstPageItems, totalPages} = firstPage;
|
const { items: firstPageItems, totalPages} = firstPage;
|
||||||
|
|
||||||
|
|
@ -365,7 +365,7 @@ export class ShopyyService {
|
||||||
* @param orderId 订单ID
|
* @param orderId 订单ID
|
||||||
* @returns 订单详情
|
* @returns 订单详情
|
||||||
*/
|
*/
|
||||||
async getOrder(siteId: string, orderId: string): Promise<any> {
|
async getOrder(siteId: string, orderId: string): Promise<ShopyyGetOneOrderResult> {
|
||||||
const site = await this.siteService.get(Number(siteId));
|
const site = await this.siteService.get(Number(siteId));
|
||||||
|
|
||||||
// ShopYY API: GET /orders/{id}
|
// ShopYY API: GET /orders/{id}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Test script for FreightwavesService createOrder method
|
// Test script for FreightwavesService createOrder method
|
||||||
|
|
||||||
const { FreightwavesService } = require('./dist/service/test-freightwaves.service');
|
const { FreightwavesService } = require('./dist/service/freightwaves.service');
|
||||||
|
|
||||||
async function testFreightwavesService() {
|
async function testFreightwavesService() {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue