zksu
/
API
forked from yoone/API
1
0
Fork 0

feat(物流): 添加物流别名映射功能并优化物流公司处理

- 新增 logistics_alias 实体用于存储物流公司别名映射
- 修改 ShipmentBookDTO 中 courierCompany 的校验规则为 any 类型
- 在订单服务中实现物流别名查询和映射功能
- 移除物流服务中 courierCompany 的特殊处理逻辑
This commit is contained in:
zhuotianyuan 2026-01-30 14:24:33 +08:00
parent 5488e1b7c6
commit 4bde698625
5 changed files with 159 additions and 117 deletions

View File

@ -42,6 +42,7 @@ import DictSeeder from '../db/seeds/dict.seeder';
import CategorySeeder from '../db/seeds/category.seeder';
import CategoryAttributeSeeder from '../db/seeds/category_attribute.seeder';
import { SiteSku } from '../entity/site-sku.entity';
import { logisticsAlias } from '../entity/logistics_alias.emtity';
export default {
// use for cookie sign key, should change to your own and keep security
@ -88,6 +89,7 @@ export default {
Area,
CategoryAttribute,
Category,
logisticsAlias,
],
synchronize: true,
logging: false,

View File

@ -25,7 +25,7 @@ export class ShipmentBookDTO {
shipmentPlatform: string;
@ApiProperty()
@Rule(RuleType.string())
@Rule(RuleType.any())
courierCompany: string;
}

View File

@ -0,0 +1,34 @@
import { ApiProperty } from '@midwayjs/swagger';
import { Entity,CreateDateColumn,UpdateDateColumn, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('logistics_alias')
export class logisticsAlias {
@PrimaryGeneratedColumn()
id: number;
@ApiProperty({ type: 'string' })
@Column()
logistics_company:string
@ApiProperty({ type: 'string' })
@Column()
logistics_alias:string
@ApiProperty({ type: 'string' })
@Column()
platform:string
// 是否可删除
@Column({ default: true, comment: '是否可删除' })
deletable: boolean;
// 创建时间
@CreateDateColumn()
createdAt: Date;
// 更新时间
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -735,14 +735,10 @@ export class LogisticsService {
if (data.shipmentPlatform === 'freightwaves') {
let courierCompany: string = "";
if (data.courierCompany != "最优物流") {
courierCompany = data.courierCompany;
}
// 根据TMS系统对接说明文档格式化参数
const reqBody: any = {
// shipCompany: 'UPSYYZ7000NEW',
shipCompany: courierCompany,
shipCompany: data.courierCompany,
partnerOrderNumber: order.siteId + '-' + order.externalOrderId,
warehouseId: '25072621030107400060',
shipper: {
@ -836,15 +832,12 @@ export class LogisticsService {
id: data.address_id,
},
})
const address = shipments?.address;
let courierCompany: string = "";
if (data.courierCompany != "最优物流") {
courierCompany = data.courierCompany;
}
// 转换为RateTryRequest格式
const r = {
//shipCompany: 'UPSYYZ7000NEW', // 必填但ShipmentFeeBookDTO中缺少
shipCompany: courierCompany,
shipCompany: data.courierCompany,
partnerOrderNumber: `order-${Date.now()}`, // 必填,使用时间戳生成
warehouseId: '25072621030107400060', // 可选使用stockPointId转换
shipper: {

View File

@ -42,6 +42,7 @@ import { UnifiedOrderDTO } from '../dto/site-api.dto';
import { CustomerService } from './customer.service';
import { ProductService } from './product.service';
import { Site } from '../entity/site.entity';
import { logisticsAlias } from '../entity/logistics_alias.emtity';
@Provide()
export class OrderService {
@ -54,6 +55,9 @@ export class OrderService {
@InjectEntityModel(Order)
orderModel: Repository<Order>;
@InjectEntityModel(logisticsAlias)
logisticsAliasModel: Repository<logisticsAlias>;
@InjectEntityModel(User)
userModel: Repository<User>;
@ -151,7 +155,7 @@ export class OrderService {
where: { externalOrderId: String(order.id), siteId: siteId },
});
if (!existingOrder) {
this.logger.debug("数据库中不存在", order.id, '订单状态:', order.status)
this.logger.debug("数据库中不存在", order.id, '订单状态:', order.status)
}
// 同步单个订单
await this.syncSingleOrder(siteId, order);
@ -630,7 +634,7 @@ export class OrderService {
await this.saveOrderItem(entity);
// 为每个订单项创建对应的销售项(OrderSale)
const site = await this.siteService.get(siteId);
await this.saveOrderSale(entity,site);
await this.saveOrderSale(entity, site);
}
}
@ -720,7 +724,7 @@ export class OrderService {
*/
// TODO 这里存的是库存商品实际
// 所以叫做 orderInventoryItems 可能更合适
async saveOrderSale(orderItem: OrderItem,site:Site) {
async saveOrderSale(orderItem: OrderItem, site: Site) {
const currentOrderSale = await this.orderSaleModel.find({
where: {
siteId: orderItem.siteId,
@ -733,12 +737,12 @@ export class OrderService {
if (!orderItem.sku) return;
// 从数据库查询产品,关联查询组件
const componentDetails = await this.productService.getComponentDetailFromSiteSku({ sku: orderItem.sku, name: orderItem.name },orderItem.quantity,site);
if(!componentDetails?.length){
return
const componentDetails = await this.productService.getComponentDetailFromSiteSku({ sku: orderItem.sku, name: orderItem.name }, orderItem.quantity, site);
if (!componentDetails?.length) {
return
}
const orderSales: OrderSale[] = componentDetails.map(({product, parentProduct, quantity}) => {
const orderSales: OrderSale[] = componentDetails.map(({ product, parentProduct, quantity }) => {
if (!product) return null
const attrsObj = this.productService.getAttributesObject(product.attributes)
const orderSale = plainToClass(OrderSale, {
@ -2821,107 +2825,116 @@ export class OrderService {
return result;
}
// 从 CSV 导入产品;存在则更新,不存在则创建
/**
* Wintopay
* @param file
* @returns
*/
// 从 CSV 导入产品;存在则更新,不存在则创建
/**
* Wintopay
* @param file
* @returns
*/
async importWintopayTable(file: any): Promise<any> {
let updated = 0;
const errors: BatchErrorItem[] = [];
// 解析文件获取工作表
let buffer: Buffer;
if (Buffer.isBuffer(file)) {
buffer = file;
} else if (file?.data) {
if (typeof file.data === 'string') {
buffer = fs.readFileSync(file.data);
} else {
buffer = file.data;
}
} else {
throw new Error('无效的文件输入');
}
const workbook = xlsx.read(buffer, { type: 'buffer', codepage: 65001 });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
// 获取表头和数据
const jsonData = xlsx.utils.sheet_to_json(worksheet, { header: 1 });
const headers = jsonData[0] as string[];
const dataRows = jsonData.slice(1) as any[][];
// 查找各列的索引
const columnIndices = {
orderNumber: headers.indexOf('订单号'),
logisticsCompany: headers.indexOf('物流公司'),
trackingNumber: headers.indexOf('单号-单元格文本格式'),
orderCreateTime: headers.indexOf('订单创建时间'),
orderEmail: headers.indexOf('订单邮箱'),
orderSite: headers.indexOf('订单网站'),
name: headers.indexOf('姓名'),
refund: headers.indexOf('退款'),
chargeback: headers.indexOf('拒付')
};
// 遍历数据行
for (let i = 0; i < dataRows.length; i++) {
const row = dataRows[i];
const orderNumber = row[columnIndices.orderNumber];
if (!orderNumber) {
errors.push({ identifier: `${i + 2}`, error: '订单号为空' });
continue;
}
try {
let orderNumbers="";
if (orderNumber.includes('_')&&orderNumber.includes('-')) {
orderNumbers = orderNumber.split('_')[0].toString();
orderNumbers = orderNumbers.split('-')[1];
}
// 通过订单号查询订单
const order = await this.orderModel.findOne({ where: { externalOrderId: orderNumbers } });
if (order) {
// 通过orderId查询fulfillments
const fulfillments = await this.orderFulfillmentModel.find({ where: { order_id: order.id } });
if (fulfillments && fulfillments.length > 0) {
const fulfillment = fulfillments[0]; // 假设每个订单只有一个物流信息
// 回填物流信息
if (columnIndices.logisticsCompany !== -1) {
row[columnIndices.logisticsCompany] = fulfillment.shipping_provider || '';
}
if (columnIndices.trackingNumber !== -1) {
row[columnIndices.trackingNumber] = fulfillment.tracking_number || '';
}
updated++;
}
}
} catch (error) {
errors.push({ identifier: `${i + 2}`, error: `处理失败: ${error.message}` });
}
}
// 将数据转换为对象数组,与 exportOrder 方法返回格式一致
const resultData = dataRows.map((row, index) => {
const rowData: any = {};
headers.forEach((header, colIndex) => {
rowData[header] = row[colIndex] || '';
});
// 添加行号信息
rowData['行号'] = index + 2;
return rowData;
});
// 返回XLSX buffer内容给前端
// const xlsxBuffer = await this.exportToXlsx(resultData, { type: 'buffer' });
return resultData;
let updated = 0;
const errors: BatchErrorItem[] = [];
// 解析文件获取工作表
let buffer: Buffer;
if (Buffer.isBuffer(file)) {
buffer = file;
} else if (file?.data) {
if (typeof file.data === 'string') {
buffer = fs.readFileSync(file.data);
} else {
buffer = file.data;
}
} else {
throw new Error('无效的文件输入');
}
const workbook = xlsx.read(buffer, { type: 'buffer', codepage: 65001 });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
// 获取表头和数据
const jsonData = xlsx.utils.sheet_to_json(worksheet, { header: 1 });
const headers = jsonData[0] as string[];
const dataRows = jsonData.slice(1) as any[][];
// 查找各列的索引
const columnIndices = {
orderNumber: headers.indexOf('订单号'),
logisticsCompany: headers.indexOf('物流公司'),
trackingNumber: headers.indexOf('单号-单元格文本格式'),
orderCreateTime: headers.indexOf('订单创建时间'),
orderEmail: headers.indexOf('订单邮箱'),
orderSite: headers.indexOf('订单网站'),
name: headers.indexOf('姓名'),
refund: headers.indexOf('退款'),
chargeback: headers.indexOf('拒付')
};
const logisticsAliases = await this.logisticsAliasModel.find();
// 构建物流公司别名映射
const logisticsAliasMap = new Map(logisticsAliases.map(alias => [ alias.logistics_alias,alias.logistics_company]));
// 遍历数据行
for (let i = 0; i < dataRows.length; i++) {
const row = dataRows[i];
const orderNumber = row[columnIndices.orderNumber];
if (!orderNumber) {
errors.push({ identifier: `${i + 2}`, error: '订单号为空' });
continue;
}
try {
let orderNumbers = orderNumber;
// 确保 orderNumber 是字符串类型
const orderNumberStr = String(orderNumber);
if (orderNumberStr.includes('_') && orderNumberStr.includes('-')) {
orderNumbers = orderNumberStr.split('_')[0].toString();
orderNumbers = orderNumbers.split('-')[1];
}
// 通过订单号查询订单
const order = await this.orderModel.findOne({ where: { externalOrderId: orderNumbers } });
if (order) {
// 通过orderId查询fulfillments
const fulfillments = await this.orderFulfillmentModel.find({ where: { order_id: order.id } });
if (fulfillments && fulfillments.length > 0) {
const fulfillment = fulfillments[0]; // 假设每个订单只有一个物流信息
const shipping_provider = logisticsAliasMap.get(fulfillment.shipping_provider);
// 回填物流信息
if (columnIndices.logisticsCompany !== -1) {
row[columnIndices.logisticsCompany] = shipping_provider || '';
}
if (columnIndices.trackingNumber !== -1) {
row[columnIndices.trackingNumber] = fulfillment.tracking_number || '';
}
updated++;
}
}
} catch (error) {
errors.push({ identifier: `${i + 2}`, error: `处理失败: ${error.message}` });
}
}
// 将数据转换为对象数组,与 exportOrder 方法返回格式一致
const resultData = dataRows.map((row, index) => {
const rowData: any = {};
headers.forEach((header, colIndex) => {
rowData[header] = row[colIndex] || '';
});
// 添加行号信息
rowData['行号'] = index + 2;
return rowData;
});
// 返回XLSX buffer内容给前端
// const xlsxBuffer = await this.exportToXlsx(resultData, { type: 'buffer' });
return resultData;
}
}