仓库管理配置 小飞侠配置
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-07-16 21:40:25 +08:00
parent f6d97b4ee8
commit bde85a7b82
15 changed files with 580 additions and 197 deletions
@@ -1,5 +1,4 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
@@ -11,6 +10,7 @@ import type { AdminOrdersQueryDto } from './dto/admin-query.dto';
import type { AdminShipOrderDto, HqLogisticsShipDto } from './dto/admin-mutate.dto';
import type { XiaofeixiaCreateShipmentDto } from './dto/admin-courier.dto';
import { FulfillmentService } from '../fulfillment/fulfillment.service';
import { FulfillmentProviderService } from '../fulfillment/fulfillment-provider.service';
@Injectable()
export class AdminOrdersService {
@@ -19,7 +19,7 @@ export class AdminOrdersService {
private readonly tradeService: TradeService,
private readonly xiaofeixiaService: AdminXiaofeixiaService,
private readonly fulfillmentService: FulfillmentService,
private readonly config: ConfigService,
private readonly fulfillmentProviderService: FulfillmentProviderService,
) {}
async list(query: AdminOrdersQueryDto) {
@@ -96,21 +96,6 @@ export class AdminOrdersService {
return this.detail(id);
}
getShipDefaults() {
return {
provider: 'XFX',
providerLabel: '小飞侠',
fromName: this.config.get<string>('SHIP_FROM_NAME') || '杜康仓库',
fromMobile: this.config.get<string>('SHIP_FROM_MOBILE') || '13800000000',
fromAddress: this.config.get<string>('SHIP_FROM_ADDRESS') || '河南省郑州市金水区',
fromAddressDetail: this.config.get<string>('SHIP_FROM_ADDRESS_DETAIL') || '杜康酒业仓',
fromLng: Number(this.config.get<string>('SHIP_FROM_LNG') || 113.665),
fromLat: Number(this.config.get<string>('SHIP_FROM_LAT') || 34.757),
weight: 2,
payMode: '1',
};
}
async shipOrder(id: bigint, dto: AdminShipOrderDto) {
if (dto.provider !== 'XFX') {
throw new BadRequestException('暂仅支持小飞侠配送');
@@ -118,7 +103,10 @@ export class AdminOrdersService {
const order = await this.prisma.order.findUnique({
where: { id },
include: { delivery: true },
include: {
delivery: true,
fulfillmentWarehouse: true,
},
});
if (!order) throw new NotFoundException('订单不存在');
@@ -129,7 +117,23 @@ export class AdminOrdersService {
throw new BadRequestException('该订单已有运单号,请勿重复发货');
}
const defaults = this.getShipDefaults();
const warehouse = order.fulfillmentWarehouse;
const providerId =
order.delivery?.fulfillmentProviderId ??
warehouse?.fulfillmentProviderId ??
null;
let xfxConfig;
if (providerId) {
xfxConfig = await this.fulfillmentProviderService.resolveXiaofeixiaConfig(providerId);
} else {
xfxConfig = await this.fulfillmentProviderService.resolveDefaultXiaofeixiaConfig();
if (!xfxConfig) {
throw new BadRequestException('请先在仓配管理中注册并配置小飞侠承运商');
}
}
const defaults = this.getShipDefaults(warehouse);
const shipmentDto: XiaofeixiaCreateShipmentDto = {
outNumber: order.orderNo,
fromName: dto.fromName || defaults.fromName,
@@ -149,13 +153,14 @@ export class AdminOrdersService {
remark: dto.remark || `HQ发货 ${order.orderNo}`,
};
const result = await this.xiaofeixiaService.createShipment(shipmentDto);
const result = await this.xiaofeixiaService.createShipment(shipmentDto, xfxConfig);
if (!result.ok || !result.data) {
throw new BadRequestException(result.error || '小飞侠创建运单失败');
}
const { providerShipmentId, trackingNumber } = result.data;
const now = new Date();
const resolvedProviderId = providerId;
await this.prisma.$transaction(async (tx) => {
if (order.delivery) {
@@ -163,6 +168,7 @@ export class AdminOrdersService {
where: { orderId: id },
data: {
provider: 'XFX',
fulfillmentProviderId: resolvedProviderId,
trackingNo: trackingNumber,
providerOrderNo: String(providerShipmentId),
shippingAt: now,
@@ -173,6 +179,7 @@ export class AdminOrdersService {
data: {
orderId: id,
provider: 'XFX',
fulfillmentProviderId: resolvedProviderId,
trackingNo: trackingNumber,
providerOrderNo: String(providerShipmentId),
shippingAt: now,
@@ -185,6 +192,40 @@ export class AdminOrdersService {
return this.detail(id);
}
getShipDefaults(warehouse?: {
contactName: string;
contactPhone: string;
address: string;
name: string;
lng: { toNumber?: () => number } | number | null;
lat: { toNumber?: () => number } | number | null;
} | null) {
const lng =
warehouse?.lng != null
? typeof warehouse.lng === 'object' && warehouse.lng && 'toNumber' in warehouse.lng
? Number(warehouse.lng)
: Number(warehouse.lng)
: 113.665;
const lat =
warehouse?.lat != null
? typeof warehouse.lat === 'object' && warehouse.lat && 'toNumber' in warehouse.lat
? Number(warehouse.lat)
: Number(warehouse.lat)
: 34.757;
return {
provider: 'XFX',
providerLabel: '小飞侠',
fromName: warehouse?.contactName || '杜康仓库',
fromMobile: warehouse?.contactPhone || '13800000000',
fromAddress: warehouse?.address || '河南省郑州市金水区',
fromAddressDetail: warehouse?.name || '杜康酒业仓',
fromLng: lng,
fromLat: lat,
weight: 2,
payMode: '1',
};
}
/** 总部传统快递填单(同城无仓 / 跨城) */
async shipLogistics(id: bigint, dto: HqLogisticsShipDto) {
await this.fulfillmentService.shipHqLogistics(id, dto);