bug fix
  完善下单失败,事务回滚相关
  增加了取消运单接口
  添加费率查询
This commit is contained in:
longbot 2025-08-08 19:21:43 +08:00
parent 5e5ed3b309
commit d59e17fefc
5 changed files with 96 additions and 83 deletions

View File

@ -162,6 +162,21 @@ export class LogisticsController {
} }
} }
@ApiOkResponse(
{type: BooleanRes}
)
@Post('/getShipmentFee')
async getShipmentFee(
@Body() data: ShipmentBookDTO
) {
try {
const fee = await this.logisticsService.getShipmentFee(data);
return successResponse(fee);
} catch (error) {
return errorResponse(error?.message || '创建失败');
}
}
@ApiOkResponse( @ApiOkResponse(
{type: BooleanRes} {type: BooleanRes}
) )
@ -182,7 +197,6 @@ export class LogisticsController {
} }
} }
@ApiOkResponse() @ApiOkResponse()
@Post('/getPaymentMethods') @Post('/getPaymentMethods')
async getpaymentmethods() { async getpaymentmethods() {

View File

@ -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;

View File

@ -22,10 +22,6 @@ export class Shipment {
id: string; id: string;
@ApiProperty() @ApiProperty()
@Column({ name: 'order_id', nullable: true })
@Expose()
orderId: string;
@OneToOne(() => Order) @OneToOne(() => Order)
@JoinColumn({ name: 'order_id' }) @JoinColumn({ name: 'order_id' })
order: Order; order: Order;
@ -39,6 +35,10 @@ export class Shipment {
@JoinColumn({ name: 'stock_point_id' }) @JoinColumn({ name: 'stock_point_id' })
stockPoint: StockPoint; stockPoint: StockPoint;
@Column({ nullable: false, default: 0})
@Expose()
fee: number;
@ApiProperty() @ApiProperty()
@Column({ nullable: true }) @Column({ nullable: true })
@Expose() @Expose()

View File

@ -204,6 +204,40 @@ export class LogisticsService {
} }
} }
async getShipmentFee(data: ShipmentBookDTO) {
try {
const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId});
const reqBody = {
sender: data.details.origin.contact_name,
start_phone: data.details.origin.phone_number,
start_postal_code: data.details.origin.address.postal_code.replace(/\s/g, ''),
pickup_address: data.details.origin.address.address_line_1,
pickup_warehouse: stock_point.upStreamStockPointId,
shipper_country_code: data.details.origin.address.country,
receiver: data.details.destination.contact_name,
city: data.details.destination.address.city,
province: data.details.destination.address.region, // todo待确认
country: data.details.destination.address.country,
postal_code: data.details.destination.address.postal_code.replace(/\s/g, ''),
delivery_address: data.details.destination.address.address_line_1,
receiver_phone: data.details.destination.phone_number.number,
receiver_email: data.details.destination.email_addresses,
// item_description: data.sales, // todo: 货品信息
length: data.details.packaging_properties.packages[0].measurements.cuboid.l,
width: data.details.packaging_properties.packages[0].measurements.cuboid.w,
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,
weight_uom: data.details.packaging_properties.packages[0].measurements.weight.unit,
currency: 'CAD',
}
const resShipmentFee = await this.uniExpressService.getRates(reqBody);
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 });
if (!order) { if (!order) {
@ -215,6 +249,8 @@ export class LogisticsService {
) { ) {
throw new Error('订单状态不正确 '); throw new Error('订单状态不正确 ');
} }
let resShipmentOrder;
try { try {
const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId}); const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId});
const reqBody = { const reqBody = {
@ -238,28 +274,24 @@ export class LogisticsService {
height: data.details.packaging_properties.packages[0].measurements.cuboid.h, height: data.details.packaging_properties.packages[0].measurements.cuboid.h,
dimension_uom: data.details.packaging_properties.packages[0].measurements.cuboid.unit, dimension_uom: data.details.packaging_properties.packages[0].measurements.cuboid.unit,
weight: data.details.packaging_properties.packages[0].measurements.weight.value, weight: data.details.packaging_properties.packages[0].measurements.weight.value,
weight_uom: `${data.details.packaging_properties.packages[0].measurements.weight.unit}S`, // todo换成KGS和LBS weight_uom: data.details.packaging_properties.packages[0].measurements.weight.unit,
currency: 'CAD', currency: 'CAD',
} }
console.log('body', reqBody);
throw new Error('test ');
// todo: 两个请求做异步 参考promise.all()方法
// 获取预估费率
const resShipmentFee = await this.uniExpressService.getRates(reqBody);
// 添加运单 // 添加运单
const resShipmentOrder = await this.uniExpressService.createShipment(reqBody); resShipmentOrder = await this.uniExpressService.createShipment(reqBody);
// 记录物流信息,并将订单状态转到完成 // 记录物流信息,并将订单状态转到完成
if (resShipmentOrder.status === 'SUCCESS') { if (resShipmentOrder.status === 'SUCCESS') {
order.orderStatus = ErpOrderStatus.COMPLETED; order.orderStatus = ErpOrderStatus.COMPLETED;
} else {
throw new Error('运单生成失败');
} }
const dataSource = this.dataSourceManager.getDataSource('default'); const dataSource = this.dataSourceManager.getDataSource('default');
dataSource.transaction(async manager => { let transactionError = undefined;
await dataSource.transaction(async manager => {
const orderRepo = manager.getRepository(Order); const orderRepo = manager.getRepository(Order);
const shipmentRepo = manager.getRepository(Shipment); const shipmentRepo = manager.getRepository(Shipment);
if (order.orderStatus === ErpOrderStatus.COMPLETED) { if (order.orderStatus === ErpOrderStatus.COMPLETED) {
const shipment = await shipmentRepo.save({ const shipment = await shipmentRepo.save({
tracking_provider: 'uniuni-express', // todo: id未确定后写进常数 tracking_provider: 'uniuni-express', // todo: id未确定后写进常数
@ -267,90 +299,38 @@ export class LogisticsService {
stockPointId: '1', // todo stockPointId: '1', // todo
state: resShipmentOrder.data.uni_status_code, state: resShipmentOrder.data.uni_status_code,
return_tracking_number: resShipmentOrder.data.tno, return_tracking_number: resShipmentOrder.data.tno,
order_id: order.id fee: data.details.shipmentFee,
order_id: orderId
}); });
order.shipmentId = shipment.id; order.shipmentId = shipment.id;
// 同步物流信息到woocommerce // 同步物流信息到woocommerce
const site = this.geSite(order.siteId); const site = await this.geSite(order.siteId);
await this.wpService.createShipment(site, order.externalOrderId, { await this.wpService.createShipment(site, order.externalOrderId, {
tracking_number: shipment.primary_tracking_number, tracking_number: shipment.return_tracking_number,
tracking_provider: shipment?.rate?.carrier_name, tracking_provider: shipment?.tracking_provider,
}); });
} }
await orderRepo.save(order); await orderRepo.save(order);
}).catch(error => {
transactionError = error
}); });
if (transactionError !== undefined) {
console.log('err', transactionError);
throw transactionError;
}
return { data: { return { data: {
resShipmentFee,
resShipmentOrder resShipmentOrder
} }; } };
} catch(error) { } catch(error) {
throw new Error(`上游请求错误:${error}`); if (resShipmentOrder.status === 'SUCCESS') {
await this.uniExpressService.deleteShipment(resShipmentOrder.data.tno);
}
throw new Error(`上游请求错误:${error}`);
} }
// const dataSource = this.dataSourceManager.getDataSource('default');
// return dataSource.transaction(async manager => {
// const productRepo = manager.getRepository(Product);
// const shipmentRepo = manager.getRepository(Shipment);
// 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);
// await shipmentRepo.save(shipment);
// await this.getShipment(shipment.id);
// const shipmentItems = [];
// for (const item of data?.sales) {
// const product = await productRepo.findOne({ where: { sku: item.sku } });
// shipmentItems.push({
// shipment_id: shipment.id,
// productId: product.id,
// name: product.name,
// sku: item.sku,
// quantity: item.quantity,
// });
// const stock = await stockRepo.findOne({
// where: {
// stockPointId: data.stockPointId,
// productSku: item.sku,
// },
// });
// stock.quantity -= item.quantity;
// await stockRepo.save(stock);
// await stockRecordRepo.save({
// stockPointId: data.stockPointId,
// productSku: item.sku,
// operationType: StockRecordOperationType.OUT,
// quantityChange: item.quantity,
// operatorId: userId,
// note: `订单${[orderId, ...data.orderIds].join(',')} 发货`,
// });
// }
// await shipmentItemRepo.save(shipmentItems);
// await orderShipmentRepo.save({
// order_id: orderId,
// shipment_id: shipment.id,
// stockPointId: data.stockPointId,
// });
// 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;
// await orderRepo.save(order);
// }
// order.orderStatus = ErpOrderStatus.COMPLETED;
// order.status = OrderStatus.COMPLETED;
// await orderRepo.save(order);
// });
} }
async syncShipment() { async syncShipment() {

View File

@ -84,6 +84,22 @@ export class UniExpressService {
} }
} }
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) { async getLabel(tracking_number: string) {
const body = { const body = {
packageId: tracking_number packageId: tracking_number