Compare commits
28 Commits
1c828f49c9
...
1a1f7c8515
| Author | SHA1 | Date |
|---|---|---|
|
|
1a1f7c8515 | |
|
|
9137e95c02 | |
|
|
2ad48143b7 | |
|
|
ca1a15c75d | |
|
|
8a1b929692 | |
|
|
d23acbee1c | |
|
|
96eb7768f3 | |
|
|
c55eefa3de | |
|
|
6c65f5be85 | |
|
|
b0e2c42ad0 | |
|
|
015113d3b3 | |
|
|
39afbae7cf | |
|
|
b2cddad10a | |
|
|
e4e4ceb7c1 | |
|
|
dd4ae5383f | |
|
|
cbd5ddc4d2 | |
|
|
8e256f67a5 | |
|
|
cc23f1694d | |
|
|
94767d5120 | |
|
|
0048d2ffec | |
|
|
4d348acf7f | |
|
|
6ae4476d0c | |
|
|
d59e17fefc | |
|
|
5e5ed3b309 | |
|
|
590aea0c1c | |
|
|
8c17b47474 | |
|
|
64ea93e3d0 | |
|
|
4c7875e099 |
|
|
@ -45,5 +45,8 @@
|
||||||
"url": ""
|
"url": ""
|
||||||
},
|
},
|
||||||
"author": "anonymous",
|
"author": "anonymous",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@midwayjs/mock": "^3.20.11"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import { OrderFee } from '../entity/order_fee.entity';
|
||||||
import { OrderRefund } from '../entity/order_refund.entity';
|
import { OrderRefund } from '../entity/order_refund.entity';
|
||||||
import { OrderRefundItem } from '../entity/order_retund_item.entity';
|
import { OrderRefundItem } from '../entity/order_retund_item.entity';
|
||||||
import { OrderSale } from '../entity/order_sale.entity';
|
import { OrderSale } from '../entity/order_sale.entity';
|
||||||
|
import { OrderSaleOriginal } from '../entity/order_item_original.entity';
|
||||||
import { OrderShipping } from '../entity/order_shipping.entity';
|
import { OrderShipping } from '../entity/order_shipping.entity';
|
||||||
import { Service } from '../entity/service.entity';
|
import { Service } from '../entity/service.entity';
|
||||||
import { ShippingAddress } from '../entity/shipping_address.entity';
|
import { ShippingAddress } from '../entity/shipping_address.entity';
|
||||||
|
|
@ -57,6 +58,7 @@ export default {
|
||||||
OrderRefund,
|
OrderRefund,
|
||||||
OrderRefundItem,
|
OrderRefundItem,
|
||||||
OrderSale,
|
OrderSale,
|
||||||
|
OrderSaleOriginal,
|
||||||
OrderShipment,
|
OrderShipment,
|
||||||
ShipmentItem,
|
ShipmentItem,
|
||||||
Shipment,
|
Shipment,
|
||||||
|
|
|
||||||
|
|
@ -3,24 +3,24 @@ export default {
|
||||||
koa: {
|
koa: {
|
||||||
port: 7001,
|
port: 7001,
|
||||||
},
|
},
|
||||||
typeorm: {
|
|
||||||
dataSource: {
|
|
||||||
default: {
|
|
||||||
host: '13.212.62.127',
|
|
||||||
username: 'root',
|
|
||||||
password: 'Yoone!@.2025',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// typeorm: {
|
// typeorm: {
|
||||||
// dataSource: {
|
// dataSource: {
|
||||||
// default: {
|
// default: {
|
||||||
// host: '127.0.0.1',
|
// host: '13.212.62.127',
|
||||||
// username: 'root',
|
// username: 'root',
|
||||||
// password: '123456',
|
// password: 'Yoone!@.2025',
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
|
typeorm: {
|
||||||
|
dataSource: {
|
||||||
|
default: {
|
||||||
|
host: 'localhost',
|
||||||
|
username: 'root',
|
||||||
|
password: '12345678',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
cors: {
|
cors: {
|
||||||
origin: '*', // 允许所有来源跨域请求
|
origin: '*', // 允许所有来源跨域请求
|
||||||
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法
|
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // 允许的 HTTP 方法
|
||||||
|
|
@ -35,17 +35,35 @@ export default {
|
||||||
{
|
{
|
||||||
id: '-1',
|
id: '-1',
|
||||||
siteName: 'Admin',
|
siteName: 'Admin',
|
||||||
email: 'tom@yoonevape.com',
|
email: '444693295@qq.com',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
wpApiUrl: 'http://localhost:10004',
|
wpApiUrl: 'http://t2-shop.local/',
|
||||||
consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652',
|
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
|
||||||
consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38',
|
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
|
||||||
siteName: 'Local',
|
siteName: 'Local',
|
||||||
email: 'tom@yoonevape.com',
|
email: '444693295@qq.com',
|
||||||
emailPswd: 'lulin91.',
|
emailPswd: 'lulin91.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
wpApiUrl: 'http://t1-shop.local/',
|
||||||
|
consumerKey: 'ck_a369473a6451dbaec63d19cbfd74a074b2c5f742',
|
||||||
|
consumerSecret: 'cs_0946bbbeea1bfefff08a69e817ac62a48412df8c',
|
||||||
|
siteName: 'Local-test-2',
|
||||||
|
email: '444693295@qq.com',
|
||||||
|
emailPswd: 'lulin91.',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// id: '2',
|
||||||
|
// wpApiUrl: 'http://localhost:10004',
|
||||||
|
// consumerKey: 'ck_dc9e151e9048c8ed3e27f35ac79d2bf7d6840652',
|
||||||
|
// consumerSecret: 'cs_d05d625d7b0ac05c6d765671d8417f41d9477e38',
|
||||||
|
// siteName: 'Local',
|
||||||
|
// email: 'tom@yoonevape.com',
|
||||||
|
// emailPswd: 'lulin91.',
|
||||||
|
// },
|
||||||
],
|
],
|
||||||
freightcom: {
|
freightcom: {
|
||||||
url: 'https://customer-external-api.ssd-test.freightcom.com',
|
url: 'https://customer-external-api.ssd-test.freightcom.com',
|
||||||
|
|
@ -58,4 +76,11 @@ export default {
|
||||||
customerNumber: '0006122480',
|
customerNumber: '0006122480',
|
||||||
contractId: '0044168528',
|
contractId: '0044168528',
|
||||||
},
|
},
|
||||||
|
uniExpress: {
|
||||||
|
url: 'https://sjqa.uniexpress.org', // 测试环境url
|
||||||
|
// url: 'https://sj.uniexpress.ca', //正式环境url
|
||||||
|
clientId: '101018',
|
||||||
|
clientSecret: 'cbcb51bea204f3f69c47b5280064408e',
|
||||||
|
customerNo: 2067,
|
||||||
|
}
|
||||||
} as MidwayConfig;
|
} as MidwayConfig;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
import { LogisticsService } from '../service/logistics.service';
|
import { LogisticsService } from '../service/logistics.service';
|
||||||
import { ShippingDetailsDTO } from '../dto/freightcom.dto';
|
import { ShippingDetailsDTO } from '../dto/freightcom.dto';
|
||||||
import { ShippingAddress } from '../entity/shipping_address.entity';
|
import { ShippingAddress } from '../entity/shipping_address.entity';
|
||||||
import { QueryServiceDTO, ShipmentBookDTO } from '../dto/logistics.dto';
|
import { QueryServiceDTO, ShipmentBookDTO, ShipmentFeeBookDTO } from '../dto/logistics.dto';
|
||||||
import { User } from '../decorator/user.decorator';
|
import { User } from '../decorator/user.decorator';
|
||||||
|
|
||||||
@Controller('/logistics')
|
@Controller('/logistics')
|
||||||
|
|
@ -145,6 +145,19 @@ export class LogisticsController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({
|
||||||
|
type: BooleanRes,
|
||||||
|
})
|
||||||
|
@Post('/deleteShipment/:id')
|
||||||
|
async deleteShipment(@Param('id') id: number) {
|
||||||
|
try {
|
||||||
|
const res = await this.logisticsService.removeShipment(id);
|
||||||
|
return successResponse(res);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '删除运单失败' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
type: BooleanRes,
|
type: BooleanRes,
|
||||||
})
|
})
|
||||||
|
|
@ -155,8 +168,43 @@ export class LogisticsController {
|
||||||
@User() user
|
@User() user
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await this.logisticsService.createShipment(orderId, data, user.id);
|
const res: any = await this.logisticsService.createShipment(orderId, data, user.id);
|
||||||
return successResponse(true);
|
return successResponse(res.data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '创建失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse(
|
||||||
|
{type: BooleanRes}
|
||||||
|
)
|
||||||
|
@Post('/getShipmentFee')
|
||||||
|
async getShipmentFee(
|
||||||
|
@Body() data: ShipmentFeeBookDTO
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const fee = await this.logisticsService.getShipmentFee(data);
|
||||||
|
return successResponse(fee);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '创建失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse(
|
||||||
|
{type: BooleanRes}
|
||||||
|
)
|
||||||
|
@Post('/getShipmentLabel/:shipmentId')
|
||||||
|
async getShipmentLabel(
|
||||||
|
@Param('shipmentId') shipmentId: number
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const res = await this.logisticsService.getShipmentLabel(shipmentId);
|
||||||
|
|
||||||
|
if (res.data.data[0].status === 'Success') {
|
||||||
|
return successResponse({ content: res.data.data[0].labelContent });
|
||||||
|
} else {
|
||||||
|
return errorResponse(res.data.data[0].errors);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || '创建失败');
|
return errorResponse(error?.message || '创建失败');
|
||||||
}
|
}
|
||||||
|
|
@ -173,9 +221,22 @@ export class LogisticsController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('/updateState/:shipmentId')
|
||||||
|
async updateShipmentState(
|
||||||
|
@Param('shipmentId') shipmentId: number
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const data = await this.logisticsService.updateShipmentStateById(shipmentId);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '更新运单状态失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Del('/shipment/:id')
|
@Del('/shipment/:id')
|
||||||
async delShipment(@Param('id') id: string, @User() user) {
|
async delShipment(@Param('id') id: number, @User() user) {
|
||||||
try {
|
try {
|
||||||
const data = await this.logisticsService.delShipment(id, user.id);
|
const data = await this.logisticsService.delShipment(id, user.id);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
|
|
@ -186,11 +247,11 @@ export class LogisticsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Post('/getTrackingNumber')
|
@Post('/getOrderList')
|
||||||
async getTrackingNumber(@Query('number') number: string) {
|
async getOrderList(@Query('number') number: string) {
|
||||||
try {
|
try {
|
||||||
return successResponse(
|
return successResponse(
|
||||||
await this.logisticsService.getTrackingNumber(number)
|
await this.logisticsService.getOrderList(number)
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || '获取失败');
|
return errorResponse(error?.message || '获取失败');
|
||||||
|
|
@ -198,11 +259,11 @@ export class LogisticsController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOkResponse()
|
@ApiOkResponse()
|
||||||
@Post('/getListByTrackingId')
|
@Post('/getListByOrderId')
|
||||||
async getListByTrackingId(@Query('shipment_id') shipment_id: string) {
|
async getListByOrderId(@Query('id') orderId: number) {
|
||||||
try {
|
try {
|
||||||
return successResponse(
|
return successResponse(
|
||||||
await this.logisticsService.getListByTrackingId(shipment_id)
|
await this.logisticsService.getListByOrderId(orderId)
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || '获取失败');
|
return errorResponse(error?.message || '获取失败');
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,12 @@ export class OrderController {
|
||||||
@Get('/getOrders')
|
@Get('/getOrders')
|
||||||
async getOrders(
|
async getOrders(
|
||||||
@Query()
|
@Query()
|
||||||
param: QueryOrderDTO
|
param: QueryOrderDTO,
|
||||||
|
@User() user
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const count = await this.orderService.getOrderStatus(param);
|
const count = await this.orderService.getOrderStatus(param);
|
||||||
const data = await this.orderService.getOrders(param);
|
const data = await this.orderService.getOrders(param, user.id);
|
||||||
return successResponse({
|
return successResponse({
|
||||||
...data,
|
...data,
|
||||||
count,
|
count,
|
||||||
|
|
@ -108,6 +109,22 @@ export class OrderController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({
|
||||||
|
type: BooleanRes,
|
||||||
|
})
|
||||||
|
@Post('/updateOrderItems/:orderId')
|
||||||
|
async updateOrderItems(
|
||||||
|
@Param('orderId') orderId: number,
|
||||||
|
@Body() data: any
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const res = await this.orderService.updateOrderSales(orderId, data);
|
||||||
|
return successResponse(res);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || '更新失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
type: BooleanRes,
|
type: BooleanRes,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,15 @@ export class UserController {
|
||||||
@Inject()
|
@Inject()
|
||||||
userService: UserService;
|
userService: UserService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
ctx;
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
type: LoginRes,
|
type: LoginRes,
|
||||||
})
|
})
|
||||||
@Post('/login')
|
@Post('/login')
|
||||||
async login(@Body() body) {
|
async login(@Body() body) {
|
||||||
|
this.ctx.logger.info('ip:', this.ctx.ip, '; path:', this.ctx.path, '; user:', body?.username);
|
||||||
try {
|
try {
|
||||||
const result = await this.userService.login(body);
|
const result = await this.userService.login(body);
|
||||||
return successResponse(result, '登录成功');
|
return successResponse(result, '登录成功');
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export class WebhookController {
|
||||||
switch (topic) {
|
switch (topic) {
|
||||||
case 'product.created':
|
case 'product.created':
|
||||||
case 'product.updated':
|
case 'product.updated':
|
||||||
const site = await this.wpProductService.geSite(siteId);
|
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(
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
Query,
|
Query,
|
||||||
Put,
|
Put,
|
||||||
Body,
|
Body,
|
||||||
|
Config,
|
||||||
} from '@midwayjs/core';
|
} from '@midwayjs/core';
|
||||||
import { WpProductService } from '../service/wp_product.service';
|
import { WpProductService } from '../service/wp_product.service';
|
||||||
import { errorResponse, successResponse } from '../utils/response.util';
|
import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
|
|
@ -19,9 +20,21 @@ import {
|
||||||
UpdateWpProductDTO,
|
UpdateWpProductDTO,
|
||||||
} from '../dto/wp_product.dto';
|
} from '../dto/wp_product.dto';
|
||||||
import { WPService } from '../service/wp.service';
|
import { WPService } from '../service/wp.service';
|
||||||
|
import { WpSite } from '../interface';
|
||||||
|
|
||||||
@Controller('/wp_product')
|
@Controller('/wp_product')
|
||||||
export class WpProductController {
|
export class WpProductController {
|
||||||
|
@Inject()
|
||||||
|
wpService: WPService;
|
||||||
|
|
||||||
|
@Config('wpSite')
|
||||||
|
sites: WpSite[];
|
||||||
|
|
||||||
|
getSite(id: string): WpSite {
|
||||||
|
let idx = this.sites.findIndex(item => item.id === id);
|
||||||
|
return this.sites[idx];
|
||||||
|
}
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private readonly wpProductService: WpProductService;
|
private readonly wpProductService: WpProductService;
|
||||||
|
|
||||||
|
|
@ -73,6 +86,22 @@ export class WpProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOkResponse({
|
||||||
|
type: BooleanRes
|
||||||
|
})
|
||||||
|
@Post('/updateState/:id')
|
||||||
|
async updateWPProductState(
|
||||||
|
@Param('id') id: number,
|
||||||
|
@Body() body: any, // todo
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const res = await this.wpProductService.updateProductStatus(id, body?.status, body?.stock_status);
|
||||||
|
return successResponse(res);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新产品接口
|
* 更新产品接口
|
||||||
* @param productId 产品 ID
|
* @param productId 产品 ID
|
||||||
|
|
@ -96,7 +125,7 @@ export class WpProductController {
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
return errorResponse('SKU已存在');
|
return errorResponse('SKU已存在');
|
||||||
}
|
}
|
||||||
const site = await this.wpProductService.geSite(siteId);
|
const site = await this.wpProductService.getSite(siteId);
|
||||||
const result = await this.wpApiService.updateProduct(
|
const result = await this.wpApiService.updateProduct(
|
||||||
site,
|
site,
|
||||||
productId,
|
productId,
|
||||||
|
|
@ -136,7 +165,7 @@ export class WpProductController {
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
return errorResponse('SKU已存在');
|
return errorResponse('SKU已存在');
|
||||||
}
|
}
|
||||||
const site = await this.wpProductService.geSite(siteId);
|
const site = await this.wpProductService.getSite(siteId);
|
||||||
const result = await this.wpApiService.updateVariation(
|
const result = await this.wpApiService.updateVariation(
|
||||||
site,
|
site,
|
||||||
productId,
|
productId,
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,9 @@ export class PackagingEnvelope {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export class ShippingDetailsDTO {
|
export class ShippingDetailsDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
shipmentFee: number;
|
||||||
|
|
||||||
@ApiProperty({ type: Location })
|
@ApiProperty({ type: Location })
|
||||||
@Rule(RuleType.object<Location>())
|
@Rule(RuleType.object<Location>())
|
||||||
origin: Location;
|
origin: Location;
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,12 @@ import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { ShippingDetailsDTO } from './freightcom.dto';
|
import { ShippingDetailsDTO } from './freightcom.dto';
|
||||||
import { Rule, RuleType } from '@midwayjs/validate';
|
import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
import { OrderSale } from '../entity/order_sale.entity';
|
import { OrderSale } from '../entity/order_sale.entity';
|
||||||
import { ShipmentType } from '../enums/base.enum';
|
|
||||||
|
|
||||||
export class ShipmentBookDTO {
|
export class ShipmentBookDTO {
|
||||||
@ApiProperty({ type: OrderSale, isArray: true })
|
@ApiProperty({ type: OrderSale, isArray: true })
|
||||||
@Rule(RuleType.array<OrderSale>())
|
@Rule(RuleType.array<OrderSale>())
|
||||||
sales: OrderSale[];
|
sales: OrderSale[];
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
payment_method_id: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
service_id: string;
|
|
||||||
|
|
||||||
@ApiProperty()
|
|
||||||
@Rule(RuleType.string())
|
|
||||||
service_type: ShipmentType;
|
|
||||||
|
|
||||||
@ApiProperty({ type: ShippingDetailsDTO })
|
@ApiProperty({ type: ShippingDetailsDTO })
|
||||||
@Rule(RuleType.object<ShippingDetailsDTO>())
|
@Rule(RuleType.object<ShippingDetailsDTO>())
|
||||||
details: ShippingDetailsDTO;
|
details: ShippingDetailsDTO;
|
||||||
|
|
@ -34,6 +21,50 @@ export class ShipmentBookDTO {
|
||||||
orderIds?: number[];
|
orderIds?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ShipmentFeeBookDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
stockPointId: number;
|
||||||
|
@ApiProperty()
|
||||||
|
sender: string;
|
||||||
|
@ApiProperty()
|
||||||
|
startPhone: string;
|
||||||
|
@ApiProperty()
|
||||||
|
startPostalCode: string;
|
||||||
|
@ApiProperty()
|
||||||
|
pickupAddress: string;
|
||||||
|
// pickupWarehouse: number; // 此处用 stockPointId 到后端解析
|
||||||
|
@ApiProperty()
|
||||||
|
shipperCountryCode: string;
|
||||||
|
@ApiProperty()
|
||||||
|
receiver: string;
|
||||||
|
@ApiProperty()
|
||||||
|
city: string;
|
||||||
|
@ApiProperty()
|
||||||
|
province: string;
|
||||||
|
@ApiProperty()
|
||||||
|
country: string;
|
||||||
|
@ApiProperty()
|
||||||
|
postalCode: string;
|
||||||
|
@ApiProperty()
|
||||||
|
deliveryAddress: string;
|
||||||
|
@ApiProperty()
|
||||||
|
receiverPhone: string;
|
||||||
|
@ApiProperty()
|
||||||
|
receiverEmail: string;
|
||||||
|
@ApiProperty()
|
||||||
|
length: number;
|
||||||
|
@ApiProperty()
|
||||||
|
width: number;
|
||||||
|
@ApiProperty()
|
||||||
|
height: number;
|
||||||
|
@ApiProperty()
|
||||||
|
dimensionUom: string;
|
||||||
|
@ApiProperty()
|
||||||
|
weight: number;
|
||||||
|
@ApiProperty()
|
||||||
|
weightUom: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class PaymentMethodDTO {
|
export class PaymentMethodDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { ErpOrderStatus } from '../enums/base.enum';
|
import { ErpOrderStatus } from '../enums/base.enum';
|
||||||
import { Rule, RuleType } from '@midwayjs/validate';
|
import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
import { Shipment } from '../entity/shipment.entity';
|
// import { Shipment } from '../entity/shipment.entity';
|
||||||
import { ShipmentItem } from '../entity/shipment_item.entity';
|
// import { ShipmentItem } from '../entity/shipment_item.entity';
|
||||||
|
|
||||||
export class OrderAddress {
|
export class OrderAddress {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
|
|
@ -90,6 +90,10 @@ export class QueryOrderSalesDTO {
|
||||||
@Rule(RuleType.bool().default(false))
|
@Rule(RuleType.bool().default(false))
|
||||||
isSource: boolean;
|
isSource: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Rule(RuleType.bool().default(false))
|
||||||
|
exceptPackage: boolean;
|
||||||
|
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
@ApiProperty({ example: '1', description: '页码' })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
current: number;
|
current: number;
|
||||||
|
|
@ -115,10 +119,10 @@ export class QueryOrderSalesDTO {
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tracking extends Shipment {
|
// export class Tracking extends Shipment {
|
||||||
@ApiProperty({ type: ShipmentItem, isArray: true })
|
// @ApiProperty({ type: ShipmentItem, isArray: true })
|
||||||
products?: ShipmentItem[];
|
// products?: ShipmentItem[];
|
||||||
}
|
// }
|
||||||
|
|
||||||
export class CreateOrderNoteDTO {
|
export class CreateOrderNoteDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
SuccessArrayWrapper,
|
SuccessArrayWrapper,
|
||||||
SuccessWrapper,
|
SuccessWrapper,
|
||||||
} from '../utils/response-wrapper.util';
|
} from '../utils/response-wrapper.util';
|
||||||
import { OrderStatusCountDTO, Tracking } from './order.dto';
|
import { OrderStatusCountDTO } from './order.dto';
|
||||||
import { SiteConfig } from './site.dto';
|
import { SiteConfig } from './site.dto';
|
||||||
import { PurchaseOrderDTO, StockDTO, StockRecordDTO } from './stock.dto';
|
import { PurchaseOrderDTO, StockDTO, StockRecordDTO } from './stock.dto';
|
||||||
import { LoginResDTO } from './user.dto';
|
import { LoginResDTO } from './user.dto';
|
||||||
|
|
@ -107,8 +107,8 @@ export class OrderDetail extends Order {
|
||||||
@ApiProperty({ type: OrderRefundItem, isArray: true })
|
@ApiProperty({ type: OrderRefundItem, isArray: true })
|
||||||
refundItems: OrderRefundItem[];
|
refundItems: OrderRefundItem[];
|
||||||
|
|
||||||
@ApiProperty({ type: Tracking, isArray: true })
|
// @ApiProperty({ type: Tracking, isArray: true })
|
||||||
trackings: Tracking[];
|
// trackings: Tracking[];
|
||||||
|
|
||||||
@ApiProperty({ type: OrderNote, isArray: true })
|
@ApiProperty({ type: OrderNote, isArray: true })
|
||||||
notes: OrderNote[];
|
notes: OrderNote[];
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,13 @@ import {
|
||||||
Entity,
|
Entity,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
|
OneToOne,
|
||||||
|
JoinColumn
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { ErpOrderStatus, OrderStatus } from '../enums/base.enum';
|
import { ErpOrderStatus, OrderStatus } from '../enums/base.enum';
|
||||||
import { OrderAddress } from '../dto/order.dto';
|
import { OrderAddress } from '../dto/order.dto';
|
||||||
import { Exclude, Expose } from 'class-transformer';
|
import { Exclude, Expose } from 'class-transformer';
|
||||||
|
import { Shipment } from './shipment.entity';
|
||||||
|
|
||||||
@Entity('order')
|
@Entity('order')
|
||||||
@Exclude()
|
@Exclude()
|
||||||
|
|
@ -44,6 +47,15 @@ export class Order {
|
||||||
@Expose()
|
@Expose()
|
||||||
orderStatus: ErpOrderStatus; // 订单状态
|
orderStatus: ErpOrderStatus; // 订单状态
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column({ name: 'shipment_id', nullable: true })
|
||||||
|
@Expose()
|
||||||
|
shipmentId: number;
|
||||||
|
|
||||||
|
@OneToOne(() => Shipment)
|
||||||
|
@JoinColumn({ name: 'shipment_id' })
|
||||||
|
shipment: Shipment;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
@Expose()
|
@Expose()
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export class OrderItem {
|
||||||
externalOrderId: string; // WooCommerce 订单 ID
|
externalOrderId: string; // WooCommerce 订单 ID
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
externalOrderItemId: string; // WooCommerce 订单item ID
|
externalOrderItemId: string; // WooCommerce 订单item ID
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
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_sale_original')
|
||||||
|
@Exclude()
|
||||||
|
export class OrderSaleOriginal {
|
||||||
|
@ApiProperty()
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
@Expose()
|
||||||
|
id?: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@ManyToOne(() => Order)
|
||||||
|
@JoinColumn({ name: 'order_id' })
|
||||||
|
@Column({ name: 'order_id' })
|
||||||
|
@Expose()
|
||||||
|
orderId: number; // 订单 ID
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column()
|
||||||
|
@Expose()
|
||||||
|
siteId: string; // 来源站点唯一标识
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column({ nullable: true })
|
||||||
|
@Expose()
|
||||||
|
externalOrderItemId: string; // WooCommerce 订单item ID
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column()
|
||||||
|
@Expose()
|
||||||
|
productId: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column()
|
||||||
|
@Expose()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'sku', type: 'string' })
|
||||||
|
@Expose()
|
||||||
|
@Column()
|
||||||
|
sku: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column()
|
||||||
|
@Expose()
|
||||||
|
quantity: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column({ default: false })
|
||||||
|
@Expose()
|
||||||
|
isPackage: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2022-12-12 11:11:11',
|
||||||
|
description: '创建时间',
|
||||||
|
})
|
||||||
|
@CreateDateColumn()
|
||||||
|
@Expose()
|
||||||
|
createdAt?: Date;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
example: '2022-12-12 11:11:11',
|
||||||
|
description: '更新时间',
|
||||||
|
})
|
||||||
|
@UpdateDateColumn()
|
||||||
|
@Expose()
|
||||||
|
updatedAt?: Date;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { Exclude, Expose } from 'class-transformer';
|
import { Exclude, Expose } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
|
BeforeInsert,
|
||||||
|
BeforeUpdate,
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
|
@ -27,7 +29,7 @@ export class OrderSale {
|
||||||
siteId: string; // 来源站点唯一标识
|
siteId: string; // 来源站点唯一标识
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
externalOrderItemId: string; // WooCommerce 订单item ID
|
externalOrderItemId: string; // WooCommerce 订单item ID
|
||||||
|
|
||||||
|
|
@ -56,6 +58,26 @@ export class OrderSale {
|
||||||
@Expose()
|
@Expose()
|
||||||
isPackage: boolean;
|
isPackage: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column({ default: false })
|
||||||
|
@Expose()
|
||||||
|
isYoone: boolean;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column({ default: false })
|
||||||
|
@Expose()
|
||||||
|
isZex: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
@Column({ type: 'int', nullable: true })
|
||||||
|
@Expose()
|
||||||
|
size: number | null;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column({ default: false })
|
||||||
|
@Expose()
|
||||||
|
isYooneNew: boolean;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '2022-12-12 11:11:11',
|
example: '2022-12-12 11:11:11',
|
||||||
description: '创建时间',
|
description: '创建时间',
|
||||||
|
|
@ -71,4 +93,25 @@ export class OrderSale {
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
@Expose()
|
@Expose()
|
||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
|
|
||||||
|
// === 自动计算逻辑 ===
|
||||||
|
@BeforeInsert()
|
||||||
|
@BeforeUpdate()
|
||||||
|
setFlags() {
|
||||||
|
if (!this.name) return;
|
||||||
|
const lower = this.name.toLowerCase();
|
||||||
|
this.isYoone = lower.includes('yoone');
|
||||||
|
this.isZex = lower.includes('zex');
|
||||||
|
this.isYooneNew = this.isYoone && lower.includes('new');
|
||||||
|
let size: number | null = null;
|
||||||
|
const sizes = [3, 6, 9, 12, 15, 18];
|
||||||
|
for (const s of sizes) {
|
||||||
|
if (lower.includes(s.toString())) {
|
||||||
|
size = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export class OrderShipment {
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
shipment_id: string;
|
shipment_id: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,49 @@ import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
PrimaryColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
|
ManyToOne,
|
||||||
|
OneToOne,
|
||||||
|
JoinColumn
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
import { StockPoint } from './stock_point.entity';
|
||||||
|
import { Order } from './order.entity';
|
||||||
|
|
||||||
@Entity('shipment')
|
@Entity('shipment')
|
||||||
@Exclude()
|
@Exclude()
|
||||||
export class Shipment {
|
export class Shipment {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@PrimaryColumn()
|
@PrimaryGeneratedColumn()
|
||||||
@Expose()
|
@Expose()
|
||||||
id: string;
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column({ nullable: true })
|
||||||
|
order_id: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@OneToOne(() => Order)
|
||||||
|
@JoinColumn({ name: 'order_id' })
|
||||||
|
order: Order;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column({ name: 'stock_point_id' })
|
||||||
|
@Expose()
|
||||||
|
stockPointId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => StockPoint)
|
||||||
|
@JoinColumn({ name: 'stock_point_id' })
|
||||||
|
stockPoint: StockPoint;
|
||||||
|
|
||||||
|
@Column({ nullable: false, default: 0})
|
||||||
|
@Expose()
|
||||||
|
fee: number;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@Column({ nullable: true })
|
||||||
|
@Expose()
|
||||||
|
tracking_id: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
|
|
@ -30,6 +62,10 @@ export class Shipment {
|
||||||
@Expose()
|
@Expose()
|
||||||
state?: string;
|
state?: string;
|
||||||
|
|
||||||
|
@Column({ nullable: false, default: false })
|
||||||
|
@Expose()
|
||||||
|
finished: boolean;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
type?: string;
|
type?: string;
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,13 @@ import {
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
import { Shipment } from './shipment.entity';
|
||||||
|
import { Order } from './order.entity';
|
||||||
|
|
||||||
@Entity('shipment_item')
|
@Entity('shipment_item')
|
||||||
@Exclude()
|
@Exclude()
|
||||||
|
|
@ -17,17 +21,19 @@ export class ShipmentItem {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
|
||||||
@Expose()
|
@Expose()
|
||||||
shipment_id: string;
|
@ManyToOne(() => Shipment)
|
||||||
|
@JoinColumn({ name: 'shipment_id' })
|
||||||
|
shipment_id: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
|
||||||
@Expose()
|
@Expose()
|
||||||
productId: number;
|
@ManyToOne(() => Order)
|
||||||
|
@JoinColumn({ name: 'order_id' })
|
||||||
|
order_id: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Column()
|
@Column({ nullable: true })
|
||||||
@Expose()
|
@Expose()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ import {
|
||||||
Entity,
|
Entity,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
|
OneToMany,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
import { Shipment } from './shipment.entity';
|
||||||
|
|
||||||
@Entity('stock_point')
|
@Entity('stock_point')
|
||||||
export class StockPoint extends BaseEntity {
|
export class StockPoint extends BaseEntity {
|
||||||
|
|
@ -15,6 +17,9 @@ export class StockPoint extends BaseEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
|
@OneToMany(() => Shipment, shipment => shipment.stockPoint)
|
||||||
|
shipments: Shipment[];
|
||||||
|
|
||||||
@ApiProperty({ type: 'string' })
|
@ApiProperty({ type: 'string' })
|
||||||
@Column()
|
@Column()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -43,6 +48,12 @@ export class StockPoint extends BaseEntity {
|
||||||
@Column({ default: false })
|
@Column({ default: false })
|
||||||
isB: boolean;
|
isB: boolean;
|
||||||
|
|
||||||
|
@Column({ default: 'uniuni' })
|
||||||
|
upStreamName: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
upStreamStockPointId: number;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: '2022-12-12 11:11:11',
|
example: '2022-12-12 11:11:11',
|
||||||
description: '创建时间',
|
description: '创建时间',
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
Entity,
|
Entity,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { ProductStatus, ProductType } from '../enums/base.enum';
|
import { ProductStatus, ProductStockStatus, ProductType } from '../enums/base.enum';
|
||||||
|
|
||||||
@Entity('wp_product')
|
@Entity('wp_product')
|
||||||
@Unique(['siteId', 'externalProductId']) // 确保产品的唯一性
|
@Unique(['siteId', 'externalProductId']) // 确保产品的唯一性
|
||||||
|
|
@ -56,6 +56,15 @@ export class WpProduct {
|
||||||
@Column({ type: 'enum', enum: ProductStatus })
|
@Column({ type: 'enum', enum: ProductStatus })
|
||||||
status: ProductStatus;
|
status: ProductStatus;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '上下架状态', enum: ProductStockStatus })
|
||||||
|
@Column({
|
||||||
|
name: 'stock_status',
|
||||||
|
type: 'enum',
|
||||||
|
enum: ProductStockStatus,
|
||||||
|
default: ProductStockStatus.INSTOCK
|
||||||
|
})
|
||||||
|
stockStatus: ProductStockStatus;
|
||||||
|
|
||||||
@ApiProperty({ description: '常规价格', type: Number })
|
@ApiProperty({ description: '常规价格', type: Number })
|
||||||
@Column('decimal', { precision: 10, scale: 2, nullable: true })
|
@Column('decimal', { precision: 10, scale: 2, nullable: true })
|
||||||
regular_price: number; // 常规价格
|
regular_price: number; // 常规价格
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,16 @@ export enum ProductStatus {
|
||||||
INHERIT = 'inherit', // 继承状态
|
INHERIT = 'inherit', // 继承状态
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProductStockStatus {
|
||||||
|
INSTOCK = 'instock',
|
||||||
|
OUT_OF_STOCK = 'outofstock',
|
||||||
|
ON_BACK_ORDER = 'onbackorder',
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProductType {
|
export enum ProductType {
|
||||||
SIMPLE = 'simple',
|
SIMPLE = 'simple',
|
||||||
VARIABLE = 'variable',
|
VARIABLE = 'variable',
|
||||||
|
WOOSB = 'woosb',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PurchaseOrderStatus {
|
export enum PurchaseOrderStatus {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { FORMAT, ILogger, Inject, Logger } from '@midwayjs/core';
|
import { FORMAT, ILogger, Inject, Logger } from '@midwayjs/core';
|
||||||
import { IJob, Job } from '@midwayjs/cron';
|
import { IJob, Job } from '@midwayjs/cron';
|
||||||
import { LogisticsService } from '../service/logistics.service';
|
import { LogisticsService } from '../service/logistics.service';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { Shipment } from '../entity/shipment.entity';
|
||||||
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
|
|
||||||
@Job({
|
@Job({
|
||||||
cronTime: FORMAT.CRONTAB.EVERY_PER_30_MINUTE,
|
cronTime: FORMAT.CRONTAB.EVERY_PER_30_MINUTE,
|
||||||
|
|
@ -20,3 +23,65 @@ export class SyncShipmentJob implements IJob {
|
||||||
}
|
}
|
||||||
onComplete?(result: any) {}
|
onComplete?(result: any) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Job({
|
||||||
|
cronTime: '0 0 12 * * *', // 每天12点执行
|
||||||
|
start: true
|
||||||
|
})
|
||||||
|
export class SyncUniuniShipmentJob implements IJob{
|
||||||
|
@Logger()
|
||||||
|
logger: ILogger;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
logisticsService: LogisticsService;
|
||||||
|
|
||||||
|
@InjectEntityModel(Shipment)
|
||||||
|
shipmentModel: Repository<Shipment>
|
||||||
|
|
||||||
|
uniuniStateCodes = {
|
||||||
|
'190': 'ORDER_RECEIVED',
|
||||||
|
'192': 'CUSTOM_HOLD',
|
||||||
|
'195': 'GATEWAY_TRANSIT_OUT',
|
||||||
|
'198': 'CUSTOM_RELEASE_DIRECT',
|
||||||
|
'199': 'GATEWAY_TRANSIT',
|
||||||
|
'200': 'PARCEL_SCANNED',
|
||||||
|
'202': 'IN_TRANSIT',
|
||||||
|
'203': 'DELIVERED',
|
||||||
|
'204': 'TRANSSHIPMENT',
|
||||||
|
'206': 'WRONG_ADDRESS_FROM_TRANSIT',
|
||||||
|
'207': 'PARCEL_LOST',
|
||||||
|
'209': 'OTHER_EXCEPTION',
|
||||||
|
'211': 'RETURN_OFFICE_FROM_TRANSIT',
|
||||||
|
'212': 'WRONG_ADDRESS_FROM_RECEIVE',
|
||||||
|
'213': 'STORAGE_30_DAYS_FROM_OFFICE',
|
||||||
|
'214': 'STORAGE_30_DAYS_AFTER_SCAN',
|
||||||
|
'215': 'PARCEL_ABANDONED',
|
||||||
|
'216': 'SELF_PICK_UP',
|
||||||
|
'217': 'TRANSSHIPMENT_COMPLETE',
|
||||||
|
'218': 'SCANNED_PARCEL_MISSING',
|
||||||
|
'219': 'WRONG_ROUTE_PARCEL',
|
||||||
|
'220': 'SECOND_DELIVERY',
|
||||||
|
'221': 'RETURNED_PARCEL_SCANNED',
|
||||||
|
'222': 'REJECTED_PARCEL_FROM_TRANSIT',
|
||||||
|
'223': 'CHANGED_ORDER_RESENT',
|
||||||
|
'224': 'RESENT_ORDER_VOIDED',
|
||||||
|
'225': 'FORWARDED_3RDPARTY',
|
||||||
|
'226': 'STORAGE_3RDPARTY_SERVICE_POINT',
|
||||||
|
'228': 'SECOND_DELIVERED',
|
||||||
|
'229': 'DROP_OFF_SERVICE_POINTS',
|
||||||
|
'230': 'RETURN TO SENDER WAREHOUSE',
|
||||||
|
'231': 'FAILED_DELIVERY_RETRY1',
|
||||||
|
'232': 'FAILED_DELIVERY_RETRY2',
|
||||||
|
'255': 'Gateway_To_Gateway_Transit'
|
||||||
|
};
|
||||||
|
async onTick() {
|
||||||
|
const shipments:Shipment[] = await this.shipmentModel.findBy({ finished: false });
|
||||||
|
shipments.forEach(shipment => {
|
||||||
|
this.logisticsService.updateShipmentState(shipment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onComplete(result: any) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -157,7 +157,7 @@ export class CanadaPostService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 取消运单 */
|
/** 取消运单 */
|
||||||
async cancelShipment(shipmentId: string) {
|
async cancelShipment(shipmentId: number) {
|
||||||
const url = `${this.url}/rs/${this.customerNumber}/${this.customerNumber}/shipment/${shipmentId}`;
|
const url = `${this.url}/rs/${this.customerNumber}/${this.customerNumber}/shipment/${shipmentId}`;
|
||||||
|
|
||||||
const res = await axios.delete(url, {
|
const res = await axios.delete(url, {
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ export class FreightcomService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询运单详细信息
|
// 查询运单详细信息
|
||||||
async getShipment(shipment_id: string) {
|
async getShipment(shipment_id: number) {
|
||||||
let { status, data } = await axios.request({
|
let { status, data } = await axios.request({
|
||||||
url: `${this.apiUrl}/shipment/${shipment_id}`,
|
url: `${this.apiUrl}/shipment/${shipment_id}`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
@ -117,7 +117,7 @@ export class FreightcomService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消发货
|
// 取消发货
|
||||||
async cancelShipment(shipment_id: string) {
|
async cancelShipment(shipment_id: number) {
|
||||||
const response = await axios.request({
|
const response = await axios.request({
|
||||||
url: `${this.apiUrl}/shipment/${shipment_id}`,
|
url: `${this.apiUrl}/shipment/${shipment_id}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
|
|
|
||||||
|
|
@ -3,30 +3,34 @@ import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
|
||||||
import { Service } from '../entity/service.entity';
|
import { Service } from '../entity/service.entity';
|
||||||
import { In, IsNull, Like, Repository } from 'typeorm';
|
import { In, IsNull, Like, Repository } from 'typeorm';
|
||||||
import { ShippingAddress } from '../entity/shipping_address.entity';
|
import { ShippingAddress } from '../entity/shipping_address.entity';
|
||||||
// import { ShipmentBookDTO } from '../dto/logistics.dto';
|
|
||||||
import { Order } from '../entity/order.entity';
|
import { Order } from '../entity/order.entity';
|
||||||
import { Shipment } from '../entity/shipment.entity';
|
import { Shipment } from '../entity/shipment.entity';
|
||||||
import { ShipmentItem } from '../entity/shipment_item.entity';
|
import { ShipmentItem } from '../entity/shipment_item.entity';
|
||||||
import { OrderShipment } from '../entity/order_shipment.entity';
|
import { OrderShipment } from '../entity/order_shipment.entity';
|
||||||
import { QueryServiceDTO, ShipmentBookDTO } from '../dto/logistics.dto';
|
import { QueryServiceDTO, ShipmentBookDTO, ShipmentFeeBookDTO } from '../dto/logistics.dto';
|
||||||
import {
|
import {
|
||||||
ErpOrderStatus,
|
ErpOrderStatus,
|
||||||
OrderStatus,
|
OrderStatus,
|
||||||
ShipmentType,
|
ShipmentType,
|
||||||
StockRecordOperationType,
|
StockRecordOperationType,
|
||||||
} from '../enums/base.enum';
|
} from '../enums/base.enum';
|
||||||
import { generateUniqueId } from '../utils/helper.util';
|
// import { generateUniqueId } from '../utils/helper.util';
|
||||||
import { FreightcomService } from './freightcom.service';
|
import { FreightcomService } from './freightcom.service';
|
||||||
import { StockRecord } from '../entity/stock_record.entity';
|
import { StockRecord } from '../entity/stock_record.entity';
|
||||||
import { Stock } from '../entity/stock.entity';
|
import { Stock } from '../entity/stock.entity';
|
||||||
import { plainToClass } from 'class-transformer';
|
import { plainToClass } from 'class-transformer';
|
||||||
import { WPService } from './wp.service';
|
import { WPService } from './wp.service';
|
||||||
import { WpSite } from '../interface';
|
import { WpSite } from '../interface';
|
||||||
import { Product } from '../entity/product.entty';
|
// import { Product } from '../entity/product.entty';
|
||||||
import { ShippingDetailsDTO } from '../dto/freightcom.dto';
|
import { ShippingDetailsDTO } from '../dto/freightcom.dto';
|
||||||
import { CanadaPostService } from './canadaPost.service';
|
import { CanadaPostService } from './canadaPost.service';
|
||||||
import { OrderItem } from '../entity/order_item.entity';
|
import { OrderItem } from '../entity/order_item.entity';
|
||||||
import { OrderSale } from '../entity/order_sale.entity';
|
import { OrderSale } from '../entity/order_sale.entity';
|
||||||
|
import { UniExpressService } from './uni_express.service';
|
||||||
|
import { StockPoint } from '../entity/stock_point.entity';
|
||||||
|
import { OrderService } from './order.service';
|
||||||
|
import { convertKeysFromCamelToSnake } from '../utils/object-transform.util';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class LogisticsService {
|
export class LogisticsService {
|
||||||
|
|
@ -42,6 +46,9 @@ export class LogisticsService {
|
||||||
@InjectEntityModel(Order)
|
@InjectEntityModel(Order)
|
||||||
orderModel: Repository<Order>;
|
orderModel: Repository<Order>;
|
||||||
|
|
||||||
|
@InjectEntityModel(StockPoint)
|
||||||
|
stockPointModel: Repository<StockPoint>
|
||||||
|
|
||||||
@InjectEntityModel(OrderSale)
|
@InjectEntityModel(OrderSale)
|
||||||
orderSaleModel: Repository<OrderSale>;
|
orderSaleModel: Repository<OrderSale>;
|
||||||
|
|
||||||
|
|
@ -63,16 +70,22 @@ export class LogisticsService {
|
||||||
@Inject()
|
@Inject()
|
||||||
canadaPostService: CanadaPostService;
|
canadaPostService: CanadaPostService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
uniExpressService: UniExpressService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
wpService: WPService;
|
wpService: WPService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
orderService: OrderService;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
dataSourceManager: TypeORMDataSourceManager;
|
dataSourceManager: TypeORMDataSourceManager;
|
||||||
|
|
||||||
@Config('wpSite')
|
@Config('wpSite')
|
||||||
sites: WpSite[];
|
sites: WpSite[];
|
||||||
|
|
||||||
geSite(id: string): WpSite {
|
getSite(id: string): WpSite {
|
||||||
let idx = this.sites.findIndex(item => item.id === id);
|
let idx = this.sites.findIndex(item => item.id === id);
|
||||||
return this.sites[idx];
|
return this.sites[idx];
|
||||||
}
|
}
|
||||||
|
|
@ -113,6 +126,27 @@ export class LogisticsService {
|
||||||
return await this.shippingAddressModel.save(shippingAddress);
|
return await this.shippingAddressModel.save(shippingAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateShipmentState(shipment: Shipment) {
|
||||||
|
try {
|
||||||
|
const data = await this.uniExpressService.getOrderStatus(shipment.return_tracking_number);
|
||||||
|
console.log('updateShipmentState data:', data);
|
||||||
|
shipment.state = data.data[0].state;
|
||||||
|
if (shipment.state in [203, 215, 216, 230]) { // todo,写常数
|
||||||
|
shipment.finished = true;
|
||||||
|
}
|
||||||
|
this.shipmentModel.save(shipment);
|
||||||
|
return shipment.state;
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
// throw new Error(`更新运单状态失败 ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateShipmentStateById(id: number) {
|
||||||
|
const shipment: Shipment = await this.shipmentModel.findOneBy({ id: id });
|
||||||
|
return this.updateShipmentState(shipment);
|
||||||
|
}
|
||||||
|
|
||||||
async updateShippingAddress(id: number, shippingAddress: ShippingAddress) {
|
async updateShippingAddress(id: number, shippingAddress: ShippingAddress) {
|
||||||
const address = await this.shippingAddressModel.findOneBy({ id });
|
const address = await this.shippingAddressModel.findOneBy({ id });
|
||||||
if (!address) {
|
if (!address) {
|
||||||
|
|
@ -184,8 +218,96 @@ export class LogisticsService {
|
||||||
return [...rates, ...canadaPostRates];
|
return [...rates, ...canadaPostRates];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getShipmentLabel(shipmentId) {
|
||||||
|
try {
|
||||||
|
const shipment: Shipment = await this.shipmentModel.findOneBy({ id: shipmentId });
|
||||||
|
if (!shipment) {
|
||||||
|
throw new Error('运单不存在');
|
||||||
|
}
|
||||||
|
return await this.uniExpressService.getLabel(shipment.return_tracking_number);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('获取运单失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeShipment(shipmentId: number) {
|
||||||
|
try {
|
||||||
|
const shipment: Shipment = await this.shipmentModel.findOneBy({ id: shipmentId });
|
||||||
|
if (shipment.state !== '190') { // todo,写常数
|
||||||
|
throw new Error('订单当前状态无法删除');
|
||||||
|
}
|
||||||
|
const order: Order = await this.orderModel.findOneBy({ id: shipment.order_id });
|
||||||
|
const dataSource = this.dataSourceManager.getDataSource('default');
|
||||||
|
let transactionError = undefined;
|
||||||
|
await dataSource.transaction(async manager => {
|
||||||
|
const orderRepo = manager.getRepository(Order);
|
||||||
|
const shipmentRepo = manager.getRepository(Shipment);
|
||||||
|
|
||||||
|
order.shipmentId = null;
|
||||||
|
orderRepo.save(order);
|
||||||
|
|
||||||
|
shipmentRepo.remove(shipment);
|
||||||
|
|
||||||
|
const res = await this.uniExpressService.deleteShipment(shipment.return_tracking_number);
|
||||||
|
console.log('res', res.data); // todo
|
||||||
|
|
||||||
|
await orderRepo.save(order);
|
||||||
|
|
||||||
|
}).catch(error => {
|
||||||
|
transactionError = error;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (transactionError !== undefined) {
|
||||||
|
throw new Error(`数据库同步错误: ${transactionError.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 同步订单状态到woocommerce
|
||||||
|
const site = await this.getSite(order.siteId);
|
||||||
|
if (order.status === OrderStatus.COMPLETED) {
|
||||||
|
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||||
|
status: OrderStatus.PROCESSING,
|
||||||
|
});
|
||||||
|
order.status = OrderStatus.PROCESSING;
|
||||||
|
}
|
||||||
|
order.orderStatus = ErpOrderStatus.PROCESSING;
|
||||||
|
this.orderModel.save(order);
|
||||||
|
|
||||||
|
// todo 同步到wooccommerce删除运单信息
|
||||||
|
await this.wpService.deleteShipment(site, order.externalOrderId, shipment.tracking_id);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('同步到woocommerce失败', error);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
throw new Error('删除运单失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getShipmentFee(data: ShipmentFeeBookDTO) {
|
||||||
|
try {
|
||||||
|
const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId });
|
||||||
|
const reqBody = {
|
||||||
|
...convertKeysFromCamelToSnake(data),
|
||||||
|
pickup_warehouse: stock_point.upStreamStockPointId,
|
||||||
|
currency: 'CAD',
|
||||||
|
// item_description: data.sales, // todo: 货品信息
|
||||||
|
}
|
||||||
|
const resShipmentFee = await this.uniExpressService.getRates(reqBody);
|
||||||
|
if (resShipmentFee.status !== 'SUCCESS') {
|
||||||
|
throw new Error(resShipmentFee.ret_msg);
|
||||||
|
}
|
||||||
|
return resShipmentFee.data.totalAfterTax * 100;
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createShipment(orderId: number, data: ShipmentBookDTO, userId: number) {
|
async createShipment(orderId: number, data: ShipmentBookDTO, userId: number) {
|
||||||
const order = await this.orderModel.findOneBy({ id: orderId });
|
const order = await this.orderModel.findOneBy({ id: orderId });
|
||||||
|
const { sales } = data;
|
||||||
if (!order) {
|
if (!order) {
|
||||||
throw new Error('订单不存在');
|
throw new Error('订单不存在');
|
||||||
}
|
}
|
||||||
|
|
@ -195,147 +317,110 @@ export class LogisticsService {
|
||||||
) {
|
) {
|
||||||
throw new Error('订单状态不正确 ');
|
throw new Error('订单状态不正确 ');
|
||||||
}
|
}
|
||||||
// for (const item of data?.sales) {
|
|
||||||
// const stock = await this.stockModel.findOne({
|
|
||||||
// where: {
|
|
||||||
// stockPointId: data.stockPointId,
|
|
||||||
// productSku: item.sku,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// if (!stock || stock.quantity < item.quantity)
|
|
||||||
// throw new Error(item.name + '库存不足');
|
|
||||||
// }
|
|
||||||
let shipment: Shipment;
|
|
||||||
|
|
||||||
if (data.service_type === ShipmentType.FREIGHTCOM) {
|
let resShipmentOrder;
|
||||||
const uuid = generateUniqueId();
|
try {
|
||||||
data.details.reference_codes = [String(orderId)];
|
const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId });
|
||||||
const { id } = await this.freightcomService.createShipment({
|
const reqBody = {
|
||||||
unique_id: uuid,
|
sender: data.details.origin.contact_name,
|
||||||
payment_method_id: data.payment_method_id,
|
start_phone: data.details.origin.phone_number,
|
||||||
service_id: data.service_id,
|
start_postal_code: data.details.origin.address.postal_code.replace(/\s/g, ''),
|
||||||
details: data.details,
|
pickup_address: data.details.origin.address.address_line_1,
|
||||||
});
|
pickup_warehouse: stock_point.upStreamStockPointId,
|
||||||
|
shipper_country_code: data.details.origin.address.country,
|
||||||
const service = await this.serviceModel.findOneBy({
|
receiver: data.details.destination.contact_name,
|
||||||
id: data.service_id,
|
|
||||||
});
|
|
||||||
shipment = {
|
|
||||||
id,
|
|
||||||
unique_id: uuid,
|
|
||||||
tracking_provider: service?.carrier_name || '',
|
|
||||||
};
|
|
||||||
} else if (data.service_type === ShipmentType.CANADAPOST) {
|
|
||||||
const shipmentRequest = {
|
|
||||||
'transmit-shipment': true,
|
|
||||||
'requested-shipping-point':
|
|
||||||
data.details.origin.address.postal_code.replace(/\s/g, ''),
|
|
||||||
'delivery-spec': {
|
|
||||||
'service-code': data.service_id,
|
|
||||||
sender: {
|
|
||||||
name: data.details.origin.name,
|
|
||||||
company: data.details.origin.name,
|
|
||||||
'contact-phone': data.details.origin.phone_number.number,
|
|
||||||
'address-details': {
|
|
||||||
'address-line-1': data.details.origin.address.address_line_1,
|
|
||||||
city: data.details.origin.address.city,
|
|
||||||
'prov-state': data.details.origin.address.region,
|
|
||||||
'postal-zip-code':
|
|
||||||
data.details.origin.address.postal_code.replace(/\s/g, ''),
|
|
||||||
'country-code': data.details.origin.address.country,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
name: data.details.destination.contact_name,
|
|
||||||
company: data.details.destination.name,
|
|
||||||
'address-details': {
|
|
||||||
'address-line-1': data.details.destination.address.address_line_1,
|
|
||||||
city: data.details.destination.address.city,
|
city: data.details.destination.address.city,
|
||||||
'prov-state': data.details.destination.address.region,
|
province: data.details.destination.address.region,
|
||||||
'postal-zip-code':
|
country: data.details.destination.address.country,
|
||||||
data.details.destination.address.postal_code.replace(/\s/g, ''),
|
postal_code: data.details.destination.address.postal_code.replace(/\s/g, ''),
|
||||||
'country-code': data.details.destination.address.country,
|
delivery_address: data.details.destination.address.address_line_1,
|
||||||
},
|
receiver_phone: data.details.destination.phone_number.number,
|
||||||
},
|
receiver_email: data.details.destination.email_addresses,
|
||||||
'parcel-characteristics': {
|
// item_description: data.sales, // todo: 货品信息
|
||||||
weight: data.details.packaging_properties.packages?.reduce(
|
length: data.details.packaging_properties.packages[0].measurements.cuboid.l,
|
||||||
(cur, next) => cur + (next?.measurements?.weight?.value || 0),
|
width: data.details.packaging_properties.packages[0].measurements.cuboid.w,
|
||||||
0
|
height: data.details.packaging_properties.packages[0].measurements.cuboid.h,
|
||||||
),
|
dimension_uom: data.details.packaging_properties.packages[0].measurements.cuboid.unit,
|
||||||
},
|
weight: data.details.packaging_properties.packages[0].measurements.weight.value,
|
||||||
preferences: {
|
weight_uom: data.details.packaging_properties.packages[0].measurements.weight.unit,
|
||||||
'show-packing-instructions': true,
|
currency: 'CAD',
|
||||||
},
|
custom_field: {
|
||||||
'settlement-info': {
|
'order_id': order.externalOrderId
|
||||||
'contract-id': this.canadaPostService.contractId,
|
}
|
||||||
'intended-method-of-payment': 'CreditCard',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
shipment = await this.canadaPostService.createShipment(shipmentRequest);
|
|
||||||
}
|
}
|
||||||
shipment.type = data.service_type;
|
|
||||||
|
|
||||||
|
// 添加运单
|
||||||
|
resShipmentOrder = await this.uniExpressService.createShipment(reqBody);
|
||||||
|
|
||||||
|
// 记录物流信息,并将订单状态转到完成
|
||||||
|
if (resShipmentOrder.status === 'SUCCESS') {
|
||||||
|
order.orderStatus = ErpOrderStatus.COMPLETED;
|
||||||
|
} else {
|
||||||
|
throw new Error('运单生成失败');
|
||||||
|
}
|
||||||
const dataSource = this.dataSourceManager.getDataSource('default');
|
const dataSource = this.dataSourceManager.getDataSource('default');
|
||||||
return dataSource.transaction(async manager => {
|
let transactionError = undefined;
|
||||||
const productRepo = manager.getRepository(Product);
|
let shipmentId = undefined;
|
||||||
const shipmentRepo = manager.getRepository(Shipment);
|
await dataSource.transaction(async manager => {
|
||||||
const shipmentItemRepo = manager.getRepository(ShipmentItem);
|
|
||||||
const orderShipmentRepo = manager.getRepository(OrderShipment);
|
|
||||||
const stockRecordRepo = manager.getRepository(StockRecord);
|
|
||||||
const stockRepo = manager.getRepository(Stock);
|
|
||||||
const orderRepo = manager.getRepository(Order);
|
const orderRepo = manager.getRepository(Order);
|
||||||
|
const shipmentRepo = manager.getRepository(Shipment);
|
||||||
|
const tracking_provider = 'UniUni'; // todo: id未确定,后写进常数
|
||||||
|
|
||||||
await shipmentRepo.save(shipment);
|
// 同步物流信息到woocommerce
|
||||||
await this.getShipment(shipment.id);
|
const site = await this.getSite(order.siteId);
|
||||||
const shipmentItems = [];
|
const res = await this.wpService.createShipment(site, order.externalOrderId, {
|
||||||
for (const item of data?.sales) {
|
tracking_number: resShipmentOrder.data.tno,
|
||||||
const product = await productRepo.findOne({ where: { sku: item.sku } });
|
tracking_provider: tracking_provider,
|
||||||
shipmentItems.push({
|
|
||||||
shipment_id: shipment.id,
|
|
||||||
productId: product.id,
|
|
||||||
name: product.name,
|
|
||||||
sku: item.sku,
|
|
||||||
quantity: item.quantity,
|
|
||||||
});
|
});
|
||||||
const stock = await stockRepo.findOne({
|
|
||||||
where: {
|
if (order.orderStatus === ErpOrderStatus.COMPLETED) {
|
||||||
stockPointId: data.stockPointId,
|
const shipment = await shipmentRepo.save({
|
||||||
productSku: item.sku,
|
tracking_provider: tracking_provider,
|
||||||
},
|
tracking_id: res.data.tracking_id,
|
||||||
});
|
unique_id: resShipmentOrder.data.uni_order_sn,
|
||||||
stock.quantity -= item.quantity;
|
stockPointId: String(data.stockPointId), // todo
|
||||||
await stockRepo.save(stock);
|
state: resShipmentOrder.data.uni_status_code,
|
||||||
await stockRecordRepo.save({
|
return_tracking_number: resShipmentOrder.data.tno,
|
||||||
stockPointId: data.stockPointId,
|
fee: data.details.shipmentFee,
|
||||||
productSku: item.sku,
|
order: order
|
||||||
operationType: StockRecordOperationType.OUT,
|
|
||||||
quantityChange: item.quantity,
|
|
||||||
operatorId: userId,
|
|
||||||
note: `订单${[orderId, ...data.orderIds].join(',')} 发货`,
|
|
||||||
});
|
});
|
||||||
|
order.shipmentId = shipment.id;
|
||||||
|
shipmentId = shipment.id;
|
||||||
}
|
}
|
||||||
await shipmentItemRepo.save(shipmentItems);
|
|
||||||
await orderShipmentRepo.save({
|
// 同步订单状态到woocommerce
|
||||||
order_id: orderId,
|
if (order.status !== OrderStatus.COMPLETED) {
|
||||||
shipment_id: shipment.id,
|
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||||
stockPointId: data.stockPointId,
|
status: OrderStatus.COMPLETED,
|
||||||
});
|
});
|
||||||
for (const orderId of data?.orderIds) {
|
|
||||||
await orderShipmentRepo.save({
|
|
||||||
order_id: orderId,
|
|
||||||
shipment_id: shipment.id,
|
|
||||||
stockPointId: data.stockPointId,
|
|
||||||
});
|
|
||||||
const order = await orderRepo.findOneBy({ id: orderId });
|
|
||||||
order.orderStatus = ErpOrderStatus.COMPLETED;
|
|
||||||
order.status = OrderStatus.COMPLETED;
|
order.status = OrderStatus.COMPLETED;
|
||||||
await orderRepo.save(order);
|
|
||||||
}
|
}
|
||||||
order.orderStatus = ErpOrderStatus.COMPLETED;
|
order.orderStatus = ErpOrderStatus.COMPLETED;
|
||||||
order.status = OrderStatus.COMPLETED;
|
|
||||||
await orderRepo.save(order);
|
await orderRepo.save(order);
|
||||||
|
}).catch(error => {
|
||||||
|
transactionError = error
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (transactionError !== undefined) {
|
||||||
|
console.log('err', transactionError);
|
||||||
|
throw transactionError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新产品发货信息
|
||||||
|
this.orderService.updateOrderSales(order.id, sales);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
shipmentId
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (resShipmentOrder.status === 'SUCCESS') {
|
||||||
|
await this.uniExpressService.deleteShipment(resShipmentOrder.data.tno);
|
||||||
|
}
|
||||||
|
throw new Error(`上游请求错误:${error}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncShipment() {
|
async syncShipment() {
|
||||||
|
|
@ -386,8 +471,8 @@ export class LogisticsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getShipment(id: string) {
|
async getShipment(id: number) {
|
||||||
const orderShipments = await this.orderShipmentModel.find({
|
const orderShipments: OrderShipment[] = await this.orderShipmentModel.find({
|
||||||
where: { shipment_id: id },
|
where: { shipment_id: id },
|
||||||
});
|
});
|
||||||
if (!orderShipments || orderShipments.length === 0) return;
|
if (!orderShipments || orderShipments.length === 0) return;
|
||||||
|
|
@ -408,7 +493,7 @@ export class LogisticsService {
|
||||||
const order = await this.orderModel.findOneBy({
|
const order = await this.orderModel.findOneBy({
|
||||||
id: orderShipment.order_id,
|
id: orderShipment.order_id,
|
||||||
});
|
});
|
||||||
const site = this.geSite(order.siteId);
|
const site = this.getSite(order.siteId);
|
||||||
await this.wpService.updateOrder(site, order.externalOrderId, {
|
await this.wpService.updateOrder(site, order.externalOrderId, {
|
||||||
status: OrderStatus.COMPLETED,
|
status: OrderStatus.COMPLETED,
|
||||||
});
|
});
|
||||||
|
|
@ -419,7 +504,8 @@ export class LogisticsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async delShipment(id: string, userId: number) {
|
async delShipment(id: number, userId: number) {
|
||||||
|
|
||||||
const shipment = await this.shipmentModel.findOneBy({ id });
|
const shipment = await this.shipmentModel.findOneBy({ id });
|
||||||
if (!shipment) throw new Error('物流不存在');
|
if (!shipment) throw new Error('物流不存在');
|
||||||
if (shipment.type === ShipmentType.FREIGHTCOM) {
|
if (shipment.type === ShipmentType.FREIGHTCOM) {
|
||||||
|
|
@ -470,7 +556,7 @@ export class LogisticsService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTrackingNumber(number: string) {
|
async getOrderList(number: string) {
|
||||||
const orders = await this.orderModel.find({
|
const orders = await this.orderModel.find({
|
||||||
where: {
|
where: {
|
||||||
externalOrderId: Like(`%${number}%`),
|
externalOrderId: Like(`%${number}%`),
|
||||||
|
|
@ -485,88 +571,51 @@ export class LogisticsService {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getListByTrackingId(id: string) {
|
async getListByOrderId(id: number) {
|
||||||
const qb = `
|
const item = await this.orderItem.find({ where: { orderId: id } });
|
||||||
SELECT
|
const saleItem = await this.orderSaleModel.find({ where: { orderId: id } });
|
||||||
oi.name,
|
|
||||||
oi.quantity,
|
|
||||||
CASE
|
|
||||||
WHEN oi.externalVariationId != 0 THEN v.constitution
|
|
||||||
ELSE p.constitution
|
|
||||||
END AS constitution
|
|
||||||
FROM order_item oi
|
|
||||||
LEFT JOIN wp_product p ON oi.siteId=p.siteId AND oi.externalProductId=p.externalProductId
|
|
||||||
LEFT JOIN variation v ON oi.siteId=v.siteId AND oi.externalVariationId=v.externalVariationId
|
|
||||||
WHERE oi.orderId=?
|
|
||||||
`;
|
|
||||||
const saleItem = await this.orderSaleModel.query(qb, [id]);
|
|
||||||
const allSkus = new Set<string>();
|
|
||||||
for (const item of saleItem) {
|
|
||||||
if (!item.constitution) continue;
|
|
||||||
try {
|
|
||||||
item.constitution.forEach(c => allSkus.add(c.sku));
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Invalid constitution JSON:', item.constitution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(allSkus);
|
|
||||||
const skuList = Array.from(allSkus);
|
|
||||||
let skuNameMap = new Map<string, string>();
|
|
||||||
|
|
||||||
if (skuList.length > 0) {
|
return {
|
||||||
const placeholders = skuList.map(() => '?').join(', ');
|
item,
|
||||||
const productRows = await this.orderSaleModel.query(
|
saleItem
|
||||||
`SELECT sku, name FROM product WHERE sku IN (${placeholders})`,
|
};
|
||||||
skuList
|
|
||||||
);
|
|
||||||
skuNameMap = new Map(productRows.map(p => [p.sku, p.name]));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const item of saleItem) {
|
|
||||||
if (!item.constitution) continue;
|
|
||||||
try {
|
|
||||||
item.constitution = item.constitution.map(c => ({
|
|
||||||
...c,
|
|
||||||
name: skuNameMap.get(c.sku) || null,
|
|
||||||
}));
|
|
||||||
} catch (e) {
|
|
||||||
item.constitution = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return saleItem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getList(param: Record<string, any>) {
|
async getList(param: Record<string, any>) {
|
||||||
const {
|
const {
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
current = 1,
|
current = 1,
|
||||||
primary_tracking_number,
|
return_tracking_number,
|
||||||
stockPointId,
|
stockPointId,
|
||||||
|
externalOrderId,
|
||||||
} = param;
|
} = param;
|
||||||
console.log(pageSize, current, primary_tracking_number, stockPointId);
|
|
||||||
|
|
||||||
const offset = pageSize * (current - 1);
|
const offset = pageSize * (current - 1);
|
||||||
const values: any[] = [];
|
const values: any[] = [];
|
||||||
let whereClause = 'WHERE 1=1';
|
let whereClause = 'WHERE 1=1';
|
||||||
|
|
||||||
if (primary_tracking_number) {
|
if (return_tracking_number) {
|
||||||
whereClause += ' AND s.primary_tracking_number LIKE ?';
|
whereClause += ' AND s.return_tracking_number LIKE ?';
|
||||||
values.push(`%${primary_tracking_number}%`);
|
values.push(`%${return_tracking_number}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stockPointId) {
|
if (stockPointId) {
|
||||||
whereClause += ' AND os.stockPointId = ?';
|
whereClause += ' AND sp.id = ?';
|
||||||
values.push(stockPointId);
|
values.push(stockPointId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo,增加订单号搜索
|
||||||
|
if (externalOrderId) {
|
||||||
|
whereClause += ' AND o.externalOrderId = ?';
|
||||||
|
values.push(externalOrderId);
|
||||||
|
}
|
||||||
|
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT s.*, sp.name
|
SELECT s.*, sp.name, o.externalOrderId, o.siteId
|
||||||
FROM shipment s
|
FROM shipment s
|
||||||
LEFT JOIN order_shipment os ON s.id = os.shipment_id
|
LEFT JOIN \`order\` o ON s.order_id = o.id
|
||||||
LEFT JOIN stock_point sp ON os.stockPointId = sp.id
|
LEFT JOIN stock_point sp ON s.stock_point_id = sp.id
|
||||||
${whereClause}
|
${whereClause}
|
||||||
GROUP BY s.id
|
|
||||||
ORDER BY s.createdAt DESC
|
ORDER BY s.createdAt DESC
|
||||||
LIMIT ?, ?
|
LIMIT ?, ?
|
||||||
`;
|
`;
|
||||||
|
|
@ -574,12 +623,12 @@ export class LogisticsService {
|
||||||
values.push(offset, Number(pageSize));
|
values.push(offset, Number(pageSize));
|
||||||
|
|
||||||
const items = await this.serviceModel.query(sql, values);
|
const items = await this.serviceModel.query(sql, values);
|
||||||
|
|
||||||
// 单独计算总数
|
// 单独计算总数
|
||||||
const countSql = `
|
const countSql = `
|
||||||
SELECT COUNT(DISTINCT s.id) as total
|
SELECT COUNT(DISTINCT s.id) as total
|
||||||
FROM shipment s
|
FROM shipment s
|
||||||
LEFT JOIN order_shipment os ON s.id = os.shipment_id
|
LEFT JOIN \`order\` o ON s.order_id = o.id
|
||||||
|
LEFT JOIN stock_point sp ON s.stock_point_id = sp.id
|
||||||
${whereClause}
|
${whereClause}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import { WpSite } from '../interface';
|
||||||
import { ShipmentItem } from '../entity/shipment_item.entity';
|
import { ShipmentItem } from '../entity/shipment_item.entity';
|
||||||
import { UpdateStockDTO } from '../dto/stock.dto';
|
import { UpdateStockDTO } from '../dto/stock.dto';
|
||||||
import { StockService } from './stock.service';
|
import { StockService } from './stock.service';
|
||||||
|
import { OrderSaleOriginal } from '../entity/order_item_original.entity';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class OrderService {
|
export class OrderService {
|
||||||
|
|
@ -44,12 +45,18 @@ export class OrderService {
|
||||||
@InjectEntityModel(Order)
|
@InjectEntityModel(Order)
|
||||||
orderModel: Repository<Order>;
|
orderModel: Repository<Order>;
|
||||||
|
|
||||||
|
@InjectEntityModel(User)
|
||||||
|
userModel: Repository<User>;
|
||||||
|
|
||||||
@InjectEntityModel(OrderItem)
|
@InjectEntityModel(OrderItem)
|
||||||
orderItemModel: Repository<OrderItem>;
|
orderItemModel: Repository<OrderItem>;
|
||||||
|
|
||||||
@InjectEntityModel(OrderSale)
|
@InjectEntityModel(OrderSale)
|
||||||
orderSaleModel: Repository<OrderSale>;
|
orderSaleModel: Repository<OrderSale>;
|
||||||
|
|
||||||
|
@InjectEntityModel(OrderSaleOriginal)
|
||||||
|
orderSaleOriginalModel: Repository<OrderSaleOriginal>;
|
||||||
|
|
||||||
@InjectEntityModel(WpProduct)
|
@InjectEntityModel(WpProduct)
|
||||||
wpProductModel: Repository<WpProduct>;
|
wpProductModel: Repository<WpProduct>;
|
||||||
|
|
||||||
|
|
@ -202,7 +209,6 @@ 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;
|
||||||
|
|
||||||
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 },
|
||||||
|
|
@ -543,7 +549,7 @@ export class OrderService {
|
||||||
current,
|
current,
|
||||||
pageSize,
|
pageSize,
|
||||||
customer_email,
|
customer_email,
|
||||||
}) {
|
}, userId = undefined) {
|
||||||
const parameters: any[] = [];
|
const parameters: any[] = [];
|
||||||
|
|
||||||
// 基础查询
|
// 基础查询
|
||||||
|
|
@ -626,6 +632,14 @@ export class OrderService {
|
||||||
totalQuery += ` AND o.date_created <= ?`;
|
totalQuery += ` AND o.date_created <= ?`;
|
||||||
parameters.push(endDate);
|
parameters.push(endDate);
|
||||||
}
|
}
|
||||||
|
const user = await this.userModel.findOneBy({id: userId});
|
||||||
|
if (user?.permissions?.includes('order-10-days')) {
|
||||||
|
sqlQuery += ` AND o.date_created >= ?`;
|
||||||
|
totalQuery += ` AND o.date_created >= ?`;
|
||||||
|
const tenDaysAgo = new Date();
|
||||||
|
tenDaysAgo.setDate(tenDaysAgo.getDate() - 10);
|
||||||
|
parameters.push(tenDaysAgo.toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
// 处理 status 参数
|
// 处理 status 参数
|
||||||
if (status) {
|
if (status) {
|
||||||
|
|
@ -735,298 +749,202 @@ export class OrderService {
|
||||||
return await query.getRawMany();
|
return await query.getRawMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrderSales({ siteId, startDate, endDate, current, pageSize, name }: QueryOrderSalesDTO) {
|
async getOrderSales({ siteId, startDate, endDate, current, pageSize, name, exceptPackage }: QueryOrderSalesDTO) {
|
||||||
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
||||||
|
const offset = (current - 1) * pageSize;
|
||||||
|
|
||||||
const parameters: any[] = [startDate, endDate];
|
// -------------------------
|
||||||
|
// 1. 查询总条数
|
||||||
// 主查询:带分页
|
// -------------------------
|
||||||
let sqlQuery = `
|
|
||||||
WITH product_purchase_counts AS (
|
|
||||||
SELECT
|
|
||||||
o.customer_email,
|
|
||||||
os.productId,
|
|
||||||
COUNT(DISTINCT o.id) AS order_count
|
|
||||||
FROM \`order\` o
|
|
||||||
JOIN order_sale os ON o.id = os.orderId
|
|
||||||
WHERE o.status IN ('completed', 'processing')
|
|
||||||
GROUP BY o.customer_email, os.productId
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
os.productId AS productId,
|
|
||||||
os.name AS name,
|
|
||||||
SUM(os.quantity) AS totalQuantity,
|
|
||||||
COUNT(DISTINCT os.orderId) AS totalOrders,
|
|
||||||
c.name AS categoryName,
|
|
||||||
COUNT(DISTINCT CASE WHEN pc.order_count = 1 THEN o.id END) AS firstOrderCount,
|
|
||||||
SUM(CASE WHEN pc.order_count = 1 THEN os.quantity ELSE 0 END) AS firstOrderYOONEBoxCount,
|
|
||||||
COUNT(DISTINCT CASE WHEN pc.order_count = 2 THEN o.id END) AS secondOrderCount,
|
|
||||||
SUM(CASE WHEN pc.order_count = 2 THEN os.quantity ELSE 0 END) AS secondOrderYOONEBoxCount,
|
|
||||||
COUNT(DISTINCT CASE WHEN pc.order_count = 3 THEN o.id END) AS thirdOrderCount,
|
|
||||||
SUM(CASE WHEN pc.order_count = 3 THEN os.quantity ELSE 0 END) AS thirdOrderYOONEBoxCount,
|
|
||||||
COUNT(DISTINCT CASE WHEN pc.order_count > 3 THEN o.id END) AS moreThirdOrderCount,
|
|
||||||
SUM(CASE WHEN pc.order_count > 3 THEN os.quantity ELSE 0 END) AS moreThirdOrderYOONEBoxCount
|
|
||||||
FROM order_sale os
|
|
||||||
INNER JOIN \`order\` o ON o.id = os.orderId
|
|
||||||
INNER JOIN product p ON os.productId = p.id
|
|
||||||
INNER JOIN category c ON p.categoryId = c.id
|
|
||||||
INNER JOIN product_purchase_counts pc ON pc.customer_email = o.customer_email AND pc.productId = os.productId
|
|
||||||
WHERE o.date_paid BETWEEN ? AND ?
|
|
||||||
AND o.status IN ('completed', 'processing')
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (siteId) {
|
|
||||||
sqlQuery += ' AND os.siteId = ?';
|
|
||||||
parameters.push(siteId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nameKeywords.length > 0) {
|
|
||||||
sqlQuery += ' AND (' + nameKeywords.map(() => 'os.name LIKE ?').join(' OR ') + ')';
|
|
||||||
parameters.push(...nameKeywords.map(word => `%${word}%`));
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlQuery += `
|
|
||||||
GROUP BY os.productId, os.name, c.name
|
|
||||||
ORDER BY totalQuantity DESC
|
|
||||||
LIMIT ? OFFSET ?
|
|
||||||
`;
|
|
||||||
|
|
||||||
parameters.push(pageSize, (current - 1) * pageSize);
|
|
||||||
const items = await this.orderSaleModel.query(sqlQuery, parameters);
|
|
||||||
|
|
||||||
// 总条数
|
|
||||||
const countParams: any[] = [startDate, endDate];
|
const countParams: any[] = [startDate, endDate];
|
||||||
let totalCountQuery = `
|
let countSql = `
|
||||||
SELECT COUNT(DISTINCT os.productId) AS totalCount
|
SELECT COUNT(DISTINCT os.productId) AS totalCount
|
||||||
FROM order_sale os
|
FROM order_sale os
|
||||||
INNER JOIN \`order\` o ON o.id = os.orderId
|
INNER JOIN \`order\` o ON o.id = os.orderId
|
||||||
WHERE o.date_paid BETWEEN ? AND ?
|
WHERE o.date_paid BETWEEN ? AND ?
|
||||||
AND o.status IN ('completed', 'processing')
|
AND o.status IN ('completed','processing')
|
||||||
`;
|
`;
|
||||||
if (siteId) {
|
if (siteId) {
|
||||||
totalCountQuery += ' AND os.siteId = ?';
|
countSql += ' AND os.siteId = ?';
|
||||||
countParams.push(siteId);
|
countParams.push(siteId);
|
||||||
}
|
}
|
||||||
if (nameKeywords.length > 0) {
|
if (nameKeywords.length > 0) {
|
||||||
totalCountQuery += ' AND (' + nameKeywords.map(() => 'os.name LIKE ?').join(' OR ') + ')';
|
countSql += ' AND (' + nameKeywords.map(() => 'os.name LIKE ?').join(' AND ') + ')';
|
||||||
countParams.push(...nameKeywords.map(word => `%${word}%`));
|
countParams.push(...nameKeywords.map(w => `%${w}%`));
|
||||||
|
}
|
||||||
|
const [countResult] = await this.orderSaleModel.query(countSql, countParams);
|
||||||
|
const totalCount = Number(countResult?.totalCount || 0);
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// 2. 分页查询 product 基础信息
|
||||||
|
// -------------------------
|
||||||
|
const itemParams: any[] = [startDate, endDate];
|
||||||
|
let nameCondition = '';
|
||||||
|
if (nameKeywords.length > 0) {
|
||||||
|
nameCondition = ' AND (' + nameKeywords.map(() => 'os.name LIKE ?').join(' AND ') + ')';
|
||||||
|
itemParams.push(...nameKeywords.map(w => `%${w}%`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalCountResult = await this.orderSaleModel.query(totalCountQuery, countParams);
|
let itemSql = `
|
||||||
|
SELECT os.productId, os.name, SUM(os.quantity) AS totalQuantity, COUNT(DISTINCT os.orderId) AS totalOrders
|
||||||
// 一次查询获取所有 yoone box 数量
|
|
||||||
const totalQuantityParams: any[] = [startDate, endDate];
|
|
||||||
let totalQuantityQuery = `
|
|
||||||
SELECT
|
|
||||||
SUM(os.quantity) AS totalQuantity,
|
|
||||||
SUM(CASE WHEN os.name LIKE '%yoone%' AND os.name LIKE '%3%' THEN os.quantity ELSE 0 END) AS yoone3Quantity,
|
|
||||||
SUM(CASE WHEN os.name LIKE '%yoone%' AND os.name LIKE '%6%' THEN os.quantity ELSE 0 END) AS yoone6Quantity,
|
|
||||||
SUM(CASE WHEN os.name LIKE '%yoone%' AND os.name LIKE '%9%' THEN os.quantity ELSE 0 END) AS yoone9Quantity,
|
|
||||||
SUM(CASE WHEN os.name LIKE '%yoone%' AND os.name LIKE '%12%' THEN os.quantity ELSE 0 END) AS yoone12Quantity,
|
|
||||||
SUM(CASE WHEN os.name LIKE '%yoone%' AND os.name LIKE '%12%' AND os.name LIKE '%NEW%' THEN os.quantity ELSE 0 END) AS yoone12QuantityNew,
|
|
||||||
SUM(CASE WHEN os.name LIKE '%yoone%' AND os.name LIKE '%15%' THEN os.quantity ELSE 0 END) AS yoone15Quantity,
|
|
||||||
SUM(CASE WHEN os.name LIKE '%yoone%' AND os.name LIKE '%18%' THEN os.quantity ELSE 0 END) AS yoone18Quantity,
|
|
||||||
SUM(CASE WHEN os.name LIKE '%zex%' THEN os.quantity ELSE 0 END) AS zexQuantity
|
|
||||||
FROM order_sale os
|
FROM order_sale os
|
||||||
INNER JOIN \`order\` o ON o.id = os.orderId
|
INNER JOIN \`order\` o ON o.id = os.orderId
|
||||||
WHERE o.date_paid BETWEEN ? AND ?
|
WHERE o.date_paid BETWEEN ? AND ?
|
||||||
AND o.status IN ('completed', 'processing')
|
AND o.status IN ('completed','processing')
|
||||||
`;
|
`;
|
||||||
if (siteId) {
|
if (siteId) {
|
||||||
totalQuantityQuery += ' AND os.siteId = ?';
|
itemSql += ' AND os.siteId = ?';
|
||||||
totalQuantityParams.push(siteId);
|
itemParams.push(siteId);
|
||||||
}
|
}
|
||||||
if (nameKeywords.length > 0) {
|
if (exceptPackage) {
|
||||||
totalQuantityQuery += ' AND (' + nameKeywords.map(() => 'os.name LIKE ?').join(' OR ') + ')';
|
itemSql += `
|
||||||
totalQuantityParams.push(...nameKeywords.map(word => `%${word}%`));
|
AND os.orderId IN (
|
||||||
|
SELECT orderId
|
||||||
|
FROM order_sale
|
||||||
|
GROUP BY orderId
|
||||||
|
HAVING COUNT(*) = 1
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
itemSql += nameCondition;
|
||||||
|
itemSql += `
|
||||||
|
GROUP BY os.productId, os.name
|
||||||
|
ORDER BY totalQuantity DESC
|
||||||
|
LIMIT ? OFFSET ?
|
||||||
|
`;
|
||||||
|
itemParams.push(pageSize, offset);
|
||||||
|
const items = await this.orderSaleModel.query(itemSql, itemParams);
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// 3. 批量统计当前页 product 历史复购
|
||||||
|
// -------------------------
|
||||||
|
if (items.length > 0) {
|
||||||
|
const productIds = items.map(i => i.productId);
|
||||||
|
const pcParams: any[] = [...productIds, startDate, endDate];
|
||||||
|
if (siteId) pcParams.push(siteId);
|
||||||
|
|
||||||
|
let pcSql = `
|
||||||
|
SELECT
|
||||||
|
os.productId,
|
||||||
|
SUM(CASE WHEN t.purchaseIndex = 1 THEN os.quantity ELSE 0 END) AS firstOrderYOONEBoxCount,
|
||||||
|
COUNT(DISTINCT CASE WHEN t.purchaseIndex = 1 THEN os.orderId END) AS firstOrderCount,
|
||||||
|
SUM(CASE WHEN t.purchaseIndex = 2 THEN os.quantity ELSE 0 END) AS secondOrderYOONEBoxCount,
|
||||||
|
COUNT(DISTINCT CASE WHEN t.purchaseIndex = 2 THEN os.orderId END) AS secondOrderCount,
|
||||||
|
SUM(CASE WHEN t.purchaseIndex = 3 THEN os.quantity ELSE 0 END) AS thirdOrderYOONEBoxCount,
|
||||||
|
COUNT(DISTINCT CASE WHEN t.purchaseIndex = 3 THEN os.orderId END) AS thirdOrderCount,
|
||||||
|
SUM(CASE WHEN t.purchaseIndex > 3 THEN os.quantity ELSE 0 END) AS moreThirdOrderYOONEBoxCount,
|
||||||
|
COUNT(DISTINCT CASE WHEN t.purchaseIndex > 3 THEN os.orderId END) AS moreThirdOrderCount
|
||||||
|
FROM order_sale os
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT o2.id AS orderId,
|
||||||
|
@idx := IF(@prev_email = o2.customer_email, @idx + 1, 1) AS purchaseIndex,
|
||||||
|
@prev_email := o2.customer_email
|
||||||
|
FROM \`order\` o2
|
||||||
|
CROSS JOIN (SELECT @idx := 0, @prev_email := '') vars
|
||||||
|
WHERE o2.status IN ('completed','processing')
|
||||||
|
ORDER BY o2.customer_email, o2.date_paid
|
||||||
|
) t ON t.orderId = os.orderId
|
||||||
|
WHERE os.productId IN (${productIds.map(() => '?').join(',')})
|
||||||
|
AND os.orderId IN (
|
||||||
|
SELECT id FROM \`order\`
|
||||||
|
WHERE date_paid BETWEEN ? AND ?
|
||||||
|
${siteId ? 'AND siteId = ?' : ''}
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
if (exceptPackage) {
|
||||||
|
pcSql += `
|
||||||
|
AND os.orderId IN (
|
||||||
|
SELECT orderId
|
||||||
|
FROM order_sale
|
||||||
|
GROUP BY orderId
|
||||||
|
HAVING COUNT(*) = 1
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
pcSql += `
|
||||||
|
GROUP BY os.productId
|
||||||
|
`;
|
||||||
|
|
||||||
|
console.log('------3.5-----', pcSql, pcParams, exceptPackage);
|
||||||
|
const pcResults = await this.orderSaleModel.query(pcSql, pcParams);
|
||||||
|
|
||||||
|
const pcMap = new Map<number, any>();
|
||||||
|
pcResults.forEach(r => pcMap.set(r.productId, r));
|
||||||
|
items.forEach(i => {
|
||||||
|
const r = pcMap.get(i.productId) || {};
|
||||||
|
i.firstOrderYOONEBoxCount = Number(r.firstOrderYOONEBoxCount || 0);
|
||||||
|
i.firstOrderCount = Number(r.firstOrderCount || 0);
|
||||||
|
i.secondOrderYOONEBoxCount = Number(r.secondOrderYOONEBoxCount || 0);
|
||||||
|
i.secondOrderCount = Number(r.secondOrderCount || 0);
|
||||||
|
i.thirdOrderYOONEBoxCount = Number(r.thirdOrderYOONEBoxCount || 0);
|
||||||
|
i.thirdOrderCount = Number(r.thirdOrderCount || 0);
|
||||||
|
i.moreThirdOrderYOONEBoxCount = Number(r.moreThirdOrderYOONEBoxCount || 0);
|
||||||
|
i.moreThirdOrderCount = Number(r.moreThirdOrderCount || 0);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const [totalQuantityResult] = await this.orderSaleModel.query(totalQuantityQuery, totalQuantityParams);
|
// -------------------------
|
||||||
|
// 4. 总量统计(时间段 + siteId)
|
||||||
|
// -------------------------
|
||||||
|
const totalParams: any[] = [startDate, endDate];
|
||||||
|
const yooneParams: any[] = [startDate, endDate];
|
||||||
|
let totalSql = `
|
||||||
|
SELECT
|
||||||
|
SUM(os.quantity) AS totalQuantity
|
||||||
|
FROM order_sale os
|
||||||
|
INNER JOIN \`order\` o ON o.id = os.orderId
|
||||||
|
WHERE o.date_paid BETWEEN ? AND ?
|
||||||
|
AND o.status IN ('completed','processing')
|
||||||
|
`;
|
||||||
|
let yooneSql = `
|
||||||
|
SELECT
|
||||||
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 3 THEN os.quantity ELSE 0 END) AS yoone3Quantity,
|
||||||
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 6 THEN os.quantity ELSE 0 END) AS yoone6Quantity,
|
||||||
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 9 THEN os.quantity ELSE 0 END) AS yoone9Quantity,
|
||||||
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 12 THEN os.quantity ELSE 0 END) AS yoone12Quantity,
|
||||||
|
SUM(CASE WHEN os.isYooneNew = 1 AND os.size = 12 THEN os.quantity ELSE 0 END) AS yoone12QuantityNew,
|
||||||
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 15 THEN os.quantity ELSE 0 END) AS yoone15Quantity,
|
||||||
|
SUM(CASE WHEN os.isYoone = 1 AND os.size = 18 THEN os.quantity ELSE 0 END) AS yoone18Quantity,
|
||||||
|
SUM(CASE WHEN os.isZex = 1 THEN os.quantity ELSE 0 END) AS zexQuantity
|
||||||
|
FROM order_sale os
|
||||||
|
INNER JOIN \`order\` o ON o.id = os.orderId
|
||||||
|
WHERE o.date_paid BETWEEN ? AND ?
|
||||||
|
AND o.status IN ('completed','processing')
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (siteId) {
|
||||||
|
totalSql += ' AND os.siteId = ?';
|
||||||
|
totalParams.push(siteId);
|
||||||
|
yooneSql += ' AND os.siteId = ?';
|
||||||
|
yooneParams.push(siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameKeywords.length > 0) {
|
||||||
|
totalSql += ' AND (' + nameKeywords.map(() => 'os.name LIKE ?').join(' AND ') + ')';
|
||||||
|
totalParams.push(...nameKeywords.map(w => `%${w}%`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const [totalResult] = await this.orderSaleModel.query(totalSql, totalParams);
|
||||||
|
const [yooneResult] = await this.orderSaleModel.query(yooneSql, yooneParams);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
total: totalCountResult[0]?.totalCount || 0,
|
total: totalCount, // ✅ 总条数
|
||||||
totalQuantity: Number(totalQuantityResult.totalQuantity || 0),
|
totalQuantity: Number(totalResult.totalQuantity || 0),
|
||||||
yoone3Quantity: Number(totalQuantityResult.yoone3Quantity || 0),
|
yoone3Quantity: Number(yooneResult.yoone3Quantity || 0),
|
||||||
yoone6Quantity: Number(totalQuantityResult.yoone6Quantity || 0),
|
yoone6Quantity: Number(yooneResult.yoone6Quantity || 0),
|
||||||
yoone9Quantity: Number(totalQuantityResult.yoone9Quantity || 0),
|
yoone9Quantity: Number(yooneResult.yoone9Quantity || 0),
|
||||||
yoone12Quantity: Number(totalQuantityResult.yoone12Quantity || 0),
|
yoone12Quantity: Number(yooneResult.yoone12Quantity || 0),
|
||||||
yoone12QuantityNew: Number(totalQuantityResult.yoone12QuantityNew || 0),
|
yoone12QuantityNew: Number(yooneResult.yoone12QuantityNew || 0),
|
||||||
yoone15Quantity: Number(totalQuantityResult.yoone15Quantity || 0),
|
yoone15Quantity: Number(yooneResult.yoone15Quantity || 0),
|
||||||
yoone18Quantity: Number(totalQuantityResult.yoone18Quantity || 0),
|
yoone18Quantity: Number(yooneResult.yoone18Quantity || 0),
|
||||||
zexQuantity: Number(totalQuantityResult.zexQuantity || 0),
|
zexQuantity: Number(yooneResult.zexQuantity || 0),
|
||||||
current,
|
current,
|
||||||
pageSize,
|
pageSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// async getOrderSales({
|
|
||||||
// siteId,
|
|
||||||
// startDate,
|
|
||||||
// endDate,
|
|
||||||
// current,
|
|
||||||
// pageSize,
|
|
||||||
// name,
|
|
||||||
// }: QueryOrderSalesDTO) {
|
|
||||||
// const nameKeywords = name ? name.split(' ').filter(Boolean) : [];
|
|
||||||
// // 分页查询
|
|
||||||
// let sqlQuery = `
|
|
||||||
// WITH product_purchase_counts AS (
|
|
||||||
// SELECT o.customer_email,os.productId, os.name, COUNT(DISTINCT o.id,os.productId) AS order_count
|
|
||||||
// FROM \`order\` o
|
|
||||||
// JOIN order_sale os ON o.id = os.orderId
|
|
||||||
// WHERE o.status IN ('completed', 'processing')
|
|
||||||
// GROUP BY o.customer_email, os.productId, os.name
|
|
||||||
// )
|
|
||||||
// SELECT
|
|
||||||
// os.productId AS productId,
|
|
||||||
// os.name AS name,
|
|
||||||
// SUM(os.quantity) AS totalQuantity,
|
|
||||||
// COUNT(distinct os.orderId) AS totalOrders,
|
|
||||||
// c.name AS categoryName,
|
|
||||||
// COUNT(DISTINCT CASE WHEN pc.order_count = 1 THEN o.id END) AS firstOrderCount,
|
|
||||||
// SUM(CASE WHEN pc.order_count = 1 THEN os.quantity ELSE 0 END) AS firstOrderYOONEBoxCount,
|
|
||||||
// COUNT(DISTINCT CASE WHEN pc.order_count = 2 THEN o.id END) AS secondOrderCount,
|
|
||||||
// SUM(CASE WHEN pc.order_count = 2 THEN os.quantity ELSE 0 END) AS secondOrderYOONEBoxCount,
|
|
||||||
// COUNT(DISTINCT CASE WHEN pc.order_count = 3 THEN o.id END) AS thirdOrderCount,
|
|
||||||
// SUM(CASE WHEN pc.order_count = 3 THEN os.quantity ELSE 0 END) AS thirdOrderYOONEBoxCount,
|
|
||||||
// COUNT(DISTINCT CASE WHEN pc.order_count > 3 THEN o.id END) AS moreThirdOrderCount,
|
|
||||||
// SUM(CASE WHEN pc.order_count > 3 THEN os.quantity ELSE 0 END) AS moreThirdOrderYOONEBoxCount
|
|
||||||
// FROM order_sale os
|
|
||||||
// INNER JOIN \`order\` o ON o.id = os.orderId
|
|
||||||
// INNER JOIN product p ON os.productId = p.id
|
|
||||||
// INNER JOIN category c ON p.categoryId = c.id
|
|
||||||
// INNER JOIN product_purchase_counts pc ON pc.customer_email = o.customer_email AND pc.productId = os.productId
|
|
||||||
// WHERE o.date_paid BETWEEN ? AND ?
|
|
||||||
// AND o.status IN ('processing', 'completed')
|
|
||||||
// `;
|
|
||||||
// const parameters: any[] = [startDate, endDate];
|
|
||||||
// if (siteId) {
|
|
||||||
// sqlQuery += ' AND os.siteId = ?';
|
|
||||||
// parameters.push(siteId);
|
|
||||||
// }
|
|
||||||
// if (nameKeywords.length > 0) {
|
|
||||||
// sqlQuery +=
|
|
||||||
// ' AND ' + nameKeywords.map(() => `os.name LIKE ?`).join(' AND ');
|
|
||||||
// parameters.push(...nameKeywords.map(word => `%${word}%`));
|
|
||||||
// }
|
|
||||||
// sqlQuery += `
|
|
||||||
// GROUP BY os.productId, os.name, c.name
|
|
||||||
// ORDER BY totalQuantity DESC
|
|
||||||
// `;
|
|
||||||
// sqlQuery += ' LIMIT ? OFFSET ?';
|
|
||||||
// parameters.push(pageSize, (current - 1) * pageSize);
|
|
||||||
|
|
||||||
// // 执行查询并传递参数
|
|
||||||
// const items = await this.orderSaleModel.query(sqlQuery, parameters);
|
|
||||||
|
|
||||||
// let totalCountQuery = `
|
|
||||||
// SELECT COUNT(DISTINCT os.productId) AS totalCount
|
|
||||||
// FROM order_sale os
|
|
||||||
// INNER JOIN \`order\` o ON o.id = os.orderId
|
|
||||||
// INNER JOIN product p ON os.productId = p.id
|
|
||||||
// INNER JOIN category c ON p.categoryId = c.id
|
|
||||||
// WHERE o.date_created BETWEEN ? AND ?
|
|
||||||
// AND o.status IN ('processing', 'completed')
|
|
||||||
// `;
|
|
||||||
// const totalCountParameters: any[] = [startDate, endDate];
|
|
||||||
// if (siteId) {
|
|
||||||
// totalCountQuery += ' AND os.siteId = ?';
|
|
||||||
// totalCountParameters.push(siteId);
|
|
||||||
// }
|
|
||||||
// if (nameKeywords.length > 0) {
|
|
||||||
// totalCountQuery +=
|
|
||||||
// ' AND ' + nameKeywords.map(() => `os.name LIKE ?`).join(' AND ');
|
|
||||||
// totalCountParameters.push(...nameKeywords.map(word => `%${word}%`));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const totalCountResult = await this.orderSaleModel.query(
|
|
||||||
// totalCountQuery,
|
|
||||||
// totalCountParameters
|
|
||||||
// );
|
|
||||||
|
|
||||||
// let totalQuantityQuery = `
|
|
||||||
// SELECT SUM(os.quantity) AS totalQuantity
|
|
||||||
// FROM order_sale os
|
|
||||||
// INNER JOIN \`order\` o ON o.id = os.orderId
|
|
||||||
// INNER JOIN product p ON os.productId = p.id
|
|
||||||
// INNER JOIN category c ON p.categoryId = c.id
|
|
||||||
// WHERE o.date_created BETWEEN ? AND ?
|
|
||||||
// AND o.status IN ('processing', 'completed')
|
|
||||||
// `;
|
|
||||||
|
|
||||||
// const totalQuantityParameters: any[] = [startDate, endDate];
|
|
||||||
// if (siteId) {
|
|
||||||
// totalQuantityQuery += ' AND os.siteId = ?';
|
|
||||||
// totalQuantityParameters.push(siteId);
|
|
||||||
// }
|
|
||||||
// const yoone3QuantityQuery =
|
|
||||||
// totalQuantityQuery + 'AND os.name LIKE "%yoone%" AND os.name LIKE "%3%"';
|
|
||||||
// const yoone6QuantityQuery =
|
|
||||||
// totalQuantityQuery + 'AND os.name LIKE "%yoone%" AND os.name LIKE "%6%"';
|
|
||||||
// const yoone9QuantityQuery =
|
|
||||||
// totalQuantityQuery + 'AND os.name LIKE "%yoone%" AND os.name LIKE "%9%"';
|
|
||||||
// const yoone12QuantityQuery =
|
|
||||||
// totalQuantityQuery + 'AND os.name LIKE "%yoone%" AND os.name LIKE "%12%"';
|
|
||||||
// const yoone15QuantityQuery =
|
|
||||||
// totalQuantityQuery + 'AND os.name LIKE "%yoone%" AND os.name LIKE "%15%"';
|
|
||||||
// const yooneParameters = [...totalQuantityParameters];
|
|
||||||
// if (nameKeywords.length > 0) {
|
|
||||||
// totalQuantityQuery +=
|
|
||||||
// ' AND ' + nameKeywords.map(() => `os.name LIKE ?`).join(' AND ');
|
|
||||||
// totalQuantityParameters.push(...nameKeywords.map(word => `%${word}%`));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const totalQuantityResult = await this.orderSaleModel.query(
|
|
||||||
// totalQuantityQuery,
|
|
||||||
// totalQuantityParameters
|
|
||||||
// );
|
|
||||||
// const yoone3QuantityResult = await this.orderSaleModel.query(
|
|
||||||
// yoone3QuantityQuery,
|
|
||||||
// yooneParameters
|
|
||||||
// );
|
|
||||||
// const yoone6QuantityResult = await this.orderSaleModel.query(
|
|
||||||
// yoone6QuantityQuery,
|
|
||||||
// yooneParameters
|
|
||||||
// );
|
|
||||||
// const yoone9QuantityResult = await this.orderSaleModel.query(
|
|
||||||
// yoone9QuantityQuery,
|
|
||||||
// yooneParameters
|
|
||||||
// );
|
|
||||||
// const yoone12QuantityResult = await this.orderSaleModel.query(
|
|
||||||
// yoone12QuantityQuery,
|
|
||||||
// yooneParameters
|
|
||||||
// );
|
|
||||||
// const yoone15QuantityResult = await this.orderSaleModel.query(
|
|
||||||
// yoone15QuantityQuery,
|
|
||||||
// yooneParameters
|
|
||||||
// );
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// items,
|
|
||||||
// total: totalCountResult[0]?.totalCount,
|
|
||||||
// totalQuantity: Number(
|
|
||||||
// totalQuantityResult.reduce((sum, row) => sum + row.totalQuantity, 0)
|
|
||||||
// ),
|
|
||||||
// yoone3Quantity: Number(
|
|
||||||
// yoone3QuantityResult.reduce((sum, row) => sum + row.totalQuantity, 0)
|
|
||||||
// ),
|
|
||||||
// yoone6Quantity: Number(
|
|
||||||
// yoone6QuantityResult.reduce((sum, row) => sum + row.totalQuantity, 0)
|
|
||||||
// ),
|
|
||||||
// yoone9Quantity: Number(
|
|
||||||
// yoone9QuantityResult.reduce((sum, row) => sum + row.totalQuantity, 0)
|
|
||||||
// ),
|
|
||||||
// yoone12Quantity: Number(
|
|
||||||
// yoone12QuantityResult.reduce((sum, row) => sum + row.totalQuantity, 0)
|
|
||||||
// ),
|
|
||||||
// yoone15Quantity: Number(
|
|
||||||
// yoone15QuantityResult.reduce((sum, row) => sum + row.totalQuantity, 0)
|
|
||||||
// ),
|
|
||||||
// current,
|
|
||||||
// pageSize,
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
async getOrderItems({
|
async getOrderItems({
|
||||||
siteId,
|
siteId,
|
||||||
startDate,
|
startDate,
|
||||||
|
|
@ -1181,6 +1099,21 @@ export class OrderService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update order_sale_origin if not exist
|
||||||
|
|
||||||
|
try {
|
||||||
|
const order_sale_origin_count = await this.orderSaleOriginalModel.countBy({ orderId: id });
|
||||||
|
if (order_sale_origin_count === 0) {
|
||||||
|
sales.forEach(async sale => {
|
||||||
|
const { id: saleId, ...saleData } = sale;
|
||||||
|
await this.orderSaleOriginalModel.save(saleData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log('create order sale origin error: ', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...order,
|
...order,
|
||||||
siteName: site.siteName,
|
siteName: site.siteName,
|
||||||
|
|
@ -1296,6 +1229,7 @@ export class OrderService {
|
||||||
return dataSource.transaction(async manager => {
|
return dataSource.transaction(async manager => {
|
||||||
const orderRepo = manager.getRepository(Order);
|
const orderRepo = manager.getRepository(Order);
|
||||||
const orderSaleRepo = manager.getRepository(OrderSale);
|
const orderSaleRepo = manager.getRepository(OrderSale);
|
||||||
|
const OrderSaleOriginalRepo = manager.getRepository(OrderSaleOriginal);
|
||||||
const productRepo = manager.getRepository(Product);
|
const productRepo = manager.getRepository(Product);
|
||||||
const order = await orderRepo.save({
|
const order = await orderRepo.save({
|
||||||
siteId: '-1',
|
siteId: '-1',
|
||||||
|
|
@ -1313,7 +1247,7 @@ export class OrderService {
|
||||||
});
|
});
|
||||||
for (const sale of sales) {
|
for (const sale of sales) {
|
||||||
const product = await productRepo.findOne({ where: { sku: sale.sku } });
|
const product = await productRepo.findOne({ where: { sku: sale.sku } });
|
||||||
await orderSaleRepo.save({
|
const saleItem = {
|
||||||
orderId: order.id,
|
orderId: order.id,
|
||||||
siteId: '-1',
|
siteId: '-1',
|
||||||
externalOrderItemId: '-1',
|
externalOrderItemId: '-1',
|
||||||
|
|
@ -1321,7 +1255,9 @@ export class OrderService {
|
||||||
name: product.name,
|
name: product.name,
|
||||||
sku: sale.sku,
|
sku: sale.sku,
|
||||||
quantity: sale.quantity,
|
quantity: sale.quantity,
|
||||||
});
|
};
|
||||||
|
await orderSaleRepo.save(saleItem);
|
||||||
|
await OrderSaleOriginalRepo.save(saleItem);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1362,4 +1298,43 @@ export class OrderService {
|
||||||
pageSize,
|
pageSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateOrderSales(orderId: number, sales: OrderSale[]) {
|
||||||
|
try {
|
||||||
|
const dataSource = this.dataSourceManager.getDataSource('default');
|
||||||
|
let transactionError = undefined;
|
||||||
|
|
||||||
|
await dataSource.transaction(async manager => {
|
||||||
|
const orderRepo = manager.getRepository(Order);
|
||||||
|
const orderSaleRepo = manager.getRepository(OrderSale);
|
||||||
|
const productRepo = manager.getRepository(Product);
|
||||||
|
|
||||||
|
const order = await orderRepo.findOneBy({ id: orderId });
|
||||||
|
let product:Product;
|
||||||
|
await orderSaleRepo.delete({ orderId });
|
||||||
|
for (const sale of sales) {
|
||||||
|
product = await productRepo.findOneBy({ sku: sale.sku });
|
||||||
|
await orderSaleRepo.save({
|
||||||
|
orderId,
|
||||||
|
siteId: order.siteId,
|
||||||
|
productId: product.id,
|
||||||
|
name: product.name,
|
||||||
|
sku: sale.sku,
|
||||||
|
quantity: sale.quantity,
|
||||||
|
// externalOrderItemId:
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}).catch(error => {
|
||||||
|
transactionError = error;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (transactionError !== undefined) {
|
||||||
|
throw new Error(`更新物流信息错误:${transactionError.message}`);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`更新发货产品失败:${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { Provide } from "@midwayjs/core";
|
||||||
|
import { Config } from '@midwayjs/decorator';
|
||||||
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
export class UniExpressService {
|
||||||
|
@Config('uniExpress.url')
|
||||||
|
url;
|
||||||
|
|
||||||
|
@Config('uniExpress.clientId')
|
||||||
|
clientId;
|
||||||
|
|
||||||
|
@Config('uniExpress.clientSecret')
|
||||||
|
cliientSecret;
|
||||||
|
|
||||||
|
@Config('uniExpress.customerNo')
|
||||||
|
customerNo;
|
||||||
|
|
||||||
|
async getToken() {
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
method: 'POST',
|
||||||
|
url: `${this.url}/storeauth/customertoken`,
|
||||||
|
data: {
|
||||||
|
'grant_type': 'client_credentials',
|
||||||
|
'client_id': this.clientId,
|
||||||
|
'client_secret': this.cliientSecret
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const tokenBody = await axios.request(config);
|
||||||
|
return tokenBody.data.data.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRates(data: any) {
|
||||||
|
try {
|
||||||
|
const requiredKeys = {
|
||||||
|
customer_no: this.customerNo,
|
||||||
|
pickup_warehouse: 1
|
||||||
|
};
|
||||||
|
const body = {
|
||||||
|
...requiredKeys,
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
const token = await this.getToken();
|
||||||
|
const config: AxiosRequestConfig= {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
url: `${this.url}/orders/estimateshipping`,
|
||||||
|
data: body
|
||||||
|
};
|
||||||
|
return (await axios.request(config)).data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createShipment(
|
||||||
|
data: any
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const requiredKeys = {
|
||||||
|
customer_no: this.customerNo,
|
||||||
|
pickup_warehouse: 1
|
||||||
|
};
|
||||||
|
const body = {
|
||||||
|
...requiredKeys,
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
const token = await this.getToken();
|
||||||
|
const config: AxiosRequestConfig= {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
url: `${this.url}/orders/createbusinessorder`,
|
||||||
|
data: body
|
||||||
|
};
|
||||||
|
const req = await axios.request(config);
|
||||||
|
const res = req.data;
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteShipment(tno: string) {
|
||||||
|
const body = {
|
||||||
|
tno
|
||||||
|
};
|
||||||
|
const token = await this.getToken();
|
||||||
|
const config: AxiosRequestConfig= {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
url: `${this.url}/orders/cancelorder`,
|
||||||
|
data: body
|
||||||
|
};
|
||||||
|
return await axios.request(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLabel(tracking_number: string) {
|
||||||
|
const body = {
|
||||||
|
packageId: tracking_number
|
||||||
|
};
|
||||||
|
const token = await this.getToken();
|
||||||
|
const config: AxiosRequestConfig= {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
url: `${this.url}/orders/printlabel`,
|
||||||
|
data: body
|
||||||
|
};
|
||||||
|
return await axios.request(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrdersByDate(from: string, to: string, page: number = 1, perPage: number = 100) {
|
||||||
|
try {
|
||||||
|
const token = await this.getToken();
|
||||||
|
const config: AxiosRequestConfig= {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
url: `${this.url}/orders`,
|
||||||
|
params: {
|
||||||
|
from, to, page, perPage
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const res = (await axios.request(config)).data;
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOrderStatus(tracking_number: string) {
|
||||||
|
try {
|
||||||
|
const key = 'SMq45nJhQuNR3WHsJA6N'; // todo,写进常数
|
||||||
|
const config: AxiosRequestConfig= {
|
||||||
|
method: 'GET',
|
||||||
|
url: `${this.url}/orders/trackinguniuni`,
|
||||||
|
params: {
|
||||||
|
key,
|
||||||
|
id: tracking_number
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const res = (await axios.request(config)).data;
|
||||||
|
return res;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -70,6 +70,7 @@ export class UserService {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
deviceId,
|
deviceId,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
|
isSuper: user.isSuper,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { WpSite } from '../interface';
|
||||||
import { WpProduct } from '../entity/wp_product.entity';
|
import { WpProduct } from '../entity/wp_product.entity';
|
||||||
import { Variation } from '../entity/variation.entity';
|
import { Variation } from '../entity/variation.entity';
|
||||||
import { UpdateVariationDTO, UpdateWpProductDTO } from '../dto/wp_product.dto';
|
import { UpdateVariationDTO, UpdateWpProductDTO } from '../dto/wp_product.dto';
|
||||||
|
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class WPService {
|
export class WPService {
|
||||||
|
|
@ -222,6 +223,27 @@ export class WPService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新 WooCommerce 产品 上下架状态
|
||||||
|
* @param productId 产品 ID
|
||||||
|
* @param status 状态
|
||||||
|
* @param stock_status 上下架状态
|
||||||
|
*/
|
||||||
|
async updateProductStatus(
|
||||||
|
site: WpSite,
|
||||||
|
productId: string,
|
||||||
|
status: ProductStatus,
|
||||||
|
stock_status: ProductStockStatus
|
||||||
|
): Promise<Boolean> {
|
||||||
|
const res = await this.updateData(`/wc/v3/products/${productId}`, site, {
|
||||||
|
status,
|
||||||
|
manage_stock: false, // 为true的时候,用quantity控制库存,为false时,直接用stock_status控制
|
||||||
|
stock_status,
|
||||||
|
});
|
||||||
|
console.log('res', res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新 WooCommerce 产品变体
|
* 更新 WooCommerce 产品变体
|
||||||
* @param productId 产品 ID
|
* @param productId 产品 ID
|
||||||
|
|
@ -261,7 +283,7 @@ export class WPService {
|
||||||
site: WpSite,
|
site: WpSite,
|
||||||
orderId: string,
|
orderId: string,
|
||||||
data: Record<string, any>
|
data: Record<string, any>
|
||||||
): Promise<Boolean> {
|
) {
|
||||||
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
||||||
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
'base64'
|
'base64'
|
||||||
|
|
@ -276,4 +298,26 @@ export class WPService {
|
||||||
};
|
};
|
||||||
return await axios.request(config);
|
return await axios.request(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteShipment(
|
||||||
|
site: WpSite,
|
||||||
|
orderId: string,
|
||||||
|
trackingId: string,
|
||||||
|
): Promise<Boolean> {
|
||||||
|
const { wpApiUrl, consumerKey, consumerSecret } = site;
|
||||||
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString(
|
||||||
|
'base64'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('del', orderId, trackingId);
|
||||||
|
// 删除接口: DELETE /wp-json/wc-shipment-tracking/v3/orders/<order_id>/shipment-trackings/<tracking_id>
|
||||||
|
const config: AxiosRequestConfig = {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: `${wpApiUrl}/wp-json/wc-ast/v3/orders/${orderId}/shipment-trackings/${trackingId}`,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${auth}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return await axios.request(config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
UpdateWpProductDTO,
|
UpdateWpProductDTO,
|
||||||
} from '../dto/wp_product.dto';
|
} from '../dto/wp_product.dto';
|
||||||
import { Product } from '../entity/product.entty';
|
import { Product } from '../entity/product.entty';
|
||||||
|
import { ProductStatus, ProductStockStatus } from '../enums/base.enum';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class WpProductService {
|
export class WpProductService {
|
||||||
|
|
@ -26,7 +27,7 @@ export class WpProductService {
|
||||||
@InjectEntityModel(Variation)
|
@InjectEntityModel(Variation)
|
||||||
variationModel: Repository<Variation>;
|
variationModel: Repository<Variation>;
|
||||||
|
|
||||||
geSite(id: string): WpSite {
|
getSite(id: string): WpSite {
|
||||||
let idx = this.sites.findIndex(item => item.id === id);
|
let idx = this.sites.findIndex(item => item.id === id);
|
||||||
return this.sites[idx];
|
return this.sites[idx];
|
||||||
}
|
}
|
||||||
|
|
@ -45,7 +46,7 @@ export class WpProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncSite(siteId: string) {
|
async syncSite(siteId: string) {
|
||||||
const site = this.geSite(siteId);
|
const site = this.getSite(siteId);
|
||||||
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 =
|
const variations =
|
||||||
|
|
@ -56,6 +57,21 @@ export class WpProductService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 控制产品上下架
|
||||||
|
async updateProductStatus(id: number, status: ProductStatus, stock_status: ProductStockStatus) {
|
||||||
|
const wpProduct = await this.wpProductModel.findOneBy({ id });
|
||||||
|
const site = await this.getSite(wpProduct.siteId);
|
||||||
|
wpProduct.status = status;
|
||||||
|
wpProduct.stockStatus = stock_status;
|
||||||
|
const res = await this.wpApiService.updateProductStatus(site, wpProduct.externalProductId, status, stock_status);
|
||||||
|
if (res === true) {
|
||||||
|
this.wpProductModel.save(wpProduct);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async findProduct(
|
async findProduct(
|
||||||
siteId: string,
|
siteId: string,
|
||||||
externalProductId: string
|
externalProductId: string
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
function camelToSnake(str: string): string {
|
||||||
|
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function snakeToCamel(str: string): string {
|
||||||
|
return str.replace(/(_[\w])/g, (match) =>
|
||||||
|
match[1].toUpperCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertKeysFromSnakeToCamel<T>(obj: T): T {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map(convertKeysFromSnakeToCamel) as unknown as T;
|
||||||
|
} else if (obj !== null && typeof obj === 'object') {
|
||||||
|
return Object.keys(obj).reduce((acc, key) => {
|
||||||
|
const newKey = snakeToCamel(key);
|
||||||
|
acc[newKey] = convertKeysFromSnakeToCamel((obj as any)[key]);
|
||||||
|
return acc;
|
||||||
|
}, {} as any);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function convertKeysFromCamelToSnake<T>(obj: T): T {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map(convertKeysFromCamelToSnake) as unknown as T;
|
||||||
|
} else if (obj !== null && typeof obj === 'object') {
|
||||||
|
return Object.keys(obj).reduce((acc, key) => {
|
||||||
|
const newKey = camelToSnake(key);
|
||||||
|
acc[newKey] = convertKeysFromCamelToSnake((obj as any)[key]);
|
||||||
|
return acc;
|
||||||
|
}, {} as any);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue