系统判断配送限制
This commit is contained in:
@@ -146,6 +146,8 @@ export type AdminOrderRow = {
|
|||||||
city?: { id: string; name: string; code: string };
|
city?: { id: string; name: string; code: string };
|
||||||
fulfillmentWarehouseId?: string | null;
|
fulfillmentWarehouseId?: string | null;
|
||||||
fulfillmentWarehouse?: { id: string; name: string } | null;
|
fulfillmentWarehouse?: { id: string; name: string } | null;
|
||||||
|
fulfillmentHold?: boolean;
|
||||||
|
fulfillmentHoldReason?: string | null;
|
||||||
user?: { id: string; userNo: string; phone: string | null; nickname: string | null };
|
user?: { id: string; userNo: string; phone: string | null; nickname: string | null };
|
||||||
delivery?: {
|
delivery?: {
|
||||||
provider: string;
|
provider: string;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
ORDER_STATUS_LABELS,
|
ORDER_STATUS_LABELS,
|
||||||
fmtTime,
|
fmtTime,
|
||||||
} from '../lib/constants';
|
} from '../lib/constants';
|
||||||
|
import { FULFILLMENT_HOLD_REASON_LABELS } from '@dukang/shared-types';
|
||||||
|
|
||||||
type ShipDefaults = {
|
type ShipDefaults = {
|
||||||
provider: string;
|
provider: string;
|
||||||
@@ -202,6 +203,7 @@ export default function OrdersPage() {
|
|||||||
if (values.status) qs.set('status', values.status);
|
if (values.status) qs.set('status', values.status);
|
||||||
if (values.cityId) qs.set('cityId', values.cityId);
|
if (values.cityId) qs.set('cityId', values.cityId);
|
||||||
if (values.receiverPhone) qs.set('receiverPhone', values.receiverPhone);
|
if (values.receiverPhone) qs.set('receiverPhone', values.receiverPhone);
|
||||||
|
if (values.fulfillmentHold) qs.set('fulfillmentHold', 'true');
|
||||||
const res = await request<Paginated<AdminOrderRow>>(`/admin/orders?${qs}`);
|
const res = await request<Paginated<AdminOrderRow>>(`/admin/orders?${qs}`);
|
||||||
setData(res);
|
setData(res);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -395,9 +397,12 @@ export default function OrdersPage() {
|
|||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
width: 100,
|
width: 140,
|
||||||
render: (s) => (
|
render: (s, row) => (
|
||||||
|
<Space size={4} wrap>
|
||||||
<Tag color={ORDER_STATUS_COLORS[s] || 'default'}>{ORDER_STATUS_LABELS[s] || s}</Tag>
|
<Tag color={ORDER_STATUS_COLORS[s] || 'default'}>{ORDER_STATUS_LABELS[s] || s}</Tag>
|
||||||
|
{row.fulfillmentHold ? <Tag color="orange">大单待确认</Tag> : null}
|
||||||
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -477,6 +482,14 @@ export default function OrdersPage() {
|
|||||||
<Form.Item name="receiverPhone" label="收货手机">
|
<Form.Item name="receiverPhone" label="收货手机">
|
||||||
<Input allowClear />
|
<Input allowClear />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item name="fulfillmentHold" label="大单拦截" valuePropName="checked">
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
style={{ width: 140 }}
|
||||||
|
placeholder="全部"
|
||||||
|
options={[{ value: true, label: '仅待确认大单' }]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="primary" htmlType="submit">查询</Button>
|
<Button type="primary" htmlType="submit">查询</Button>
|
||||||
@@ -550,9 +563,17 @@ export default function OrdersPage() {
|
|||||||
{detail.fulfillmentWarehouse?.name || '未分配'}
|
{detail.fulfillmentWarehouse?.name || '未分配'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="状态">
|
<Descriptions.Item label="状态">
|
||||||
|
<Space>
|
||||||
<Tag color={ORDER_STATUS_COLORS[detail.status] || 'default'}>
|
<Tag color={ORDER_STATUS_COLORS[detail.status] || 'default'}>
|
||||||
{ORDER_STATUS_LABELS[detail.status] || detail.status}
|
{ORDER_STATUS_LABELS[detail.status] || detail.status}
|
||||||
</Tag>
|
</Tag>
|
||||||
|
{detail.fulfillmentHold ? (
|
||||||
|
<Tag color="orange">
|
||||||
|
{FULFILLMENT_HOLD_REASON_LABELS[detail.fulfillmentHoldReason || ''] ||
|
||||||
|
'大单待确认'}
|
||||||
|
</Tag>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="用户">{detail.user?.userNo} / {detail.user?.nickname}</Descriptions.Item>
|
<Descriptions.Item label="用户">{detail.user?.userNo} / {detail.user?.nickname}</Descriptions.Item>
|
||||||
<Descriptions.Item label="商品额">¥{detail.productAmount}</Descriptions.Item>
|
<Descriptions.Item label="商品额">¥{detail.productAmount}</Descriptions.Item>
|
||||||
@@ -864,6 +885,18 @@ export default function OrdersPage() {
|
|||||||
>
|
>
|
||||||
{shipTarget && (
|
{shipTarget && (
|
||||||
<>
|
<>
|
||||||
|
{shipTarget.fulfillmentHold ? (
|
||||||
|
<Alert
|
||||||
|
type="warning"
|
||||||
|
showIcon
|
||||||
|
style={{ marginBottom: 12 }}
|
||||||
|
message="大单已拦截自动推小飞侠"
|
||||||
|
description={
|
||||||
|
FULFILLMENT_HOLD_REASON_LABELS[shipTarget.fulfillmentHoldReason || ''] ||
|
||||||
|
'≥10箱订单需总部确认:可选仓推小飞侠,或改用快递自配送。'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<Descriptions size="small" column={1} style={{ marginBottom: 16 }}>
|
<Descriptions size="small" column={1} style={{ marginBottom: 16 }}>
|
||||||
<Descriptions.Item label="城市">{shipTarget.city?.name || '—'}</Descriptions.Item>
|
<Descriptions.Item label="城市">{shipTarget.city?.name || '—'}</Descriptions.Item>
|
||||||
<Descriptions.Item label="收货">
|
<Descriptions.Item label="收货">
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import {
|
|||||||
calcBenefitAmount,
|
calcBenefitAmount,
|
||||||
calcRedeemSettleAmount,
|
calcRedeemSettleAmount,
|
||||||
calcLogisticsFeeByBottles,
|
calcLogisticsFeeByBottles,
|
||||||
|
calcOrderBoxCount,
|
||||||
|
shouldHoldAutoCourierDispatch,
|
||||||
validateMinPurchase,
|
validateMinPurchase,
|
||||||
validateRedeemAmount,
|
validateRedeemAmount,
|
||||||
allocateBenefitCoupons,
|
allocateBenefitCoupons,
|
||||||
@@ -99,6 +101,15 @@ describe('calcLogisticsFeeByBottles', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('shouldHoldAutoCourierDispatch', () => {
|
||||||
|
it('holds at 10 full boxes (60 bottles)', () => {
|
||||||
|
expect(shouldHoldAutoCourierDispatch(59)).toBe(false);
|
||||||
|
expect(shouldHoldAutoCourierDispatch(60)).toBe(true);
|
||||||
|
expect(shouldHoldAutoCourierDispatch(66)).toBe(true);
|
||||||
|
expect(calcOrderBoxCount(60)).toBe(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('allocateBenefitCoupons', () => {
|
describe('allocateBenefitCoupons', () => {
|
||||||
const coupons = [
|
const coupons = [
|
||||||
{ id: '1', balance: 300, createdAt: 1 },
|
{ id: '1', balance: 300, createdAt: 1 },
|
||||||
|
|||||||
@@ -95,6 +95,30 @@ export function calcRedeemSettleAmount(amount: number, settlementRate: number):
|
|||||||
return Math.round(amount * settlementRate * 100) / 100;
|
return Math.round(amount * settlementRate * 100) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 箱规默认瓶数(跨城起购 / 物流计价 / 大单拦截) */
|
||||||
|
export const BOTTLES_PER_BOX = 6;
|
||||||
|
|
||||||
|
/** 小飞侠自动推单上限箱数:达到该箱数起拦截,需总部确认后推单或自配送 */
|
||||||
|
export const XFX_AUTO_DISPATCH_MAX_BOXES = 10;
|
||||||
|
|
||||||
|
/** 按箱规向上取整得到箱数 */
|
||||||
|
export function calcOrderBoxCount(quantity: number, bottlesPerBox = BOTTLES_PER_BOX): number {
|
||||||
|
const qty = Math.floor(Number(quantity) || 0);
|
||||||
|
if (qty <= 0 || !(bottlesPerBox > 0)) return 0;
|
||||||
|
return Math.ceil(qty / bottlesPerBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 是否因大单拦截自动推承运商(默认 ≥10 箱 = 60 瓶) */
|
||||||
|
export function shouldHoldAutoCourierDispatch(
|
||||||
|
quantity: number,
|
||||||
|
options?: { bottlesPerBox?: number; maxBoxes?: number },
|
||||||
|
): boolean {
|
||||||
|
const bottlesPerBox = options?.bottlesPerBox ?? BOTTLES_PER_BOX;
|
||||||
|
const maxBoxes = options?.maxBoxes ?? XFX_AUTO_DISPATCH_MAX_BOXES;
|
||||||
|
const qty = Math.floor(Number(quantity) || 0);
|
||||||
|
return qty >= bottlesPerBox * maxBoxes;
|
||||||
|
}
|
||||||
|
|
||||||
/** 物流(快递)按瓶计价规则,如小飞侠:2瓶6元、加一瓶+2元、6瓶一箱14元 */
|
/** 物流(快递)按瓶计价规则,如小飞侠:2瓶6元、加一瓶+2元、6瓶一箱14元 */
|
||||||
export type LogisticsPricingRule = {
|
export type LogisticsPricingRule = {
|
||||||
baseBottles: number;
|
baseBottles: number;
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ export interface DeliveryDto {
|
|||||||
deliveredAt?: string;
|
deliveredAt?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 大单拦截原因:≥10 箱不自动推小飞侠 */
|
||||||
|
export const FULFILLMENT_HOLD_REASON_LABELS: Record<string, string> = {
|
||||||
|
LARGE_ORDER_GE_10_BOXES: '大单≥10箱,待总部确认推单/自配送',
|
||||||
|
};
|
||||||
|
|
||||||
export type PartnerProxyOrderProductOption = {
|
export type PartnerProxyOrderProductOption = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -1150,6 +1150,9 @@ model Order {
|
|||||||
partnerAccountIdAtPay BigInt? @map("partner_account_id_at_pay") @db.UnsignedBigInt
|
partnerAccountIdAtPay BigInt? @map("partner_account_id_at_pay") @db.UnsignedBigInt
|
||||||
orderCommissionRateAtPay Decimal? @map("order_commission_rate_at_pay") @db.Decimal(5, 4)
|
orderCommissionRateAtPay Decimal? @map("order_commission_rate_at_pay") @db.Decimal(5, 4)
|
||||||
fulfillmentWarehouseId BigInt? @map("fulfillment_warehouse_id") @db.UnsignedBigInt
|
fulfillmentWarehouseId BigInt? @map("fulfillment_warehouse_id") @db.UnsignedBigInt
|
||||||
|
/// 大单等场景拦截自动推承运商,待总部确认后推单或自配送
|
||||||
|
fulfillmentHold Boolean @default(false) @map("fulfillment_hold")
|
||||||
|
fulfillmentHoldReason String? @map("fulfillment_hold_reason") @db.VarChar(64)
|
||||||
remark String? @db.VarChar(512)
|
remark String? @db.VarChar(512)
|
||||||
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
|
createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
|
||||||
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
|
updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { BadRequestException, Injectable, Logger, NotFoundException, Inject, forwardRef } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger, NotFoundException, Inject, forwardRef } from '@nestjs/common';
|
||||||
import type { CityWarehouse, FulfillmentProvider, Order } from '@prisma/client';
|
import type { CityWarehouse, FulfillmentProvider, Order } from '@prisma/client';
|
||||||
import { isXfxProviderCode } from '@dukang/shared-types';
|
import { isXfxProviderCode } from '@dukang/shared-types';
|
||||||
|
import {
|
||||||
|
BOTTLES_PER_BOX,
|
||||||
|
XFX_AUTO_DISPATCH_MAX_BOXES,
|
||||||
|
calcOrderBoxCount,
|
||||||
|
shouldHoldAutoCourierDispatch,
|
||||||
|
} from '@dukang/domain';
|
||||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||||
import { CourierService } from '../../integrations/courier/courier.service';
|
import { CourierService } from '../../integrations/courier/courier.service';
|
||||||
import { CourierPayMode } from '../../integrations/courier/courier.types';
|
import { CourierPayMode } from '../../integrations/courier/courier.types';
|
||||||
@@ -16,6 +22,8 @@ export type ManualShipInput = {
|
|||||||
|
|
||||||
export type HqLogisticsShipInput = ManualShipInput;
|
export type HqLogisticsShipInput = ManualShipInput;
|
||||||
|
|
||||||
|
export const FULFILLMENT_HOLD_LARGE_ORDER = 'LARGE_ORDER_GE_10_BOXES';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FulfillmentService {
|
export class FulfillmentService {
|
||||||
private readonly logger = new Logger(FulfillmentService.name);
|
private readonly logger = new Logger(FulfillmentService.name);
|
||||||
@@ -64,10 +72,48 @@ export class FulfillmentService {
|
|||||||
await this.ensureDeliveryRecord(orderId, 'MANUAL');
|
await this.ensureDeliveryRecord(orderId, 'MANUAL');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 大单拦截:≥10 箱不自动推小飞侠,待总部确认后推单或自配送
|
||||||
|
if (shouldHoldAutoCourierDispatch(order.quantity)) {
|
||||||
|
const boxes = calcOrderBoxCount(order.quantity);
|
||||||
|
this.logger.warn(
|
||||||
|
`大单拦截自动推单:${order.orderNo} quantity=${order.quantity} bottles≈${boxes}箱(阈值 ${XFX_AUTO_DISPATCH_MAX_BOXES}箱/${BOTTLES_PER_BOX}瓶)`,
|
||||||
|
);
|
||||||
|
await this.prisma.order.update({
|
||||||
|
where: { id: orderId },
|
||||||
|
data: {
|
||||||
|
fulfillmentHold: true,
|
||||||
|
fulfillmentHoldReason: FULFILLMENT_HOLD_LARGE_ORDER,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await this.ensureDeliveryRecord(orderId, 'MANUAL', provider.id);
|
||||||
|
await this.prisma.logThirdParty.create({
|
||||||
|
data: {
|
||||||
|
provider: 'XFX',
|
||||||
|
scene: 'ORDER_DISPATCH_HOLD',
|
||||||
|
refType: 'ORDER',
|
||||||
|
refId: order.id,
|
||||||
|
status: 'PENDING',
|
||||||
|
errorMessage: `大单拦截:${order.quantity}瓶(约${boxes}箱),需总部确认后推小飞侠或自配送`.slice(
|
||||||
|
0,
|
||||||
|
512,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.dispatchApiAuto(order, warehouse, provider);
|
await this.dispatchApiAuto(order, warehouse, provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async clearFulfillmentHold(orderId: bigint) {
|
||||||
|
await this.prisma.order.update({
|
||||||
|
where: { id: orderId },
|
||||||
|
data: { fulfillmentHold: false, fulfillmentHoldReason: null },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async dispatchApiAuto(order: Order, warehouse: CityWarehouse, provider: FulfillmentProvider) {
|
async dispatchApiAuto(order: Order, warehouse: CityWarehouse, provider: FulfillmentProvider) {
|
||||||
if (!isXfxProviderCode(provider.code)) {
|
if (!isXfxProviderCode(provider.code)) {
|
||||||
this.logger.warn(`承运商 ${provider.code} 自动推单尚未实现,订单 ${order.orderNo} 保持待发货`);
|
this.logger.warn(`承运商 ${provider.code} 自动推单尚未实现,订单 ${order.orderNo} 保持待发货`);
|
||||||
@@ -129,6 +175,10 @@ export class FulfillmentService {
|
|||||||
} else {
|
} else {
|
||||||
await tx.orderDelivery.create({ data: { orderId: order.id, ...data } });
|
await tx.orderDelivery.create({ data: { orderId: order.id, ...data } });
|
||||||
}
|
}
|
||||||
|
await tx.order.update({
|
||||||
|
where: { id: order.id },
|
||||||
|
data: { fulfillmentHold: false, fulfillmentHoldReason: null },
|
||||||
|
});
|
||||||
await tx.logThirdParty.create({
|
await tx.logThirdParty.create({
|
||||||
data: {
|
data: {
|
||||||
provider: 'XFX',
|
provider: 'XFX',
|
||||||
@@ -260,6 +310,10 @@ export class FulfillmentService {
|
|||||||
} else {
|
} else {
|
||||||
await tx.orderDelivery.create({ data: { orderId: order.id, ...data } });
|
await tx.orderDelivery.create({ data: { orderId: order.id, ...data } });
|
||||||
}
|
}
|
||||||
|
await tx.order.update({
|
||||||
|
where: { id: order.id },
|
||||||
|
data: { fulfillmentHold: false, fulfillmentHoldReason: null },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.tradeService.applyStatusTransition(order.id, order.status, 'SHIPPING', input.operator);
|
await this.tradeService.applyStatusTransition(order.id, order.status, 'SHIPPING', input.operator);
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ export class AdminOrdersService {
|
|||||||
if (query.userId) where.userId = BigInt(query.userId);
|
if (query.userId) where.userId = BigInt(query.userId);
|
||||||
if (query.cityId) where.cityId = BigInt(query.cityId);
|
if (query.cityId) where.cityId = BigInt(query.cityId);
|
||||||
if (query.receiverPhone) where.receiverPhone = { contains: query.receiverPhone };
|
if (query.receiverPhone) where.receiverPhone = { contains: query.receiverPhone };
|
||||||
|
if (query.fulfillmentHold === true || query.fulfillmentHold === 'true') {
|
||||||
|
where.fulfillmentHold = true;
|
||||||
|
}
|
||||||
if (query.createdFrom || query.createdTo) {
|
if (query.createdFrom || query.createdTo) {
|
||||||
where.createdAt = {};
|
where.createdAt = {};
|
||||||
if (query.createdFrom) where.createdAt.gte = new Date(query.createdFrom);
|
if (query.createdFrom) where.createdAt.gte = new Date(query.createdFrom);
|
||||||
@@ -256,6 +259,10 @@ export class AdminOrdersService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
await tx.order.update({
|
||||||
|
where: { id },
|
||||||
|
data: { fulfillmentHold: false, fulfillmentHoldReason: null },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.tradeService.applyStatusTransition(id, order.status, 'SHIPPING', 'HQ_SHIP');
|
await this.tradeService.applyStatusTransition(id, order.status, 'SHIPPING', 'HQ_SHIP');
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ export class AdminOrdersQueryDto extends PaginationQueryDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
receiverPhone?: string;
|
receiverPhone?: string;
|
||||||
|
|
||||||
|
/** 仅看大单拦截待总部确认:true / 1 */
|
||||||
|
@IsOptional()
|
||||||
|
fulfillmentHold?: string | boolean;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
createdFrom?: string;
|
createdFrom?: string;
|
||||||
|
|||||||
@@ -1172,7 +1172,12 @@ export class SettlementService {
|
|||||||
ok: true,
|
ok: true,
|
||||||
skipped: Boolean(bill.skipped),
|
skipped: Boolean(bill.skipped),
|
||||||
message: bill.reason,
|
message: bill.reason,
|
||||||
billId: bill.bill?.id?.toString?.() ?? bill.bill?.id,
|
billId:
|
||||||
|
bill.bill?.id != null
|
||||||
|
? typeof bill.bill.id === 'string'
|
||||||
|
? bill.bill.id
|
||||||
|
: String(bill.bill.id)
|
||||||
|
: undefined,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
results.push({
|
results.push({
|
||||||
@@ -1419,7 +1424,23 @@ export class SettlementService {
|
|||||||
orderBy: { code: 'asc' },
|
orderBy: { code: 'asc' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const rows = [];
|
const rows: Array<{
|
||||||
|
providerId: string;
|
||||||
|
providerCode: string;
|
||||||
|
providerName: string;
|
||||||
|
settlementMethod: string;
|
||||||
|
prepaidBalance: number;
|
||||||
|
bankAccountName: string | null;
|
||||||
|
bankName: string | null;
|
||||||
|
bankAccountNo: string | null;
|
||||||
|
pricingRules: ReturnType<FulfillmentProviderService['parsePricingRules']>;
|
||||||
|
orderCount: number;
|
||||||
|
bottleCount: number;
|
||||||
|
logisticsAmount: number;
|
||||||
|
billId: string | null;
|
||||||
|
billStatus: string | null;
|
||||||
|
billAmount: number | null;
|
||||||
|
}> = [];
|
||||||
for (const p of providers) {
|
for (const p of providers) {
|
||||||
const pricing =
|
const pricing =
|
||||||
this.fulfillmentProviderService.parsePricingRules(p.pricingRulesJson) ??
|
this.fulfillmentProviderService.parsePricingRules(p.pricingRulesJson) ??
|
||||||
|
|||||||
+2
-1
@@ -131,6 +131,7 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
- **同城**:仓配履约——有仓且绑 API 承运商则自动推单(首期小飞侠);有仓选自管则管仓方手工填单;**无仓**则总部传统快递填单
|
- **同城**:仓配履约——有仓且绑 API 承运商则自动推单(首期小飞侠);有仓选自管则管仓方手工填单;**无仓**则总部传统快递填单
|
||||||
|
- **大单拦截**:同城订单 **≥10 箱(箱规 6 瓶,即 ≥60 瓶)** 不自动推小飞侠;订单打标「大单待确认」,由总部确认后推小飞侠或自配送(填快递单)
|
||||||
- **跨城**:总部传统快递到付填单;订单佣金归总部
|
- **跨城**:总部传统快递到付填单;订单佣金归总部
|
||||||
- **现场提货**:支付后直接已完成;有现场推广码则订单佣金归码所属合伙人,无码归总部
|
- **现场提货**:支付后直接已完成;有现场推广码则订单佣金归码所属合伙人,无码归总部
|
||||||
|
|
||||||
@@ -212,7 +213,7 @@
|
|||||||
- 一城多仓;每仓最多关联 1 名管仓合伙人
|
- 一城多仓;每仓最多关联 1 名管仓合伙人
|
||||||
- **仓配管理**(总部):注册第三方履约接口(小飞侠、京东、顺丰等);启用后仓库方可选择;**承运商需配置银行账户、结算方式(充值/挂账月结)、计价标准**
|
- **仓配管理**(总部):注册第三方履约接口(小飞侠、京东、顺丰等);启用后仓库方可选择;**承运商需配置银行账户、结算方式(充值/挂账月结)、计价标准**
|
||||||
- **仓库设置**:履约方式 = API 自动推单(选已注册承运商)或 **自管**(手工填运单号 + 查询链接模板)
|
- **仓库设置**:履约方式 = API 自动推单(选已注册承运商)或 **自管**(手工填运单号 + 查询链接模板)
|
||||||
- 同城有仓订单支付后自动按仓配置推单;自管仓由管仓合伙人/总部代填单
|
- 同城有仓订单支付后自动按仓配置推单;自管仓由管仓合伙人/总部代填单;**≥10 箱大单除外**(见 §3.2)
|
||||||
- 同城无仓 / 跨城:总部传统快递填单
|
- 同城无仓 / 跨城:总部传统快递填单
|
||||||
- 佣金与仓无关(订单佣金仍按 §3.3.1);仓用于履约与工单协同
|
- 佣金与仓无关(订单佣金仍按 §3.3.1);仓用于履约与工单协同
|
||||||
- 未关联合伙人的仓 → 总部直派
|
- 未关联合伙人的仓 → 总部直派
|
||||||
|
|||||||
+2
-1
@@ -439,7 +439,8 @@
|
|||||||
|------|------|
|
|------|------|
|
||||||
| 仓库 | 名称、地址、联系人;管仓方 = 总部直派 或 关联合伙人(每仓最多 1 名) |
|
| 仓库 | 名称、地址、联系人;管仓方 = 总部直派 或 关联合伙人(每仓最多 1 名) |
|
||||||
| 履约方式 | **API 自动推单**(选已注册承运商,首期小飞侠)或 **自管**(手工填运单号 + 查询链接模板) |
|
| 履约方式 | **API 自动推单**(选已注册承运商,首期小飞侠)或 **自管**(手工填运单号 + 查询链接模板) |
|
||||||
| 仓配管理 | 注册第三方履约接口;启用后仓库才可选 |
|
| 仓配管理 | 注册第三方履约接口;启用后仓库才可选;配置银行账户/结算/计价 |
|
||||||
|
| 大单拦截 | 同城 ≥10 箱不自动推小飞侠,订单标「大单待确认」,总部确认推单或自配送 |
|
||||||
|
|
||||||
规则摘要:
|
规则摘要:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user