feat(partner): complete proxy order with dual SMS and pickup
CI / verify (pull_request) Has been cancelled
CI / verify (pull_request) Has been cancelled
Add partner/customer SMS confirm, address auto-receive or on-site pickup, proxy placer fields, PARTNER_PROXY user source, and C-end badge. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -256,6 +256,7 @@ enum UserSourceType {
|
||||
SHARE_LINK
|
||||
FRIEND_REFERRAL
|
||||
OFFLINE_EVENT
|
||||
PARTNER_PROXY
|
||||
OTHER
|
||||
}
|
||||
|
||||
@@ -1153,6 +1154,10 @@ model Order {
|
||||
payExpireAt DateTime? @map("pay_expire_at") @db.DateTime(3)
|
||||
partnerAccountIdAtPay BigInt? @map("partner_account_id_at_pay") @db.UnsignedBigInt
|
||||
orderCommissionRateAtPay Decimal? @map("order_commission_rate_at_pay") @db.Decimal(5, 4)
|
||||
/// 代下单操作人(与佣金归属 partnerAccountIdAtPay 分离)
|
||||
proxyPartnerAccountId BigInt? @map("proxy_partner_account_id") @db.UnsignedBigInt
|
||||
proxyPartnerName String? @map("proxy_partner_name") @db.VarChar(64)
|
||||
proxyPartnerPhone String? @map("proxy_partner_phone") @db.VarChar(20)
|
||||
fulfillmentWarehouseId BigInt? @map("fulfillment_warehouse_id") @db.UnsignedBigInt
|
||||
/// 大单等场景拦截自动推承运商,待总部确认后推单或自配送
|
||||
fulfillmentHold Boolean @default(false) @map("fulfillment_hold")
|
||||
@@ -1181,6 +1186,7 @@ model Order {
|
||||
@@index([ipCity])
|
||||
@@index([gpsCity])
|
||||
@@index([fulfillmentWarehouseId])
|
||||
@@index([proxyPartnerAccountId])
|
||||
@@map("user_order")
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,19 @@ export function mapOrderItemCompat(order: OrderLike) {
|
||||
};
|
||||
}
|
||||
|
||||
export function mapOrderCompat<T extends OrderLike>(order: T) {
|
||||
export function mapOrderCompat<T extends OrderLike & {
|
||||
orderType?: string | null;
|
||||
proxyPartnerName?: string | null;
|
||||
proxyPartnerPhone?: string | null;
|
||||
proxyPartnerAccountId?: bigint | number | string | null;
|
||||
}>(order: T) {
|
||||
const payStatus = order.payStatus ?? 'UNPAID';
|
||||
const isProxyOrder = order.orderType === 'PROXY';
|
||||
return {
|
||||
...order,
|
||||
isProxyOrder,
|
||||
proxyPartnerName: order.proxyPartnerName ?? null,
|
||||
proxyPartnerPhone: order.proxyPartnerPhone ?? null,
|
||||
items: [mapOrderItemCompat(order)],
|
||||
payment: {
|
||||
status: payStatus === 'PAID' ? 'SUCCESS' : payStatus,
|
||||
|
||||
@@ -66,7 +66,7 @@ export class SmsAliyunProvider implements ISmsProvider {
|
||||
return this.config.aliyunSmsRedeemConfirmTemplateCode;
|
||||
}
|
||||
if (
|
||||
scene === 'PARTNER_PROXY_ORDER' &&
|
||||
(scene === 'PARTNER_PROXY_ORDER' || scene === 'PARTNER_PROXY_CUSTOMER') &&
|
||||
this.config.aliyunSmsProxyOrderTemplateCode
|
||||
) {
|
||||
return this.config.aliyunSmsProxyOrderTemplateCode;
|
||||
|
||||
@@ -97,6 +97,7 @@ export class AuthService {
|
||||
case SmsScene.REDEEM_PHONE_CONFIRM:
|
||||
return ClientApp.SHOP_H5;
|
||||
case SmsScene.PARTNER_PROXY_ORDER:
|
||||
case SmsScene.PARTNER_PROXY_CUSTOMER:
|
||||
return ClientApp.PARTNER_H5;
|
||||
default:
|
||||
return ClientApp.USER_H5;
|
||||
@@ -155,13 +156,20 @@ export class AuthService {
|
||||
});
|
||||
return user ? { refType: 'USER', refId: user.id } : undefined;
|
||||
}
|
||||
case SmsScene.PARTNER_PROXY_ORDER: {
|
||||
case SmsScene.PARTNER_PROXY_CUSTOMER: {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: { phone, mergedIntoUserId: null, status: 1 },
|
||||
select: { id: true },
|
||||
});
|
||||
return user ? { refType: 'USER', refId: user.id } : undefined;
|
||||
}
|
||||
case SmsScene.PARTNER_PROXY_ORDER: {
|
||||
const partner = await this.prisma.partnerAccount.findUnique({
|
||||
where: { phone },
|
||||
select: { id: true },
|
||||
});
|
||||
return partner ? { refType: 'PARTNER', refId: partner.id } : undefined;
|
||||
}
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
@@ -334,7 +342,11 @@ export class AuthService {
|
||||
if (!user.phoneVerifiedAt) throw new BadRequestException('用户手机号未验证,无法核销');
|
||||
return;
|
||||
}
|
||||
if (scene === SmsScene.PARTNER_PROXY_CUSTOMER) {
|
||||
return;
|
||||
}
|
||||
if (scene === SmsScene.PARTNER_PROXY_ORDER) {
|
||||
await this.assertPartnerAccountByPhone(phone);
|
||||
return;
|
||||
}
|
||||
if (scene === SmsScene.PARTNER_STORE_OPEN) {
|
||||
@@ -348,7 +360,14 @@ export class AuthService {
|
||||
}
|
||||
|
||||
/** 合伙人代下单:按手机号查找或创建已验证用户 */
|
||||
async findOrCreateUserByPhone(phone: string) {
|
||||
async findOrCreateUserByPhone(
|
||||
phone: string,
|
||||
source?: {
|
||||
sourceType?: 'PARTNER_PROXY';
|
||||
sourceRefId?: bigint;
|
||||
sourceLabel?: string;
|
||||
},
|
||||
) {
|
||||
const normalizedPhone = this.assertMobilePhone(phone);
|
||||
let user = await this.prisma.user.findUnique({
|
||||
where: { phone: normalizedPhone },
|
||||
@@ -362,6 +381,9 @@ export class AuthService {
|
||||
phoneVerifiedAt: new Date(),
|
||||
userNo: generateUserNo(),
|
||||
nickname: `用户${normalizedPhone.slice(-4)}`,
|
||||
sourceType: source?.sourceType ?? 'ORGANIC',
|
||||
sourceRefId: source?.sourceRefId,
|
||||
sourceLabel: source?.sourceLabel,
|
||||
cityPreference: {
|
||||
create: {
|
||||
selectedCityCode: '410100',
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsInt, IsNotEmpty, IsOptional, IsString, Matches, MaxLength, Min } from 'class-validator';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsIn,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Matches,
|
||||
MaxLength,
|
||||
Min,
|
||||
ValidateIf,
|
||||
} from 'class-validator';
|
||||
|
||||
export class PartnerProxyOrderPreviewDto {
|
||||
@IsString()
|
||||
@@ -11,6 +22,14 @@ export class PartnerProxyOrderPreviewDto {
|
||||
@Min(1)
|
||||
quantity: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn(['ADDRESS', 'ON_SITE_PICKUP'])
|
||||
deliveryMode?: 'ADDRESS' | 'ON_SITE_PICKUP';
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
storeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
receiverCity?: string;
|
||||
@@ -20,7 +39,7 @@ export class PartnerProxyOrderPreviewDto {
|
||||
receiverDistrict?: string;
|
||||
}
|
||||
|
||||
export class PartnerProxyOrderSendSmsDto {
|
||||
export class PartnerProxyOrderSendCustomerSmsDto {
|
||||
@IsString()
|
||||
@Matches(/^1\d{10}$/, { message: '请输入有效手机号' })
|
||||
phone: string;
|
||||
@@ -33,28 +52,48 @@ export class PartnerProxyOrderCreateDto {
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
smsCode: string;
|
||||
customerSmsCode: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
partnerSmsCode: string;
|
||||
|
||||
@IsIn(['ADDRESS', 'ON_SITE_PICKUP'])
|
||||
deliveryMode: 'ADDRESS' | 'ON_SITE_PICKUP';
|
||||
|
||||
@ValidateIf((o: PartnerProxyOrderCreateDto) => o.deliveryMode === 'ADDRESS')
|
||||
@IsBoolean()
|
||||
autoReceive?: boolean;
|
||||
|
||||
@ValidateIf((o: PartnerProxyOrderCreateDto) => o.deliveryMode === 'ON_SITE_PICKUP')
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
storeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(32)
|
||||
receiverName?: string;
|
||||
|
||||
@ValidateIf((o: PartnerProxyOrderCreateDto) => o.deliveryMode === 'ADDRESS')
|
||||
@IsString()
|
||||
@MaxLength(32)
|
||||
province: string;
|
||||
province?: string;
|
||||
|
||||
@ValidateIf((o: PartnerProxyOrderCreateDto) => o.deliveryMode === 'ADDRESS')
|
||||
@IsString()
|
||||
@MaxLength(32)
|
||||
city: string;
|
||||
city?: string;
|
||||
|
||||
@ValidateIf((o: PartnerProxyOrderCreateDto) => o.deliveryMode === 'ADDRESS')
|
||||
@IsString()
|
||||
@MaxLength(32)
|
||||
district: string;
|
||||
district?: string;
|
||||
|
||||
@ValidateIf((o: PartnerProxyOrderCreateDto) => o.deliveryMode === 'ADDRESS')
|
||||
@IsString()
|
||||
@MaxLength(256)
|
||||
addressDetail: string;
|
||||
addressDetail?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
|
||||
@@ -9,7 +9,7 @@ import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
import {
|
||||
PartnerProxyOrderCreateDto,
|
||||
PartnerProxyOrderPreviewDto,
|
||||
PartnerProxyOrderSendSmsDto,
|
||||
PartnerProxyOrderSendCustomerSmsDto,
|
||||
} from './dto/partner-proxy-order.dto';
|
||||
import { ManualShipOrderDto } from '../ops/dto/admin-mutate.dto';
|
||||
import { CreateAfterSaleTicketDto, CreateInvoiceDto } from './dto/after-sale.dto';
|
||||
@@ -204,8 +204,8 @@ export class PartnerProxyOrderController {
|
||||
constructor(private readonly tradeService: TradeService) {}
|
||||
|
||||
@Get('options')
|
||||
options() {
|
||||
return this.tradeService.getPartnerProxyOrderOptions();
|
||||
options(@CurrentUser() user: AuthUser) {
|
||||
return this.tradeService.getPartnerProxyOrderOptions(user.actorId);
|
||||
}
|
||||
|
||||
@Post('preview')
|
||||
@@ -213,9 +213,20 @@ export class PartnerProxyOrderController {
|
||||
return this.tradeService.previewPartnerProxyOrder(dto);
|
||||
}
|
||||
|
||||
/** @deprecated 兼容:转发客户短信 */
|
||||
@Post('send-sms')
|
||||
sendSms(@Body() dto: PartnerProxyOrderSendSmsDto) {
|
||||
return this.tradeService.sendPartnerProxyOrderSms(dto.phone);
|
||||
sendSms(@Body() dto: PartnerProxyOrderSendCustomerSmsDto) {
|
||||
return this.tradeService.sendPartnerProxyCustomerSms(dto.phone);
|
||||
}
|
||||
|
||||
@Post('send-customer-sms')
|
||||
sendCustomerSms(@Body() dto: PartnerProxyOrderSendCustomerSmsDto) {
|
||||
return this.tradeService.sendPartnerProxyCustomerSms(dto.phone);
|
||||
}
|
||||
|
||||
@Post('send-partner-sms')
|
||||
sendPartnerSms(@CurrentUser() user: AuthUser) {
|
||||
return this.tradeService.sendPartnerProxyPartnerSms(user.actorId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
|
||||
@@ -1023,10 +1023,25 @@ export class TradeService {
|
||||
});
|
||||
}
|
||||
|
||||
async getPartnerProxyOrderOptions() {
|
||||
const [products, promoCodes] = await Promise.all([
|
||||
async getPartnerProxyOrderOptions(partnerAccountId: bigint) {
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const [products, promoCodes, stores] = await Promise.all([
|
||||
this.catalogService.listProducts(),
|
||||
this.promoCodeService.listActiveOptions(),
|
||||
this.prisma.store.findMany({
|
||||
where: { partnerAccountId: primary.id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
address: true,
|
||||
phone: true,
|
||||
province: true,
|
||||
cityName: true,
|
||||
district: true,
|
||||
status: true,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
return serializeBigInt({
|
||||
products: products.map((p) => ({
|
||||
@@ -1035,14 +1050,27 @@ export class TradeService {
|
||||
spec: p.spec,
|
||||
price: Number(p.price),
|
||||
benefitAmount: p.benefitAmount != null ? Number(p.benefitAmount) : null,
|
||||
coverUrl: (p as { mainImageUrl?: string | null }).mainImageUrl ?? null,
|
||||
allowOnSitePickup: !!(p as { allowOnSitePickup?: boolean }).allowOnSitePickup,
|
||||
})),
|
||||
promoCodes,
|
||||
stores: stores.map((s) => ({
|
||||
id: s.id,
|
||||
name: s.name,
|
||||
address: s.address,
|
||||
phone: s.phone,
|
||||
province: s.province,
|
||||
cityName: s.cityName,
|
||||
district: s.district,
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
async previewPartnerProxyOrder(body: {
|
||||
productId: string;
|
||||
quantity: number;
|
||||
deliveryMode?: 'ADDRESS' | 'ON_SITE_PICKUP';
|
||||
storeId?: string;
|
||||
receiverCity?: string;
|
||||
receiverDistrict?: string;
|
||||
}) {
|
||||
@@ -1053,14 +1081,23 @@ export class TradeService {
|
||||
const city = await this.prisma.commonCity.findFirst({ where: { status: 'ACTIVE' } });
|
||||
if (!city) throw new BadRequestException('暂无开城城市');
|
||||
|
||||
let deliveryType: 'LOCAL' | 'CROSS_CITY' = 'LOCAL';
|
||||
const receiverCity = body.receiverCity?.trim();
|
||||
if (receiverCity && receiverCity !== city.name && receiverCity !== '郑州市') {
|
||||
deliveryType = 'CROSS_CITY';
|
||||
const deliveryMode = body.deliveryMode ?? 'ADDRESS';
|
||||
let deliveryType: 'LOCAL' | 'CROSS_CITY' | 'ON_SITE_PICKUP' = 'LOCAL';
|
||||
|
||||
if (deliveryMode === 'ON_SITE_PICKUP') {
|
||||
if (!product.allowOnSitePickup) {
|
||||
throw new BadRequestException('该商品不支持现场提货');
|
||||
}
|
||||
deliveryType = 'ON_SITE_PICKUP';
|
||||
} else {
|
||||
const receiverCity = body.receiverCity?.trim();
|
||||
if (receiverCity && receiverCity !== city.name && receiverCity !== '郑州市') {
|
||||
deliveryType = 'CROSS_CITY';
|
||||
}
|
||||
}
|
||||
|
||||
const check = validateMinPurchase(
|
||||
deliveryType,
|
||||
deliveryType === 'CROSS_CITY' ? 'CROSS_CITY' : 'LOCAL',
|
||||
body.quantity,
|
||||
city.localMinQty,
|
||||
city.crossMinQty,
|
||||
@@ -1083,9 +1120,9 @@ export class TradeService {
|
||||
};
|
||||
}
|
||||
|
||||
async sendPartnerProxyOrderSms(phone: string) {
|
||||
async sendPartnerProxyCustomerSms(phone: string) {
|
||||
const normalizedPhone = phone.trim();
|
||||
await this.authService.sendSms(normalizedPhone, SmsScene.PARTNER_PROXY_ORDER, {
|
||||
await this.authService.sendSms(normalizedPhone, SmsScene.PARTNER_PROXY_CUSTOMER, {
|
||||
clientApp: ClientApp.PARTNER_H5,
|
||||
});
|
||||
const masked =
|
||||
@@ -1095,16 +1132,41 @@ export class TradeService {
|
||||
return { ok: true, maskedPhone: masked };
|
||||
}
|
||||
|
||||
/** @deprecated 兼容旧前端:转发为客户短信 */
|
||||
async sendPartnerProxyOrderSms(phone: string) {
|
||||
return this.sendPartnerProxyCustomerSms(phone);
|
||||
}
|
||||
|
||||
async sendPartnerProxyPartnerSms(partnerAccountId: bigint) {
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const partnerPhone = primary.phone?.trim();
|
||||
if (!partnerPhone || !/^1\d{10}$/.test(partnerPhone)) {
|
||||
throw new BadRequestException('合伙人手机号无效,无法发送确认验证码');
|
||||
}
|
||||
await this.authService.sendSms(partnerPhone, SmsScene.PARTNER_PROXY_ORDER, {
|
||||
clientApp: ClientApp.PARTNER_H5,
|
||||
});
|
||||
const masked =
|
||||
partnerPhone.length >= 7
|
||||
? `${partnerPhone.slice(0, 3)}****${partnerPhone.slice(-4)}`
|
||||
: partnerPhone;
|
||||
return { ok: true, maskedPhone: masked };
|
||||
}
|
||||
|
||||
async createPartnerProxyOrder(
|
||||
partnerAccountId: bigint,
|
||||
body: {
|
||||
phone: string;
|
||||
smsCode: string;
|
||||
customerSmsCode: string;
|
||||
partnerSmsCode: string;
|
||||
deliveryMode: 'ADDRESS' | 'ON_SITE_PICKUP';
|
||||
autoReceive?: boolean;
|
||||
storeId?: string;
|
||||
receiverName?: string;
|
||||
province: string;
|
||||
city: string;
|
||||
district: string;
|
||||
addressDetail: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
district?: string;
|
||||
addressDetail?: string;
|
||||
productId: string;
|
||||
quantity: number;
|
||||
promoCodeId?: string;
|
||||
@@ -1112,16 +1174,43 @@ export class TradeService {
|
||||
req: Request,
|
||||
) {
|
||||
const normalizedPhone = body.phone.trim();
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const partnerPhone = primary.phone?.trim();
|
||||
if (!partnerPhone || !/^1\d{10}$/.test(partnerPhone)) {
|
||||
throw new BadRequestException('合伙人手机号无效');
|
||||
}
|
||||
|
||||
await this.authService.verifySmsCode(
|
||||
normalizedPhone,
|
||||
body.smsCode.trim(),
|
||||
body.customerSmsCode.trim(),
|
||||
SmsScene.PARTNER_PROXY_CUSTOMER,
|
||||
);
|
||||
await this.authService.verifySmsCode(
|
||||
partnerPhone,
|
||||
body.partnerSmsCode.trim(),
|
||||
SmsScene.PARTNER_PROXY_ORDER,
|
||||
);
|
||||
|
||||
const user = await this.authService.findOrCreateUserByPhone(normalizedPhone);
|
||||
if (body.deliveryMode === 'ADDRESS' && body.autoReceive !== true) {
|
||||
throw new BadRequestException('配送到址须勾选同意自动收货');
|
||||
}
|
||||
|
||||
const maskedPartnerPhone =
|
||||
partnerPhone.length >= 7
|
||||
? `${partnerPhone.slice(0, 3)}****${partnerPhone.slice(-4)}`
|
||||
: partnerPhone;
|
||||
|
||||
const user = await this.authService.findOrCreateUserByPhone(normalizedPhone, {
|
||||
sourceType: 'PARTNER_PROXY',
|
||||
sourceRefId: primary.id,
|
||||
sourceLabel: `代下单·${maskedPartnerPhone}`,
|
||||
});
|
||||
|
||||
const preview = await this.previewPartnerProxyOrder({
|
||||
productId: body.productId,
|
||||
quantity: body.quantity,
|
||||
deliveryMode: body.deliveryMode,
|
||||
storeId: body.storeId,
|
||||
receiverCity: body.city,
|
||||
receiverDistrict: body.district,
|
||||
});
|
||||
@@ -1130,8 +1219,37 @@ export class TradeService {
|
||||
where: { id: BigInt(body.productId) },
|
||||
});
|
||||
const city = await this.prisma.commonCity.findFirstOrThrow({ where: { status: 'ACTIVE' } });
|
||||
const primary = await this.partnerCityService.resolvePrimaryAccount(partnerAccountId);
|
||||
const paySnapshot = await this.partnerCityService.resolveForOrder(city.id, body.district);
|
||||
|
||||
let receiverName = body.receiverName?.trim() || `用户${normalizedPhone.slice(-4)}`;
|
||||
let receiverProvince = body.province?.trim() || '';
|
||||
let receiverCity = body.city?.trim() || '';
|
||||
let receiverDistrict = body.district?.trim() || '';
|
||||
let receiverAddress = '';
|
||||
let commissionDistrict = receiverDistrict;
|
||||
|
||||
if (body.deliveryMode === 'ON_SITE_PICKUP') {
|
||||
if (!body.storeId?.trim()) throw new BadRequestException('请选择提货门店');
|
||||
const store = await this.prisma.store.findFirst({
|
||||
where: { id: BigInt(body.storeId.trim()), partnerAccountId: primary.id },
|
||||
});
|
||||
if (!store) throw new BadRequestException('门店不存在或不属于当前合伙人');
|
||||
receiverName = store.name;
|
||||
receiverProvince = store.province;
|
||||
receiverCity = store.cityName;
|
||||
receiverDistrict = store.district;
|
||||
receiverAddress = `现场提货·${store.name}·${store.province}${store.cityName}${store.district}${store.address}`;
|
||||
commissionDistrict = store.district;
|
||||
} else {
|
||||
if (!receiverProvince || !receiverCity || !receiverDistrict) {
|
||||
throw new BadRequestException('请选择省市区');
|
||||
}
|
||||
if (!body.addressDetail?.trim()) {
|
||||
throw new BadRequestException('请填写详细地址');
|
||||
}
|
||||
receiverAddress = `${receiverProvince}${receiverCity}${receiverDistrict}${body.addressDetail.trim()}`;
|
||||
}
|
||||
|
||||
const paySnapshot = await this.partnerCityService.resolveForOrder(city.id, commissionDistrict);
|
||||
|
||||
let promoCodeId: bigint | undefined;
|
||||
if (body.promoCodeId?.trim()) {
|
||||
@@ -1139,11 +1257,8 @@ export class TradeService {
|
||||
await this.promoCodeService.attributeUserToPromo(user.id, promoCodeId);
|
||||
}
|
||||
|
||||
const receiverName = body.receiverName?.trim() || `用户${normalizedPhone.slice(-4)}`;
|
||||
const receiverAddress = `${body.province}${body.city}${body.district}${body.addressDetail}`;
|
||||
const orderNo = generateOrderNo();
|
||||
const now = new Date();
|
||||
|
||||
const location = buildOrderClientLocationSnapshot(
|
||||
req,
|
||||
this.ipGeoService.resolve(extractClientIp(req)),
|
||||
@@ -1178,9 +1293,9 @@ export class TradeService {
|
||||
receiverName,
|
||||
receiverPhone: normalizedPhone,
|
||||
receiverAddress,
|
||||
receiverProvince: body.province,
|
||||
receiverCity: body.city,
|
||||
receiverDistrict: body.district,
|
||||
receiverProvince,
|
||||
receiverCity,
|
||||
receiverDistrict,
|
||||
clientIp: location.clientIp,
|
||||
ipProvince: location.ipProvince,
|
||||
ipCity: location.ipCity,
|
||||
@@ -1190,7 +1305,10 @@ export class TradeService {
|
||||
completedAt: now,
|
||||
partnerAccountIdAtPay: paySnapshot?.partnerAccountId ?? primary.id,
|
||||
orderCommissionRateAtPay: paySnapshot?.orderCommissionRate ?? null,
|
||||
remark: `合伙人代下单 partnerAccountId=${primary.id}`,
|
||||
proxyPartnerAccountId: primary.id,
|
||||
proxyPartnerName: primary.name,
|
||||
proxyPartnerPhone: partnerPhone,
|
||||
remark: `合伙人代下单 partnerAccountId=${primary.id} deliveryMode=${body.deliveryMode} customer=${normalizedPhone}`,
|
||||
},
|
||||
include: { product: true, imageResource: true },
|
||||
});
|
||||
@@ -1211,7 +1329,7 @@ export class TradeService {
|
||||
fromStatus: 'PENDING_PAY',
|
||||
toStatus: 'COMPLETED',
|
||||
operator: 'PARTNER_PROXY',
|
||||
remark: '合伙人线下代下单',
|
||||
remark: `合伙人线下代下单 mode=${body.deliveryMode} customer=${normalizedPhone} partner=${primary.id}`,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1237,6 +1355,7 @@ export class TradeService {
|
||||
userId: user.id.toString(),
|
||||
productId: body.productId,
|
||||
quantity: body.quantity,
|
||||
deliveryMode: body.deliveryMode,
|
||||
promoCodeId: promoCodeId?.toString() ?? null,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user