feat(assoc): v4.0.1 合伙人关联码、分佣账单与 H5 用户管理
订单佣金只认关联用户;合伙人备注写入独立表;H5 增加用户管理与首页统计。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { resolveMaxPartnerCommissionRate, validatePartnerCommissionRates } from
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
||||
import { PartnerCityService } from '../city-scope/partner-city.service';
|
||||
import { PartnerAssocService } from '../store/partner-assoc.service';
|
||||
import type { AdminPartnerAccountsQueryDto, AdminPartnersQueryDto } from './dto/admin-query.dto';
|
||||
import type {
|
||||
CreatePartnerAccountDto,
|
||||
@@ -26,11 +27,24 @@ function assertPartnerCommissionRates(
|
||||
if (!check.ok) throw new BadRequestException(check.message);
|
||||
}
|
||||
|
||||
function toCommissionDecimal(rate: number): Prisma.Decimal {
|
||||
return new Prisma.Decimal(Number(rate).toFixed(4));
|
||||
}
|
||||
|
||||
/** 选填字符串:undefined 不改;null / 空白写成 null */
|
||||
function trimOptionalText(value: string | null | undefined): string | null | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
if (value == null) return null;
|
||||
const t = String(value).trim();
|
||||
return t.length ? t : null;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminPartnersService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly partnerCityService: PartnerCityService,
|
||||
private readonly partnerAssocService: PartnerAssocService,
|
||||
) {}
|
||||
|
||||
async listPartners(query: AdminPartnersQueryDto) {
|
||||
@@ -64,7 +78,7 @@ export class AdminPartnersService {
|
||||
},
|
||||
orderBy: { createdAt: 'asc' },
|
||||
},
|
||||
_count: { select: { stores: true, children: true } },
|
||||
_count: { select: { stores: true, children: true, assocUsers: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.partnerAccount.count({ where }),
|
||||
@@ -88,6 +102,7 @@ export class AdminPartnersService {
|
||||
managedWarehouseId: p.managedWarehouseId?.toString() ?? null,
|
||||
managedWarehouseName: p.managedWarehouse?.name ?? null,
|
||||
storeCount: p._count.stores,
|
||||
assocUserCount: p._count.assocUsers,
|
||||
accountCount: p._count.children + 1,
|
||||
children: p.children.map((c) => ({
|
||||
id: c.id.toString(),
|
||||
@@ -125,7 +140,7 @@ export class AdminPartnersService {
|
||||
orderBy: { createdAt: 'asc' },
|
||||
},
|
||||
stores: { select: { id: true, name: true, status: true }, take: 10, orderBy: { createdAt: 'desc' } },
|
||||
_count: { select: { stores: true, children: true } },
|
||||
_count: { select: { stores: true, children: true, assocUsers: true } },
|
||||
},
|
||||
});
|
||||
if (!account) throw new NotFoundException('开城合伙人不存在');
|
||||
@@ -140,6 +155,8 @@ export class AdminPartnersService {
|
||||
bankAccountNo: account.bankAccountNo,
|
||||
bankBranch: account.bankBranch,
|
||||
managedWarehouseName: account.managedWarehouse?.name ?? null,
|
||||
storeCount: account._count.stores,
|
||||
assocUserCount: account._count.assocUsers,
|
||||
accountCount: account._count.children + 1,
|
||||
maxPartnerCommissionRate:
|
||||
account.city?.maxPartnerCommissionRate != null
|
||||
@@ -189,8 +206,8 @@ export class AdminPartnersService {
|
||||
scopeType: dto.scopeType as CityPartnerScopeType,
|
||||
districtCodes:
|
||||
dto.scopeType === 'DISTRICT' ? (dto.districtCodes ?? []) : Prisma.JsonNull,
|
||||
orderCommissionRate: orderCommissionRate,
|
||||
redeemCommissionRate: redeemCommissionRate,
|
||||
orderCommissionRate: toCommissionDecimal(orderCommissionRate),
|
||||
redeemCommissionRate: toCommissionDecimal(redeemCommissionRate),
|
||||
bindingStatus: (dto.bindingStatus ?? 'ACTIVE') as CityPartnerStatus,
|
||||
companyName: dto.companyName?.trim() || null,
|
||||
address: dto.address?.trim() || null,
|
||||
@@ -204,7 +221,13 @@ export class AdminPartnersService {
|
||||
include: { city: { select: { id: true, code: true, name: true } } },
|
||||
});
|
||||
|
||||
return serializeBigInt(this.partnerCityService.toDto(account));
|
||||
const dtoOut = serializeBigInt(this.partnerCityService.toDto(account));
|
||||
try {
|
||||
await this.partnerAssocService.ensureQrcode(account.id);
|
||||
} catch {
|
||||
/* 补码失败不挡创建 */
|
||||
}
|
||||
return dtoOut;
|
||||
}
|
||||
|
||||
async updatePartner(id: bigint, dto: UpdatePartnerDto) {
|
||||
@@ -221,17 +244,26 @@ export class AdminPartnersService {
|
||||
? dto.districtCodes ?? this.partnerCityService.parseDistrictCodes(existing.districtCodes)
|
||||
: null;
|
||||
|
||||
await this.partnerCityService.validatePrimaryBinding(
|
||||
cityId,
|
||||
{
|
||||
partnerAccountId: id.toString(),
|
||||
scopeType,
|
||||
districtCodes: districtCodes ?? undefined,
|
||||
},
|
||||
id,
|
||||
);
|
||||
const existingDistricts = this.partnerCityService.parseDistrictCodes(existing.districtCodes);
|
||||
const nextDistrictsSorted = [...(districtCodes ?? [])].sort();
|
||||
const existingDistrictsSorted = [...(existingDistricts ?? [])].sort();
|
||||
const scopeActuallyChanged =
|
||||
(dto.scopeType !== undefined && dto.scopeType !== existing.scopeType) ||
|
||||
(dto.districtCodes !== undefined &&
|
||||
JSON.stringify(nextDistrictsSorted) !== JSON.stringify(existingDistrictsSorted));
|
||||
if (scopeActuallyChanged) {
|
||||
await this.partnerCityService.validatePrimaryBinding(
|
||||
cityId,
|
||||
{
|
||||
partnerAccountId: id.toString(),
|
||||
scopeType,
|
||||
districtCodes: districtCodes ?? undefined,
|
||||
},
|
||||
id,
|
||||
);
|
||||
}
|
||||
|
||||
if (dto.phone !== undefined) {
|
||||
if (dto.phone !== undefined && dto.phone != null) {
|
||||
const phone = dto.phone.trim();
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||||
throw new BadRequestException('请输入正确的登录手机号');
|
||||
@@ -254,18 +286,22 @@ export class AdminPartnersService {
|
||||
assertPartnerCommissionRates(city, orderCommissionRate, redeemCommissionRate);
|
||||
}
|
||||
|
||||
const phoneChanged =
|
||||
dto.phone !== undefined && dto.phone.trim() !== existing.phone;
|
||||
const nextPhone = dto.phone != null ? dto.phone.trim() : undefined;
|
||||
const phoneChanged = nextPhone !== undefined && nextPhone !== existing.phone;
|
||||
const nextName = dto.name != null ? dto.name.trim() : undefined;
|
||||
const nextCompanyName = trimOptionalText(dto.companyName);
|
||||
const nextAddress = trimOptionalText(dto.address);
|
||||
const nextContactPhone = trimOptionalText(dto.contactPhone);
|
||||
|
||||
const account = await this.prisma.partnerAccount.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...(dto.name !== undefined ? { name: dto.name.trim() } : {}),
|
||||
...(dto.phone !== undefined ? { phone: dto.phone.trim() } : {}),
|
||||
...(nextName !== undefined ? { name: nextName } : {}),
|
||||
...(nextPhone !== undefined ? { phone: nextPhone } : {}),
|
||||
...(phoneChanged ? { wxOpenId: null, wxUnionId: null } : {}),
|
||||
...(dto.companyName !== undefined ? { companyName: dto.companyName.trim() } : {}),
|
||||
...(dto.address !== undefined ? { address: dto.address.trim() } : {}),
|
||||
...(dto.contactPhone !== undefined ? { contactPhone: dto.contactPhone.trim() } : {}),
|
||||
...(nextCompanyName !== undefined ? { companyName: nextCompanyName } : {}),
|
||||
...(nextAddress !== undefined ? { address: nextAddress } : {}),
|
||||
...(nextContactPhone !== undefined ? { contactPhone: nextContactPhone } : {}),
|
||||
...(dto.scopeType !== undefined ? { scopeType: dto.scopeType as CityPartnerScopeType } : {}),
|
||||
...(dto.scopeType !== undefined || dto.districtCodes !== undefined
|
||||
? {
|
||||
@@ -275,8 +311,12 @@ export class AdminPartnersService {
|
||||
: Prisma.JsonNull,
|
||||
}
|
||||
: {}),
|
||||
...(dto.orderCommissionRate !== undefined ? { orderCommissionRate: dto.orderCommissionRate } : {}),
|
||||
...(dto.redeemCommissionRate !== undefined ? { redeemCommissionRate: dto.redeemCommissionRate } : {}),
|
||||
...(dto.orderCommissionRate !== undefined
|
||||
? { orderCommissionRate: toCommissionDecimal(dto.orderCommissionRate) }
|
||||
: {}),
|
||||
...(dto.redeemCommissionRate !== undefined
|
||||
? { redeemCommissionRate: toCommissionDecimal(dto.redeemCommissionRate) }
|
||||
: {}),
|
||||
...(dto.bindingStatus !== undefined ? { bindingStatus: dto.bindingStatus as CityPartnerStatus } : {}),
|
||||
...(dto.contractNo !== undefined ? { contractNo: dto.contractNo } : {}),
|
||||
...(dto.bankAccountName !== undefined ? { bankAccountName: dto.bankAccountName } : {}),
|
||||
|
||||
Reference in New Issue
Block a user