feat(mini-user): v3.5.10 同城配送提示可配置并支持换行
承运商 HTML 按收货市下发;textarea 回车在 C 端转成换行。含门店去掉分享按钮与小程序企微客服。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,6 +37,8 @@ export class ClientConfigController {
|
||||
brandLogoMarkUrl: brand.brandLogoMarkUrl,
|
||||
qualificationDisclosureUrl: brand.qualificationDisclosureUrl,
|
||||
customerServicePhone: brand.customerServicePhone,
|
||||
customerServiceWecomUrl: brand.customerServiceWecomUrl || null,
|
||||
wecomCorpId: brand.wecomCorpId || null,
|
||||
partnerOnboardCsQrUrl: (env.PARTNER_ONBOARD_CS_QR_URL ?? '').trim() || null,
|
||||
partnerOnboardCsHint:
|
||||
(env.PARTNER_ONBOARD_CS_HINT ?? '').trim() ||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import {
|
||||
DEFAULT_XFX_LOGISTICS_PRICING,
|
||||
isXfxProviderCode,
|
||||
sanitizeDeliveryHintHtml,
|
||||
type LogisticsPricingRuleDto,
|
||||
type XiaofeixiaProviderConfig,
|
||||
type XiaofeixiaProviderConfigPublic,
|
||||
@@ -30,6 +31,7 @@ export type CreateFulfillmentProviderInput = {
|
||||
bankAccountNo?: string | null;
|
||||
settlementMethod?: LogisticsSettlementMethod | string;
|
||||
pricingRules?: LogisticsPricingRuleDto | null;
|
||||
deliveryHintHtml?: string | null;
|
||||
};
|
||||
|
||||
export type UpdateFulfillmentProviderInput = Partial<CreateFulfillmentProviderInput>;
|
||||
@@ -149,6 +151,7 @@ export class FulfillmentProviderService {
|
||||
bankAccountNo: this.normOptional(input.bankAccountNo),
|
||||
settlementMethod,
|
||||
pricingRulesJson,
|
||||
deliveryHintHtml: sanitizeDeliveryHintHtml(input.deliveryHintHtml),
|
||||
},
|
||||
});
|
||||
return this.toDto(row);
|
||||
@@ -195,11 +198,43 @@ export class FulfillmentProviderService {
|
||||
? { settlementMethod: this.parseSettlementMethod(input.settlementMethod)! }
|
||||
: {}),
|
||||
...(pricingRulesJson !== undefined ? { pricingRulesJson } : {}),
|
||||
...(input.deliveryHintHtml !== undefined
|
||||
? { deliveryHintHtml: sanitizeDeliveryHintHtml(input.deliveryHintHtml) }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
return this.toDto(row);
|
||||
}
|
||||
|
||||
async remove(id: bigint) {
|
||||
const current = await this.prisma.fulfillmentProvider.findUnique({ where: { id } });
|
||||
if (!current) throw new NotFoundException('仓配承运商不存在');
|
||||
|
||||
const [billCount, ledgerCount] = await Promise.all([
|
||||
this.prisma.logisticsBill.count({ where: { fulfillmentProviderId: id } }),
|
||||
this.prisma.logisticsPrepaidLedger.count({ where: { fulfillmentProviderId: id } }),
|
||||
]);
|
||||
if (billCount > 0) {
|
||||
throw new BadRequestException(`该承运商已有 ${billCount} 笔物流对账单,不能删除`);
|
||||
}
|
||||
if (ledgerCount > 0) {
|
||||
throw new BadRequestException('该承运商已有充值/扣款流水,不能删除');
|
||||
}
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
await tx.cityWarehouse.updateMany({
|
||||
where: { fulfillmentProviderId: id },
|
||||
data: { fulfillmentProviderId: null, fulfillmentMode: 'MANUAL' },
|
||||
});
|
||||
await tx.orderDelivery.updateMany({
|
||||
where: { fulfillmentProviderId: id },
|
||||
data: { fulfillmentProviderId: null },
|
||||
});
|
||||
await tx.fulfillmentProvider.delete({ where: { id } });
|
||||
});
|
||||
return { ok: true, id: id.toString() };
|
||||
}
|
||||
|
||||
/** 充值(结算模块可复用) */
|
||||
async rechargePrepaid(providerId: bigint, amount: number, remark?: string) {
|
||||
if (!(amount > 0)) throw new BadRequestException('充值金额须大于 0');
|
||||
@@ -413,6 +448,7 @@ export class FulfillmentProviderService {
|
||||
settlementMethod?: string;
|
||||
pricingRulesJson?: string | null;
|
||||
prepaidBalance?: Prisma.Decimal | number;
|
||||
deliveryHintHtml?: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}) {
|
||||
@@ -434,8 +470,61 @@ export class FulfillmentProviderService {
|
||||
settlementMethod: row.settlementMethod ?? 'PREPAID',
|
||||
pricingRules: this.parsePricingRules(row.pricingRulesJson ?? null),
|
||||
prepaidBalance: Number(row.prepaidBalance ?? 0),
|
||||
deliveryHintHtml: sanitizeDeliveryHintHtml(row.deliveryHintHtml ?? null),
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
async listLocalDeliveries(filter?: { cityCode?: string; cityName?: string }) {
|
||||
const cityCode = filter?.cityCode?.trim() || '';
|
||||
const cityName = filter?.cityName?.trim() || '';
|
||||
const cities = await this.prisma.commonCity.findMany({
|
||||
where: {
|
||||
status: 'ACTIVE',
|
||||
...(cityCode ? { code: cityCode } : {}),
|
||||
...(cityName && !cityCode
|
||||
? { name: { in: this.cityNameAliases(cityName) } }
|
||||
: {}),
|
||||
},
|
||||
include: {
|
||||
warehouses: {
|
||||
where: { status: 'ACTIVE' },
|
||||
include: { fulfillmentProvider: true },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
},
|
||||
},
|
||||
orderBy: { name: 'asc' },
|
||||
});
|
||||
|
||||
return cities.map((city) => {
|
||||
const preferred =
|
||||
city.warehouses.find((w) => w.fulfillmentMode === 'API_AUTO' && w.fulfillmentProviderId) ??
|
||||
city.warehouses.find((w) => w.fulfillmentProviderId) ??
|
||||
null;
|
||||
const provider = preferred?.fulfillmentProvider ?? null;
|
||||
return {
|
||||
city: { id: city.id.toString(), code: city.code, name: city.name },
|
||||
warehouse: preferred
|
||||
? {
|
||||
id: preferred.id.toString(),
|
||||
name: preferred.name,
|
||||
fulfillmentMode: preferred.fulfillmentMode,
|
||||
}
|
||||
: null,
|
||||
provider: provider
|
||||
? { id: provider.id.toString(), code: provider.code, name: provider.name }
|
||||
: null,
|
||||
hintHtml: sanitizeDeliveryHintHtml(provider?.deliveryHintHtml ?? null),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private cityNameAliases(name: string): string[] {
|
||||
const raw = name.trim();
|
||||
if (!raw) return [];
|
||||
const noSuffix = raw.replace(/市$/, '');
|
||||
const withSuffix = raw.endsWith('市') ? raw : `${raw}市`;
|
||||
return Array.from(new Set([raw, noSuffix, withSuffix]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ import { IntegrationsModule } from '../../integrations/integrations.module';
|
||||
import { TradeModule } from '../trade/trade.module';
|
||||
import { FulfillmentProviderService } from './fulfillment-provider.service';
|
||||
import { FulfillmentService } from './fulfillment.service';
|
||||
import { LocalDeliveriesController } from './local-deliveries.controller';
|
||||
|
||||
@Module({
|
||||
imports: [IntegrationsModule, forwardRef(() => TradeModule)],
|
||||
controllers: [LocalDeliveriesController],
|
||||
providers: [FulfillmentProviderService, FulfillmentService],
|
||||
exports: [FulfillmentProviderService, FulfillmentService],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { FulfillmentProviderService } from './fulfillment-provider.service';
|
||||
|
||||
@Controller('catalog')
|
||||
export class LocalDeliveriesController {
|
||||
constructor(private readonly providers: FulfillmentProviderService) {}
|
||||
|
||||
@Get('local-deliveries')
|
||||
list(@Query('cityCode') cityCode?: string, @Query('cityName') cityName?: string) {
|
||||
return this.providers.listLocalDeliveries({ cityCode, cityName });
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Body, Controller, Get, Param, Post, Put, UseGuards } from '@nestjs/common';
|
||||
import { Body, Controller, Delete, Get, Param, Post, Put, UseGuards } from '@nestjs/common';
|
||||
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
|
||||
import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
|
||||
import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
|
||||
@@ -47,6 +47,7 @@ export class AdminFulfillmentProvidersController {
|
||||
bankAccountNo: dto.bankAccountNo,
|
||||
settlementMethod: dto.settlementMethod,
|
||||
pricingRules: dto.pricingRules,
|
||||
deliveryHintHtml: dto.deliveryHintHtml,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,6 +77,7 @@ export class AdminFulfillmentProvidersController {
|
||||
bankAccountNo: dto.bankAccountNo,
|
||||
settlementMethod: dto.settlementMethod,
|
||||
pricingRules: dto.pricingRules,
|
||||
deliveryHintHtml: dto.deliveryHintHtml,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,4 +91,14 @@ export class AdminFulfillmentProvidersController {
|
||||
recharge(@Param('id') id: string, @Body() dto: RechargeFulfillmentProviderDto) {
|
||||
return this.service.rechargePrepaid(BigInt(id), Number(dto.amount), dto.remark);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HqOperation({
|
||||
action: HqOperationAction.LOGISTICS_PROVIDER_DELETE,
|
||||
refType: 'FULFILLMENT_PROVIDER',
|
||||
refIdParam: 'id',
|
||||
})
|
||||
remove(@Param('id') id: string) {
|
||||
return this.service.remove(BigInt(id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -797,6 +797,11 @@ export class CreateFulfillmentProviderDto {
|
||||
boxBottles?: number;
|
||||
boxFee?: number;
|
||||
} | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(2000)
|
||||
deliveryHintHtml?: string | null;
|
||||
}
|
||||
|
||||
export class UpdateFulfillmentProviderDto {
|
||||
@@ -859,6 +864,11 @@ export class UpdateFulfillmentProviderDto {
|
||||
boxBottles?: number;
|
||||
boxFee?: number;
|
||||
} | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(2000)
|
||||
deliveryHintHtml?: string | null;
|
||||
}
|
||||
|
||||
export class RechargeFulfillmentProviderDto {
|
||||
|
||||
Reference in New Issue
Block a user