仓库管理配置 小飞侠配置
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,10 +1,12 @@
import { BadRequestException, Injectable, Logger, NotFoundException, Inject, forwardRef } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import type { CityWarehouse, FulfillmentProvider, Order } from '@prisma/client';
import { isXfxProviderCode } from '@dukang/shared-types';
import { PrismaService } from '../../common/prisma/prisma.module';
import { CourierService } from '../../integrations/courier/courier.service';
import { CourierPayMode } from '../../integrations/courier/courier.types';
import type { XiaofeixiaConfig } from '../../integrations/courier/courier.config';
import { TradeService } from '../trade/trade.service';
import { FulfillmentProviderService } from './fulfillment-provider.service';
export type ManualShipInput = {
logisticsCompany: string;
@@ -14,16 +16,14 @@ export type ManualShipInput = {
export type HqLogisticsShipInput = ManualShipInput;
const XFX_CODES = new Set(['XFX', 'XIAOFEIXIA']);
@Injectable()
export class FulfillmentService {
private readonly logger = new Logger(FulfillmentService.name);
constructor(
private readonly prisma: PrismaService,
private readonly config: ConfigService,
private readonly courier: CourierService,
private readonly fulfillmentProviderService: FulfillmentProviderService,
@Inject(forwardRef(() => TradeService))
private readonly tradeService: TradeService,
) {}
@@ -69,38 +69,50 @@ export class FulfillmentService {
}
async dispatchApiAuto(order: Order, warehouse: CityWarehouse, provider: FulfillmentProvider) {
if (!XFX_CODES.has(provider.code)) {
if (!isXfxProviderCode(provider.code)) {
this.logger.warn(`承运商 ${provider.code} 自动推单尚未实现,订单 ${order.orderNo} 保持待发货`);
await this.ensureDeliveryRecord(order.id, 'MANUAL', provider.id);
return;
}
const defaults = this.getShipDefaults();
const fromLng = warehouse.lng != null ? Number(warehouse.lng) : defaults.fromLng;
const fromLat = warehouse.lat != null ? Number(warehouse.lat) : defaults.fromLat;
let xfxConfig: XiaofeixiaConfig;
try {
xfxConfig = await this.fulfillmentProviderService.resolveXiaofeixiaConfig(provider.id);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
await this.logDispatchFailure(order, provider, message);
await this.ensureDeliveryRecord(order.id, 'MANUAL', provider.id);
return;
}
const fromLng = warehouse.lng != null ? Number(warehouse.lng) : 113.665;
const fromLat = warehouse.lat != null ? Number(warehouse.lat) : 34.757;
try {
const result = await this.courier.createShipment({
outNumber: order.orderNo,
from: {
name: warehouse.contactName,
mobile: warehouse.contactPhone,
address: warehouse.address,
addressDetail: warehouse.name,
coordinate: { lng: fromLng, lat: fromLat },
const result = await this.courier.createShipment(
{
outNumber: order.orderNo,
from: {
name: warehouse.contactName,
mobile: warehouse.contactPhone,
address: warehouse.address,
addressDetail: warehouse.name,
coordinate: { lng: fromLng, lat: fromLat },
},
to: {
name: order.receiverName,
mobile: order.receiverPhone,
address: `${order.receiverProvince}${order.receiverCity}${order.receiverDistrict}`,
addressDetail: order.receiverAddress,
},
goodsName: order.productName,
goodsNum: order.quantity,
weight: 2,
payMode: CourierPayMode.SENDER,
remark: `仓配自动发货 ${order.orderNo}`,
},
to: {
name: order.receiverName,
mobile: order.receiverPhone,
address: `${order.receiverProvince}${order.receiverCity}${order.receiverDistrict}`,
addressDetail: order.receiverAddress,
},
goodsName: order.productName,
goodsNum: order.quantity,
weight: defaults.weight,
payMode: defaults.payMode,
remark: `仓配自动发货 ${order.orderNo}`,
});
{ xiaofeixia: xfxConfig },
);
const now = new Date();
await this.prisma.$transaction(async (tx) => {
@@ -196,10 +208,20 @@ export class FulfillmentService {
if (order.delivery.provider === 'XFX' && (order.delivery.trackingNo || order.orderNo)) {
try {
const nodes = await this.courier.getTrack({
trackingNumber: order.delivery.trackingNo ?? undefined,
outNumber: order.orderNo,
});
const options = order.delivery.fulfillmentProviderId
? {
xiaofeixia: await this.fulfillmentProviderService.resolveXiaofeixiaConfig(
order.delivery.fulfillmentProviderId,
),
}
: undefined;
const nodes = await this.courier.getTrack(
{
trackingNumber: order.delivery.trackingNo ?? undefined,
outNumber: order.orderNo,
},
options,
);
return {
nodes,
manualQueryUrl: order.delivery.manualQueryUrl,
@@ -208,7 +230,7 @@ export class FulfillmentService {
logisticsCompany: order.delivery.logisticsCompany,
};
} catch {
// fall through to manual fields
// fall through
}
}
@@ -294,13 +316,4 @@ export class FulfillmentService {
if (!tpl) return undefined;
return tpl.replace(/\{trackingNo\}/g, encodeURIComponent(trackingNo));
}
private getShipDefaults() {
return {
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: CourierPayMode.SENDER,
};
}
}