feat(assoc): v4.0.1 合伙人关联码、分佣账单与 H5 用户管理

订单佣金只认关联用户;合伙人备注写入独立表;H5 增加用户管理与首页统计。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 14:35:39 +08:00
parent 3b669f7e38
commit 9c8d5f2cad
125 changed files with 6355 additions and 1436 deletions
@@ -1,6 +1,6 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma, CityPartnerScopeType, CityPartnerStatus } from '@prisma/client';
import { resolveOrderCityPartner, validatePartnerCityBinding } from '@dukang/domain';
import { Prisma, CityPartnerScopeType } from '@prisma/client';
import { validatePartnerCityBinding } from '@dukang/domain';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
@@ -45,30 +45,50 @@ export class PartnerCityService {
return this.assertPartnerAccountBoundToCity(partnerAccountId, cityId);
}
async resolveForOrder(cityId: bigint, receiverDistrict?: string | null) {
const bindings = await this.prisma.partnerAccount.findMany({
where: { ...PRIMARY_WHERE, cityId, bindingStatus: 'ACTIVE' },
});
const ref = resolveOrderCityPartner(
bindings.map((b) => ({
id: b.id.toString(),
partnerAccountId: b.id.toString(),
scopeType: b.scopeType as CityPartnerScopeType,
districtCodes: this.parseDistrictCodes(b.districtCodes),
orderCommissionRate: Number(b.orderCommissionRate ?? 0),
redeemCommissionRate: Number(b.redeemCommissionRate ?? 0.03),
bindingStatus: b.bindingStatus as CityPartnerStatus,
})),
receiverDistrict,
);
if (!ref) return null;
async snapshotForPartner(partnerAccountId: bigint) {
const primary = await this.resolvePrimaryAccount(partnerAccountId);
if (primary.bindingStatus === 'PAUSED' || primary.status !== 'ACTIVE') {
return { partnerAccountId: null as bigint | null, orderCommissionRate: null as number | null };
}
return {
partnerAccountId: BigInt(ref.partnerAccountId),
orderCommissionRate: ref.orderCommissionRate,
redeemCommissionRate: ref.redeemCommissionRate,
partnerAccountId: primary.id,
orderCommissionRate: Number(primary.orderCommissionRate ?? 0),
};
}
async snapshotForUser(userId: bigint) {
const user = await this.prisma.user.findUnique({
where: { id: userId },
select: { assocPartnerAccountId: true },
});
if (!user?.assocPartnerAccountId) {
return { partnerAccountId: null as bigint | null, orderCommissionRate: null as number | null };
}
return this.snapshotForPartner(user.assocPartnerAccountId);
}
/** 代下单显式选择:本单快照归该合伙人;用户未关联则 first-lock */
async applyProxyAssoc(userId: bigint, partnerAccountId: bigint) {
const snapshot = await this.snapshotForPartner(partnerAccountId);
const user = await this.prisma.user.findUnique({
where: { id: userId },
select: { assocPartnerAccountId: true, sourceType: true },
});
if (user && !user.assocPartnerAccountId && snapshot.partnerAccountId) {
await this.prisma.user.update({
where: { id: userId },
data: {
assocPartnerAccountId: snapshot.partnerAccountId,
assocBoundAt: new Date(),
...(user.sourceType === 'ORGANIC'
? { sourceType: 'PARTNER_ASSOC', sourceRefId: snapshot.partnerAccountId }
: {}),
},
});
}
return snapshot;
}
async validatePrimaryBinding(
cityId: bigint,
input: {