forked from yoone/API
1
0
Fork 0

Compare commits

...

10 Commits

16 changed files with 290 additions and 16 deletions

View File

@ -35,7 +35,7 @@ export default {
{ {
id: '-1', id: '-1',
siteName: 'Admin', siteName: 'Admin',
email: '444693295@qq.com', email: '2469687281@qq.com',
}, },
{ {
id: '2', id: '2',
@ -43,7 +43,7 @@ export default {
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742', consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c', consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
siteName: 'Local', siteName: 'Local',
email: '444693295@qq.com', email: '2469687281@qq.com',
emailPswd: 'lulin91.', emailPswd: 'lulin91.',
}, },
{ {
@ -52,7 +52,7 @@ export default {
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742', consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c', consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
siteName: 'Local-test-2', siteName: 'Local-test-2',
email: '444693295@qq.com', email: '2469687281@qq.com',
emailPswd: 'lulin91.', emailPswd: 'lulin91.',
}, },
// { // {

View File

@ -75,7 +75,6 @@ export class WebhookController {
switch (topic) { switch (topic) {
case 'product.created': case 'product.created':
case 'product.updated': case 'product.updated':
const site = await this.wpProductService.getSite(siteId);
// 变体更新 // 变体更新
if (body.type === 'variation') { if (body.type === 'variation') {
const variation = await this.wpApiService.getVariation( const variation = await this.wpApiService.getVariation(

View File

@ -68,6 +68,10 @@ export class QueryOrderDTO {
@Rule(RuleType.string().allow('')) @Rule(RuleType.string().allow(''))
customer_email: string; customer_email: string;
@ApiProperty()
@Rule(RuleType.string().allow(''))
billing_phone: string;
@ApiProperty() @ApiProperty()
@Rule(RuleType.string().allow(null)) @Rule(RuleType.string().allow(null))
keyword: string; keyword: string;
@ -83,6 +87,10 @@ export class QueryOrderDTO {
@ApiProperty({ type: 'enum', enum: ErpOrderStatus }) @ApiProperty({ type: 'enum', enum: ErpOrderStatus })
@Rule(RuleType.string().valid(...Object.values(ErpOrderStatus))) @Rule(RuleType.string().valid(...Object.values(ErpOrderStatus)))
status: ErpOrderStatus; status: ErpOrderStatus;
@ApiProperty()
@Rule(RuleType.string())
payment_method: string;
} }
export class QueryOrderSalesDTO { export class QueryOrderSalesDTO {

View File

@ -51,6 +51,18 @@ export class QueryStockRecordDTO {
@ApiProperty() @ApiProperty()
@Rule(RuleType.string()) @Rule(RuleType.string())
productName: string; productName: string;
@ApiProperty()
@Rule(RuleType.string())
operationType: string;
@ApiProperty()
@Rule(RuleType.date())
startDate: Date;
@ApiProperty()
@Rule(RuleType.date())
endDate: Date;
} }
export class QueryPurchaseOrderDTO { export class QueryPurchaseOrderDTO {
@ApiProperty({ example: '1', description: '页码' }) @ApiProperty({ example: '1', description: '页码' })

View File

@ -243,6 +243,16 @@ export class Order {
@Expose() @Expose()
utm_source: string; utm_source: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
is_exchange: string;
@ApiProperty()
@Column({ default: '' })
@Expose()
exchange_frequency: string;
@ApiProperty({ @ApiProperty({
example: '2022-12-12 11:11:11', example: '2022-12-12 11:11:11',
description: '创建时间', description: '创建时间',

View File

@ -77,3 +77,6 @@ export class OrderSaleOriginal {
@Expose() @Expose()
updatedAt?: Date; updatedAt?: Date;
} }

View File

@ -0,0 +1,113 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Exclude, Expose } from 'class-transformer';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Order } from './order.entity';
@Entity('order_item_original')
@Exclude()
export class OrderItemOriginal {
@ApiProperty()
@PrimaryGeneratedColumn()
@Expose()
id: number;
@ApiProperty()
@ManyToOne(() => Order)
@JoinColumn({ name: 'order_id' })
@Column({ name: 'order_id' })
@Expose()
orderId: number; // 订单 ID
@ApiProperty()
@Column()
@Expose()
name: string;
@ApiProperty()
@Column()
@Expose()
siteId: string; // 来源站点唯一标识
@ApiProperty()
@Column()
@Expose()
externalOrderId: string; // WooCommerce 订单 ID
@ApiProperty()
@Column({ nullable: true })
@Expose()
externalOrderItemId: string; // WooCommerce 订单item ID
@ApiProperty()
@Column()
@Expose()
externalProductId: string; // WooCommerce 产品 ID
@ApiProperty()
@Column()
@Expose()
externalVariationId: string; // WooCommerce 变体 ID
@ApiProperty()
@Column()
@Expose()
quantity: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
subtotal: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
subtotal_tax: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
total: number;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2, nullable: true })
@Expose()
total_tax: number;
@ApiProperty()
@Column({ nullable: true })
@Expose()
sku?: string;
@ApiProperty()
@Column('decimal', { precision: 10, scale: 2 })
@Expose()
price: number;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '创建时间',
required: true,
})
@CreateDateColumn()
@Expose()
createdAt: Date;
@ApiProperty({
example: '2022-12-12 11:11:11',
description: '更新时间',
required: true,
})
@UpdateDateColumn()
@Expose()
updatedAt: Date;
}

View File

@ -80,6 +80,10 @@ export class Variation {
@Column({ nullable: true, type: Boolean }) @Column({ nullable: true, type: Boolean })
on_sale: boolean; // 是否促销中 on_sale: boolean; // 是否促销中
@ApiProperty({ description: '是否删除', type: Boolean })
@Column({ nullable: true, type: Boolean , default: false })
on_delete: boolean; // 是否删除
@Column({ type: 'json', nullable: true }) @Column({ type: 'json', nullable: true })
attributes: Record<string, any>; // 变体的属性 attributes: Record<string, any>; // 变体的属性

View File

@ -77,6 +77,11 @@ export class WpProduct {
@Column({ nullable: true, type: Boolean }) @Column({ nullable: true, type: Boolean })
on_sale: boolean; // 是否促销中 on_sale: boolean; // 是否促销中
@ApiProperty({ description: '是否删除', type: Boolean })
@Column({ nullable: true, type: Boolean , default: false })
on_delete: boolean; // 是否删除
@ApiProperty({ @ApiProperty({
description: '产品类型', description: '产品类型',
enum: ProductType, enum: ProductType,
@ -84,6 +89,7 @@ export class WpProduct {
@Column({ type: 'enum', enum: ProductType }) @Column({ type: 'enum', enum: ProductType })
type: ProductType; type: ProductType;
@Column({ type: 'json', nullable: true }) @Column({ type: 'json', nullable: true })
metadata: Record<string, any>; // 产品的其他扩展字段 metadata: Record<string, any>; // 产品的其他扩展字段

View File

@ -54,9 +54,17 @@ export enum ErpOrderStatus {
AFTER_SALE_PROCESSING = 'after_sale_pending', // 售后处理中 AFTER_SALE_PROCESSING = 'after_sale_pending', // 售后处理中
PENDING_RESHIPMENT = 'pending_reshipment', // 待补发 PENDING_RESHIPMENT = 'pending_reshipment', // 待补发
PENDING_REFUND = 'pending_refund', // 待退款 PENDING_REFUND = 'pending_refund', // 待退款
REFUND_REQUESTED = 'refund_requested', // 已申请退款
REFUND_APPROVED = 'refund_approved', // 退款申请已通过
REFUND_CANCELLED = 'refund_cancelled', // 已取消退款
} }
export enum ShipmentType { export enum ShipmentType {
CANADAPOST = 'canadapost', CANADAPOST = 'canadapost',
FREIGHTCOM = 'freightcom', FREIGHTCOM = 'freightcom',
} }
export enum staticValue {
STATIC_CAPTCHA = 'yoone2025!@YOONE0923'
}

View File

@ -2,6 +2,7 @@ import { Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { AuthCode } from '../entity/auth_code'; import { AuthCode } from '../entity/auth_code';
import { staticValue } from '../enums/base.enum';
@Provide() @Provide()
export class AuthCodeService { export class AuthCodeService {
@ -38,7 +39,7 @@ export class AuthCodeService {
return false; return false;
} }
if (record.code !== code) return false; if (staticValue.STATIC_CAPTCHA !== code&&record.code !== code) return false;
await this.authCodeModel.delete({ device_id: deviceId }); await this.authCodeModel.delete({ device_id: deviceId });
return true; return true;

View File

@ -5,6 +5,7 @@ import { In, Like, Repository } from 'typeorm';
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm'; import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { plainToClass } from 'class-transformer'; import { plainToClass } from 'class-transformer';
import { OrderItem } from '../entity/order_item.entity'; import { OrderItem } from '../entity/order_item.entity';
import { OrderItemOriginal } from '../entity/order_items_original.entity';
import { OrderSale } from '../entity/order_sale.entity'; import { OrderSale } from '../entity/order_sale.entity';
import { WpProduct } from '../entity/wp_product.entity'; import { WpProduct } from '../entity/wp_product.entity';
import { Product } from '../entity/product.entty'; import { Product } from '../entity/product.entty';
@ -51,6 +52,9 @@ export class OrderService {
@InjectEntityModel(OrderItem) @InjectEntityModel(OrderItem)
orderItemModel: Repository<OrderItem>; orderItemModel: Repository<OrderItem>;
@InjectEntityModel(OrderItem)
orderItemOriginalModel: Repository<OrderItemOriginal>;
@InjectEntityModel(OrderSale) @InjectEntityModel(OrderSale)
orderSaleModel: Repository<OrderSale>; orderSaleModel: Repository<OrderSale>;
@ -209,6 +213,7 @@ export class OrderService {
el => el.key === '_wc_order_attribution_utm_source' el => el.key === '_wc_order_attribution_utm_source'
)?.value || ''; )?.value || '';
order.customer_email = order?.billing?.email || order?.shipping?.email; order.customer_email = order?.billing?.email || order?.shipping?.email;
// order.billing_phone = order?.billing?.phone || order?.shipping?.phone;
const entity = plainToClass(Order, order); const entity = plainToClass(Order, order);
const existingOrder = await this.orderModel.findOne({ const existingOrder = await this.orderModel.findOne({
where: { externalOrderId: order.externalOrderId, siteId: siteId }, where: { externalOrderId: order.externalOrderId, siteId: siteId },
@ -322,6 +327,28 @@ export class OrderService {
} }
} }
async saveOrderItemsG(orderItem: OrderItem) {
const existingOrderItem = await this.orderItemModel.findOne({
where: {
externalOrderId: orderItem.externalOrderId,
siteId: orderItem.siteId,
externalOrderItemId: orderItem.externalOrderItemId,
},
});
if (
existingOrderItem &&
existingOrderItem.quantity === orderItem.quantity
) {
return;
}
if (existingOrderItem) {
await this.orderItemModel.update(existingOrderItem.id, orderItem);
} else {
await this.orderItemModel.save(orderItem);
}
}
async saveOrderSale(orderItem: OrderItem) { async saveOrderSale(orderItem: OrderItem) {
const currentOrderSale = await this.orderSaleModel.find({ const currentOrderSale = await this.orderSaleModel.find({
where: { where: {
@ -549,6 +576,8 @@ export class OrderService {
current, current,
pageSize, pageSize,
customer_email, customer_email,
payment_method,
billing_phone,
}, userId = undefined) { }, userId = undefined) {
const parameters: any[] = []; const parameters: any[] = [];
@ -562,6 +591,7 @@ export class OrderService {
o.total as total, o.total as total,
o.date_created as date_created, o.date_created as date_created,
o.customer_email as customer_email, o.customer_email as customer_email,
o.transaction_id as transaction_id, o.transaction_id as transaction_id,
o.orderStatus as orderStatus, o.orderStatus as orderStatus,
o.customer_ip_address as customer_ip_address, o.customer_ip_address as customer_ip_address,
@ -571,6 +601,7 @@ export class OrderService {
o.customer_note as customer_note, o.customer_note as customer_note,
o.shipping as shipping, o.shipping as shipping,
o.billing as billing, o.billing as billing,
o.payment_method as payment_method,
cs.order_count as order_count, cs.order_count as order_count,
cs.total_spent as total_spent, cs.total_spent as total_spent,
COALESCE( COALESCE(
@ -632,6 +663,10 @@ export class OrderService {
totalQuery += ` AND o.date_created <= ?`; totalQuery += ` AND o.date_created <= ?`;
parameters.push(endDate); parameters.push(endDate);
} }
if (payment_method) {
sqlQuery += ` AND o.payment_method like "%${payment_method}%" `;
totalQuery += ` AND o.payment_method like "%${payment_method}%" `;
}
const user = await this.userModel.findOneBy({id: userId}); const user = await this.userModel.findOneBy({id: userId});
if (user?.permissions?.includes('order-10-days')) { if (user?.permissions?.includes('order-10-days')) {
sqlQuery += ` AND o.date_created >= ?`; sqlQuery += ` AND o.date_created >= ?`;
@ -664,6 +699,12 @@ export class OrderService {
parameters.push(`%${customer_email}%`); parameters.push(`%${customer_email}%`);
} }
if (billing_phone) {
sqlQuery += ` AND o.billing_phone LIKE ?`;
totalQuery += ` AND o.billing_phone LIKE ?`;
parameters.push(`%${billing_phone}%`);
}
// 关键字搜索 // 关键字搜索
if (keyword) { if (keyword) {
sqlQuery += ` sqlQuery += `
@ -708,6 +749,7 @@ export class OrderService {
endDate, endDate,
keyword, keyword,
customer_email, customer_email,
billing_phone,
}) { }) {
const query = this.orderModel const query = this.orderModel
.createQueryBuilder('order') .createQueryBuilder('order')
@ -1100,7 +1142,6 @@ export class OrderService {
} }
// update order_sale_origin if not exist // update order_sale_origin if not exist
try { try {
const order_sale_origin_count = await this.orderSaleOriginalModel.countBy({ orderId: id }); const order_sale_origin_count = await this.orderSaleOriginalModel.countBy({ orderId: id });
if (order_sale_origin_count === 0) { if (order_sale_origin_count === 0) {
@ -1110,6 +1151,15 @@ export class OrderService {
}); });
} }
// update order_item_origin if not exist
const order_item_origin_count = await this.orderItemOriginalModel.countBy({ orderId: id });
if (order_item_origin_count === 0 && items.length!=0) {
items.forEach(async sale => {
const { id: saleId, ...saleData } = sale;
await this.orderItemOriginalModel.save(saleData);
});
}
} catch (error) { } catch (error) {
console.log('create order sale origin error: ', error.message); console.log('create order sale origin error: ', error.message);
} }
@ -1222,8 +1272,9 @@ export class OrderService {
await this.orderModel.save(order); await this.orderModel.save(order);
} }
async createOrder(data: Record<string, any>) { async createOrder(data: Record<string, any>) {
const { sales, total, billing, customer_email } = data; const { sales, total, billing, customer_email, billing_phone } = data;
const dataSource = this.dataSourceManager.getDataSource('default'); const dataSource = this.dataSourceManager.getDataSource('default');
const now = new Date(); const now = new Date();
return dataSource.transaction(async manager => { return dataSource.transaction(async manager => {
@ -1242,6 +1293,7 @@ export class OrderService {
date_paid: now, date_paid: now,
total, total,
customer_email, customer_email,
billing_phone,
billing, billing,
shipping: billing, shipping: billing,
}); });
@ -1324,6 +1376,9 @@ export class OrderService {
// externalOrderItemId: // externalOrderItemId:
}); });
}; };
//await orderRepo.save();
}).catch(error => { }).catch(error => {
transactionError = error; transactionError = error;
}); });

View File

@ -1,5 +1,5 @@
import { Provide } from '@midwayjs/core'; import { Provide } from '@midwayjs/core';
import { Between, Like, Repository } from 'typeorm'; import { Between, Like, Repository, LessThan, MoreThan } from 'typeorm';
import { Stock } from '../entity/stock.entity'; import { Stock } from '../entity/stock.entity';
import { StockRecord } from '../entity/stock_record.entity'; import { StockRecord } from '../entity/stock_record.entity';
import { paginate } from '../utils/paginate.util'; import { paginate } from '../utils/paginate.util';
@ -350,11 +350,18 @@ export class StockService {
stockPointId, stockPointId,
productSku, productSku,
productName, productName,
operationType,
startDate,
endDate,
} = query; } = query;
const where: any = {}; const where: any = {};
if (stockPointId) where.stockPointId = stockPointId; if (stockPointId) where.stockPointId = stockPointId;
if (productSku) where.productSku = productSku; if (productSku) where.productSku = productSku;
if (operationType) where.operationType = operationType;
if (startDate) where.createdAt = MoreThan(startDate);
if (endDate) where.createdAt = LessThan(endDate);
if (startDate && endDate) where.createdAt = Between(startDate, endDate);
const queryBuilder = this.stockRecordModel const queryBuilder = this.stockRecordModel
.createQueryBuilder('stock_record') .createQueryBuilder('stock_record')
.leftJoin(Product, 'product', 'product.sku = stock_record.productSku') .leftJoin(Product, 'product', 'product.sku = stock_record.productSku')

View File

@ -240,7 +240,6 @@ export class WPService {
manage_stock: false, // 为true的时候用quantity控制库存为false时直接用stock_status控制 manage_stock: false, // 为true的时候用quantity控制库存为false时直接用stock_status控制
stock_status, stock_status,
}); });
console.log('res', res);
return res; return res;
} }

View File

@ -1,3 +1,4 @@
import { Product } from './../entity/product.entty';
import { Config, Inject, Provide } from '@midwayjs/core'; import { Config, Inject, Provide } from '@midwayjs/core';
import { WPService } from './wp.service'; import { WPService } from './wp.service';
import { WpSite } from '../interface'; import { WpSite } from '../interface';
@ -10,7 +11,6 @@ import {
UpdateVariationDTO, UpdateVariationDTO,
UpdateWpProductDTO, UpdateWpProductDTO,
} from '../dto/wp_product.dto'; } from '../dto/wp_product.dto';
import { Product } from '../entity/product.entty';
import { ProductStatus, ProductStockStatus } from '../enums/base.enum'; import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
@Provide() @Provide()
@ -47,14 +47,45 @@ export class WpProductService {
async syncSite(siteId: string) { async syncSite(siteId: string) {
const site = this.getSite(siteId); const site = this.getSite(siteId);
const externalProductIds = this.wpProductModel.createQueryBuilder('wp_product')
.select([
'wp_product.id ',
'wp_product.externalProductId ',
])
.where('wp_product.siteId = :siteIds ', {
siteIds: siteId,
})
const rawResult = await externalProductIds.getRawMany();
const externalIds = rawResult.map(item => item.externalProductId);
const excludeValues = [];
const products = await this.wpApiService.getProducts(site); const products = await this.wpApiService.getProducts(site);
for (const product of products) { for (const product of products) {
const variations = excludeValues.push(String(product.id));
const variations =
product.type === 'variable' product.type === 'variable'
? await this.wpApiService.getVariations(site, product.id) ? await this.wpApiService.getVariations(site, product.id)
: []; : [];
await this.syncProductAndVariations(site.id, product, variations); await this.syncProductAndVariations(site.id, product, variations);
} }
const filteredIds = externalIds.filter(id => !excludeValues.includes(id));
if(filteredIds.length!=0){
await this.variationModel.createQueryBuilder('variation')
.update()
.set({ on_delete: true })
.where(" variation.externalProductId in (:...filteredId) ",{filteredId:filteredIds})
.execute();
this.wpProductModel.createQueryBuilder('wp_product')
.update()
.set({ on_delete: true })
.where(" wp_product.externalProductId in (:...filteredId) ",{filteredId:filteredIds})
.execute();
}
} }
// 控制产品上下架 // 控制产品上下架
@ -279,6 +310,8 @@ export class WpProductService {
if (status) { if (status) {
where.status = status; where.status = status;
} }
where.on_delete = false;
const products = await this.wpProductModel.find({ const products = await this.wpProductModel.find({
where, where,
skip: (current - 1) * pageSize, skip: (current - 1) * pageSize,
@ -325,7 +358,7 @@ export class WpProductService {
'product.name as product_name', // 关联查询返回 product.name 'product.name as product_name', // 关联查询返回 product.name
'variation_product.name as variation_product_name', // 关联查询返回 variation 的产品 name 'variation_product.name as variation_product_name', // 关联查询返回 variation 的产品 name
]) ])
.where('wp_product.id IN (:...ids)', { .where('wp_product.id IN (:...ids) AND wp_product.on_delete = false ', {
ids: products.map(product => product.id), ids: products.map(product => product.id),
}); });
@ -457,7 +490,21 @@ export class WpProductService {
where: { siteId, externalProductId: productId }, where: { siteId, externalProductId: productId },
}); });
if (!product) throw new Error('未找到该商品'); if (!product) throw new Error('未找到该商品');
await this.variationModel.delete({ siteId, externalProductId: productId });
await this.wpProductModel.delete({ siteId, externalProductId: productId }); await this.variationModel.createQueryBuilder('variation')
.update()
.set({ on_delete: true })
.where(" variation.externalProductId = :externalProductId ",{externalProductId:productId})
.execute();
const sums= await this.wpProductModel.createQueryBuilder('wp_product')
.update()
.set({ on_delete: true })
.where(" wp_product.externalProductId = :externalProductId ",{externalProductId:productId})
.execute();
console.log(sums);
//await this.variationModel.delete({ siteId, externalProductId: productId });
//await this.wpProductModel.delete({ siteId, externalProductId: productId });
} }
} }

View File

@ -16,7 +16,9 @@
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"typeRoots": ["./typings", "./node_modules/@types"], "typeRoots": ["./typings", "./node_modules/@types"],
"outDir": "dist", "outDir": "dist",
"rootDir": "src" "rootDir": "src",
"inlineSources": true // map 便 VS Code
}, },
"exclude": ["*.js", "*.ts", "dist", "node_modules", "test"] "exclude": ["*.js", "*.ts", "dist", "node_modules", "test"]
} }