API/src/service/logistics.service.ts

876 lines
33 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Inject, Provide, sleep } from '@midwayjs/core';
import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm';
import { Service } from '../entity/service.entity';
import { In, IsNull, Like, Repository } from 'typeorm';
import { ShippingAddress } from '../entity/shipping_address.entity';
import { Order } from '../entity/order.entity';
import { Shipment } from '../entity/shipment.entity';
import { ShipmentItem } from '../entity/shipment_item.entity';
import { OrderShipment } from '../entity/order_shipment.entity';
import { QueryServiceDTO, ShipmentBookDTO, ShipmentFeeBookDTO } from '../dto/logistics.dto';
import {
ErpOrderStatus,
OrderStatus,
ShipmentType,
StockRecordOperationType,
} from '../enums/base.enum';
// import { generateUniqueId } from '../utils/helper.util';
import { FreightcomService } from './freightcom.service';
import { StockRecord } from '../entity/stock_record.entity';
import { Stock } from '../entity/stock.entity';
import { plainToClass } from 'class-transformer';
import { WPService } from './wp.service';
// import { Product } from '../entity/product.entty';
import { ShippingDetailsDTO } from '../dto/freightcom.dto';
import { CanadaPostService } from './canadaPost.service';
import { OrderItem } from '../entity/order_item.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';
import { SiteService } from './site.service';
import { FreightwavesService, RateTryRequest } from './freightwaves.service';
@Provide()
export class LogisticsService {
@InjectEntityModel(Service)
serviceModel: Repository<Service>;
@InjectEntityModel(ShippingAddress)
shippingAddressModel: Repository<ShippingAddress>;
@InjectEntityModel(Stock)
stockModel: Repository<Stock>;
@InjectEntityModel(Order)
orderModel: Repository<Order>;
@InjectEntityModel(StockPoint)
stockPointModel: Repository<StockPoint>
@InjectEntityModel(OrderSale)
orderSaleModel: Repository<OrderSale>;
@InjectEntityModel(Shipment)
shipmentModel: Repository<Shipment>;
@InjectEntityModel(ShipmentItem)
shipmentItemModel: Repository<ShipmentItem>;
@InjectEntityModel(OrderShipment)
orderShipmentModel: Repository<OrderShipment>;
@InjectEntityModel(OrderItem)
orderItem: Repository<OrderItem>;
@Inject()
freightcomService: FreightcomService;
@Inject()
canadaPostService: CanadaPostService;
@Inject()
uniExpressService: UniExpressService;
@Inject()
freightwavesService: FreightwavesService;
@Inject()
wpService: WPService;
@Inject()
orderService: OrderService;
@Inject()
dataSourceManager: TypeORMDataSourceManager;
@Inject()
private readonly siteService: SiteService;
async getServiceList(param: QueryServiceDTO) {
const { pageSize, current, carrier_name, isActive } = param;
const where: Record<string, any> = {};
if (carrier_name) where.carrier_name = Like(`%${carrier_name}%`);
if (isActive !== undefined) where.isActive = isActive;
const [items, total] = await this.serviceModel.findAndCount({
where,
skip: (current - 1) * pageSize,
take: pageSize,
});
return { items, total, current, pageSize };
}
async toggleServiceActive(id: string, isActive: boolean) {
const service = await this.serviceModel.findOne({ where: { id } });
if (!service) {
throw new Error('服务商不存在');
}
service.isActive = isActive;
return this.serviceModel.save(service);
}
async getActiveServices() {
const services = await this.serviceModel.find({
where: { isActive: true },
});
if (!services) {
return [];
}
return services.map(service => service.id);
}
async createShippingAddress(shippingAddress: 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}`);
}
}
//"expressFinish": 0, //是否快递创建完成1完成 0未完成需要轮询 2:失败)
async updateFreightwavesShipmentState(shipment: Shipment) {
try {
const data = await this.freightwavesService.queryOrder({ shipOrderId: shipment.order_id.toString() });
console.log('updateFreightwavesShipmentState data:', data);
// huo
if (data.expressFinish === 2) {
throw new Error('获取运单状态失败,原因为' + data.expressFailMsg)
}
if (data.expressFinish === 0) {
shipment.state = '203';
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) {
const address = await this.shippingAddressModel.findOneBy({ id });
if (!address) {
throw new Error(`发货地址 ID ${id} 不存在`);
}
await this.shippingAddressModel.update(id, shippingAddress);
return await this.shippingAddressModel.findOneBy({ id });
}
async getShippingAddressList() {
return await this.shippingAddressModel.find();
}
async delShippingAddress(id: number) {
const address = await this.shippingAddressModel.findOneBy({ id });
if (!address) {
throw new Error(`发货地址 ID ${id} 不存在`);
}
const result = await this.shippingAddressModel.delete(id);
return result.affected > 0;
}
// async saveTracking(
// orderId: number,
// shipment: Record<string, any>,
// data: ShipmentBookDTO
// ) {
// const order = await this.orderModel.findOneBy({ id: orderId });
// const orderTracking = this.orderTrackingModel.save({
// orderId: String(orderId),
// siteId: order.siteId,
// externalOrderId: order.externalOrderId,
// });
// }
async getRateList(details: ShippingDetailsDTO) {
details.destination.address.country = 'CA';
details.origin.address.country = 'CA';
const { request_id } = await this.freightcomService.getRateEstimate(
details
);
const rateRequest = {
'customer-number': this.canadaPostService.customerNumber,
'parcel-characteristics': {
weight: details?.packaging_properties?.packages?.reduce(
(cur, next) => cur + (next?.measurements?.weight?.value || 0),
0
),
},
'origin-postal-code': details.origin.address.postal_code.replace(
/\s/g,
''
),
destination: {
domestic: {
'postal-code': details.destination.address.postal_code.replace(
/\s/g,
''
),
},
},
};
const canadaPostRates = await this.canadaPostService.getRates(rateRequest);
await sleep(3000);
const rates = await this.freightcomService.getRates(request_id);
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.siteService.get(Number(order.siteId), true);
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) {
const order = await this.orderModel.findOneBy({ id: orderId });
const { sales } = data;
if (!order) {
throw new Error('订单不存在');
}
if (
order.orderStatus !== ErpOrderStatus.PROCESSING &&
order.orderStatus !== ErpOrderStatus.PENDING_RESHIPMENT
) {
throw new Error('订单状态不正确 ');
}
let resShipmentOrder;
try {
resShipmentOrder = this.mepShipment(data);
// 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,
// 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',
// custom_field: {
// 'order_id': order.externalOrderId
// }
// }
// // 添加运单
// resShipmentOrder = await this.uniExpressService.createShipment(reqBody);
// // 记录物流信息,并将订单状态转到完成
if (resShipmentOrder.status === 'SUCCESS') {
order.orderStatus = ErpOrderStatus.COMPLETED;
} else {
throw new Error('运单生成失败');
}
const dataSource = this.dataSourceManager.getDataSource('default');
let transactionError = undefined;
let shipmentId = undefined;
await dataSource.transaction(async manager => {
const orderRepo = manager.getRepository(Order);
const shipmentRepo = manager.getRepository(Shipment);
const tracking_provider = 'UniUni'; // todo: id未确定后写进常数
// 同步物流信息到woocommerce
const site = await this.siteService.get(Number(order.siteId), true);
const res = await this.wpService.createShipment(site, order.externalOrderId, {
tracking_number: resShipmentOrder.data.tno,
tracking_provider: tracking_provider,
});
if (order.orderStatus === ErpOrderStatus.COMPLETED) {
const shipment = await shipmentRepo.save({
tracking_provider: tracking_provider,
tracking_id: res.data.tracking_id,
unique_id: resShipmentOrder.data.uni_order_sn,
stockPointId: String(data.stockPointId), // todo
state: resShipmentOrder.data.uni_status_code,
return_tracking_number: resShipmentOrder.data.tno,
fee: data.details.shipmentFee,
order: order
});
order.shipmentId = shipment.id;
shipmentId = shipment.id;
}
// 同步订单状态到woocommerce
if (order.status !== OrderStatus.COMPLETED) {
await this.wpService.updateOrder(site, order.externalOrderId, {
status: OrderStatus.COMPLETED,
});
order.status = OrderStatus.COMPLETED;
}
order.orderStatus = ErpOrderStatus.COMPLETED;
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() {
try {
const shipments = await this.shipmentModel.find({
where: {
primary_tracking_number: IsNull(),
},
});
if (!shipments) return;
for (const shipment of shipments) {
await this.getShipment(shipment.id);
}
} catch (error) {
console.log('syncShipment', error);
}
}
async syncShipmentStatus() {
const shipments = await this.shipmentModel.find({
where: {
state: In([
'draft',
'waiting-for-transit',
'waiting-for-scheduling',
'in-transit',
]),
},
});
if (!shipments) return;
for (const item of shipments) {
try {
let res;
if (item.type === ShipmentType.FREIGHTCOM) {
res = await this.freightcomService.getShipment(item.id);
} else if (item.type === ShipmentType.CANADAPOST) {
res = await this.canadaPostService.getShipment(
item.primary_tracking_number
);
res.shipment.id = item.id;
}
if (!res) return;
const shipment = plainToClass(Shipment, res.shipment);
this.shipmentModel.save(shipment);
} catch (error) {
console.log('syncShipmentStatus error');
}
}
}
async getShipment(id: number) {
const orderShipments: OrderShipment[] = await this.orderShipmentModel.find({
where: { shipment_id: id },
});
if (!orderShipments || orderShipments.length === 0) return;
const oldShipment = await this.shipmentModel.findOneBy({ id });
let res;
if (oldShipment.type === ShipmentType.FREIGHTCOM) {
res = await this.freightcomService.getShipment(id);
} else if (oldShipment.type === ShipmentType.CANADAPOST) {
res = await this.canadaPostService.getShipment(
oldShipment.primary_tracking_number
);
res.shipment.id = oldShipment.id;
}
if (!res) return;
const shipment = plainToClass(Shipment, res.shipment);
await this.shipmentModel.save(shipment);
for (const orderShipment of orderShipments) {
const order = await this.orderModel.findOneBy({
id: orderShipment.order_id,
});
const site = await this.siteService.get(Number(order.siteId), true);
await this.wpService.updateOrder(site, order.externalOrderId, {
status: OrderStatus.COMPLETED,
});
await this.wpService.createShipment(site, order.externalOrderId, {
tracking_number: shipment.primary_tracking_number,
tracking_provider: shipment?.rate?.carrier_name,
});
}
}
async delShipment(id: number, userId: number) {
const shipment = await this.shipmentModel.findOneBy({ id });
if (!shipment) throw new Error('物流不存在');
if (shipment.type === ShipmentType.FREIGHTCOM) {
await this.freightcomService.cancelShipment(shipment.id);
} else if (shipment.type === ShipmentType.CANADAPOST) {
await this.canadaPostService.cancelShipment(shipment.id);
}
const dataSource = this.dataSourceManager.getDataSource('default');
return dataSource.transaction(async manager => {
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);
const orderShipments = await orderShipmentRepo.findBy({
shipment_id: id,
});
const shipmentItems = await shipmentItemRepo.findBy({ shipment_id: id });
await shipmentRepo.delete({ id });
await shipmentItemRepo.delete({ shipment_id: id });
await orderShipmentRepo.delete({ shipment_id: id });
for (const item of shipmentItems) {
const stock = await stockRepo.findOne({
where: {
stockPointId: orderShipments[0].stockPointId,
productSku: item.sku,
},
});
stock.quantity += item.quantity;
await stockRepo.save(stock);
await stockRecordRepo.save({
stockPointId: orderShipments[0].stockPointId,
productSku: item.sku,
operationType: StockRecordOperationType.IN,
quantityChange: item.quantity,
operatorId: userId,
note: `订单${orderShipments.map(v => v.order_id).join(',')} 取消发货`,
});
}
await orderRepo.update(
{ id: In(orderShipments.map(v => v.order_id)) },
{
orderStatus: ErpOrderStatus.PENDING_RESHIPMENT,
}
);
});
}
async getOrderList(number: string) {
const orders = await this.orderModel.find({
where: {
externalOrderId: Like(`%${number}%`),
},
});
// 从数据库批量获取站点信息,构建映射以避免 N+1 查询
const siteIds = Array.from(new Set(orders.map(o => o.siteId).filter(Boolean)));
const { items: sites } = await this.siteService.list({ current: 1, pageSize: 1000, ids: siteIds.join(',') }, false);
const siteMap = new Map(sites.map((s: any) => [String(s.id), s.siteName]));
return orders.map(order => ({
...order,
siteName: siteMap.get(order.siteId) || '',
}));
}
async getListByOrderId(id: number) {
const item = await this.orderItem.find({ where: { orderId: id } });
const saleItem = await this.orderSaleModel.find({ where: { orderId: id } });
return {
item,
saleItem
};
}
async getList(param: Record<string, any>) {
const {
pageSize = 10,
current = 1,
return_tracking_number,
stockPointId,
externalOrderId,
} = param;
const offset = pageSize * (current - 1);
const values: any[] = [];
let whereClause = 'WHERE 1=1';
if (return_tracking_number) {
whereClause += ' AND s.return_tracking_number LIKE ?';
values.push(`%${return_tracking_number}%`);
}
if (stockPointId) {
whereClause += ' AND sp.id = ?';
values.push(stockPointId);
}
// todo增加订单号搜索
if (externalOrderId) {
whereClause += ' AND o.externalOrderId = ?';
values.push(externalOrderId);
}
const sql = `
SELECT s.*, sp.name, o.externalOrderId, o.siteId
FROM shipment s
LEFT JOIN \`order\` o ON s.order_id = o.id
LEFT JOIN stock_point sp ON s.stock_point_id = sp.id
${whereClause}
ORDER BY s.createdAt DESC
LIMIT ?, ?
`;
values.push(offset, Number(pageSize));
const items = await this.serviceModel.query(sql, values);
// 单独计算总数
const countSql = `
SELECT COUNT(DISTINCT s.id) as total
FROM shipment s
LEFT JOIN \`order\` o ON s.order_id = o.id
LEFT JOIN stock_point sp ON s.stock_point_id = sp.id
${whereClause}
`;
const countResult = await this.serviceModel.query(
countSql,
values.slice(0, values.length - 2)
);
const total = countResult[0]?.total || 0;
return { items, total, current, pageSize };
}
async mepShipment(data: ShipmentBookDTO) {
try {
const stock_point = await this.stockPointModel.findOneBy({ id: data.stockPointId });
let resShipmentOrder;
if (data.shipmentPlatform === 'uniuni') {
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,
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',
custom_field: {
'order_id': '' // todo: 需要获取订单的externalOrderId
}
};
// 添加运单
resShipmentOrder = await this.uniExpressService.createShipment(reqBody);
}
if (data.shipmentPlatform === 'freightwaves') {
// 根据TMS系统对接说明文档格式化参数
const reqBody = {
shipCompany: '', // 渠道(可以不指定)
partnerOrderNumber: `order-${Date.now()}`, // 第三方客户订单编号(唯一)
warehouseId: String(stock_point.upStreamStockPointId), // 发货仓库编号(转换为字符串类型)
shipper: {
name: data.details.origin.contact_name, // 姓名
phone: data.details.origin.phone_number.number, // 电话提取number属性转换为字符串
company: '', // 公司
countryCode: data.details.origin.address.country, // 国家Code
city: data.details.origin.address.city, // 城市
state: data.details.origin.address.region, // 州/省Code两个字母缩写
address1: data.details.origin.address.address_line_1, // 详细地址
address2: '', // 详细地址2Address类型中没有address_line_2属性
postCode: data.details.origin.address.postal_code.replace(/\s/g, ''), // 邮编
countryName: data.details.origin.address.country, // 国家名称Address类型中没有country_name属性使用country代替
cityName: data.details.origin.address.city, // 城市名称
stateName: data.details.origin.address.region, // 州/省名称
companyName: '' // 公司名称
},
reciver: {
name: data.details.destination.contact_name, // 姓名
phone: data.details.destination.phone_number.number, // 电话
company: '', // 公司
countryCode: data.details.destination.address.country, // 国家Code
city: data.details.destination.address.city, // 城市
state: data.details.destination.address.region, // 州/省Code两个字母的缩写
address1: data.details.destination.address.address_line_1, // 详细地址
address2: '', // 详细地址2Address类型中没有address_line_2属性
postCode: data.details.destination.address.postal_code.replace(/\s/g, ''), // 邮编
countryName: data.details.destination.address.country, // 国家名称Address类型中没有country_name属性使用country代替
cityName: data.details.destination.address.city, // 城市名称
stateName: data.details.destination.address.region, // 州/省名称
companyName: '' // 公司名称
},
packages: [
{
dimensions: {
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, // 高
lengthUnit: (data.details.packaging_properties.packages[0].measurements.cuboid.unit === 'cm' ? 'CM' : 'IN') as 'CM' | 'IN', // 长度单位IN,CM
weight: data.details.packaging_properties.packages[0].measurements.weight.value, // 重量
weightUnit: (data.details.packaging_properties.packages[0].measurements.weight.unit === 'kg' ? 'KG' : 'LB') as 'KG' | 'LB' // 重量单位LB,KG
},
currency: 'CAD', // 币种默认CAD
description: 'Package' // 包裹描述(确保是字符串类型)
}
],
signService: 0 as 0 | 1, // 签名服务 0不使用, 1使用
declaration: {
boxNo: 'BOX-001', // 箱号
sku: 'DEFAULT-SKU', // SKU
cnname: '默认商品', // 中文名称
enname: 'Default Product', // 英文名称
declaredPrice: 0, // 申报价格
declaredQty: 1, // 申报数量
material: 'General', // 材质
intendedUse: 'General use', // 用途
cweight: data.details.packaging_properties.packages[0].measurements.weight.value, // 重量
hsCode: '39269090', // 海关编码
battery: 'No' // 是否含电池
}
};
// 调用freightwaves费用试算或创建订单API
// 注意:根据实际需要调用对应的方法
// resShipmentOrder = await this.freightwavesService.rateTry(reqBody); // 费用试算
resShipmentOrder = await this.freightwavesService.createOrder(reqBody); // 创建订单
}
return resShipmentOrder;
} catch (error) {
console.log('物流订单处理失败:', error); // 使用console.log代替this.log
throw error;
}
}
/**
* 将ShipmentFeeBookDTO转换为freightwaves的RateTryRequest格式
* @param data ShipmentFeeBookDTO数据
* @returns RateTryRequest格式的数据
*/
convertToFreightwavesRateTry(data: ShipmentFeeBookDTO): Omit<RateTryRequest, 'partner'> {
// 转换为RateTryRequest格式
return {
shipCompany: '', // 必填但ShipmentFeeBookDTO中缺少
partnerOrderNumber: `order-${Date.now()}`, // 必填,使用时间戳生成
warehouseId: String(data.stockPointId), // 可选使用stockPointId转换
shipper: {
name: data.sender, // 必填
phone: data.startPhone, // 必填
company: '', // 必填但ShipmentFeeBookDTO中缺少
countryCode: data.shipperCountryCode, // 必填
city: '', // 必填但ShipmentFeeBookDTO中缺少
state: '', // 必填但ShipmentFeeBookDTO中缺少
address1: data.pickupAddress, // 必填
address2: '', // 必填但ShipmentFeeBookDTO中缺少
postCode: data.startPostalCode, // 必填
countryName: '', // 必填但ShipmentFeeBookDTO中缺少
cityName: '', // 必填但ShipmentFeeBookDTO中缺少
stateName: '', // 必填但ShipmentFeeBookDTO中缺少
companyName: '', // 必填但ShipmentFeeBookDTO中缺少
},
reciver: {
name: data.receiver, // 必填
phone: data.receiverPhone, // 必填
company: '', // 必填但ShipmentFeeBookDTO中缺少
countryCode: data.country, // 必填使用country代替countryCode
city: data.city, // 必填
state: data.province, // 必填使用province代替state
address1: data.deliveryAddress, // 必填
address2: '', // 必填但ShipmentFeeBookDTO中缺少
postCode: data.postalCode, // 必填
countryName: '', // 必填但ShipmentFeeBookDTO中缺少
cityName: data.city, // 必填使用city代替cityName
stateName: data.province, // 必填使用province代替stateName
companyName: '', // 必填但ShipmentFeeBookDTO中缺少
},
packages: [
{
dimensions: {
length: data.length, // 必填
width: data.width, // 必填
height: data.height, // 必填
lengthUnit: (data.dimensionUom.toUpperCase() === 'CM' ? 'CM' : 'IN') as 'CM' | 'IN', // 必填,转换为有效的单位
weight: data.weight, // 必填
weightUnit: (data.weightUom.toUpperCase() === 'KG' ? 'KG' : 'LB') as 'KG' | 'LB', // 必填,转换为有效的单位
},
currency: 'CAD', // 必填但ShipmentFeeBookDTO中缺少使用默认值
description: 'Package', // 必填但ShipmentFeeBookDTO中缺少使用默认值
},
],
signService: 0, // 可选,默认不使用签名服务
};
}
/**
* 获取ShipmentFeeBookDTO缺少的freightwaves必填字段
* @returns 缺少的必填字段列表
*/
getMissingFreightwavesFields(): string[] {
return [
'shipCompany', // 渠道
'partnerOrderNumber', // 第三方客户订单编号
'shipper.company', // 发货人公司
'shipper.city', // 发货人城市
'shipper.state', // 发货人州/省Code
'shipper.address2', // 发货人详细地址2
'shipper.countryName', // 发货人国家名称
'shipper.cityName', // 发货人城市名称
'shipper.stateName', // 发货人州/省名称
'shipper.companyName', // 发货人公司名称
'reciver.company', // 收货人公司
'reciver.address2', // 收货人详细地址2
'reciver.countryName', // 收货人国家名称
'reciver.companyName', // 收货人公司名称
'packages[0].currency', // 包裹币种
'packages[0].description', // 包裹描述
'partner', // 商户ID
];
}
}