城市合伙人端的修改(后台)

This commit is contained in:
2026-07-12 10:19:39 +08:00
parent d0f0fa09af
commit 7f031cc4c2
74 changed files with 5430 additions and 975 deletions
@@ -11,6 +11,7 @@ import { serializeBigInt } from '../../common/decorators/current-user.decorator'
import { mapStoreCompat } from '../../common/compat/v31-compat';
import { parseBigIntParam } from '../../common/parse-bigint';
import { AnalyticsService } from '../analytics/analytics.service';
import { PartnerCityService } from '../city-scope/partner-city.service';
@Injectable()
export class StoreService {
@@ -19,6 +20,7 @@ export class StoreService {
constructor(
private readonly prisma: PrismaService,
private readonly analyticsService: AnalyticsService,
private readonly partnerCityService: PartnerCityService,
) {}
async listOpenStores(cityCode?: string) {
@@ -48,9 +50,15 @@ export class StoreService {
return serializeBigInt(mapStoreCompat({ ...store, media }));
}
private async resolvePartnerScope(actorAccountId: bigint) {
const account = await this.getPartnerAccount(actorAccountId);
const primaryId = await this.getPartnerPrimaryId(actorAccountId);
return { account, primaryId };
}
async partnerListStores(partnerAccountId: bigint) {
const account = await this.getPartnerAccount(partnerAccountId);
const where: { partnerId: bigint; id?: { in: bigint[] } } = { partnerId: account.partnerId };
const { account, primaryId } = await this.resolvePartnerScope(partnerAccountId);
const where: { partnerAccountId: bigint; id?: { in: bigint[] } } = { partnerAccountId: primaryId };
if (this.isSubAccount(account)) {
const storeIds = await this.getStoreIdsCreatedByAccount(partnerAccountId);
if (storeIds.length === 0) return [];
@@ -65,9 +73,9 @@ export class StoreService {
}
async partnerGetStore(partnerAccountId: bigint, storeId: bigint) {
const account = await this.getPartnerAccount(partnerAccountId);
const { account, primaryId } = await this.resolvePartnerScope(partnerAccountId);
const store = await this.prisma.store.findFirst({
where: { id: storeId, partnerId: account.partnerId },
where: { id: storeId, partnerAccountId: primaryId },
include: { category: true, coverResource: true },
});
if (!store) throw new NotFoundException('门店不存在');
@@ -88,10 +96,11 @@ export class StoreService {
}
async partnerListCities(partnerAccountId: bigint) {
const account = await this.getPartnerAccount(partnerAccountId);
const { primaryId } = await this.resolvePartnerScope(partnerAccountId);
const cityWhere = await this.partnerCityService.buildPartnerCityWhere(primaryId);
const cities = await this.prisma.commonCity.findMany({
where: { partnerId: account.partnerId },
select: { id: true, name: true, code: true, province: true, partnerId: true },
where: cityWhere,
select: { id: true, name: true, code: true, province: true },
orderBy: { createdAt: 'desc' },
});
return serializeBigInt(cities);
@@ -115,11 +124,11 @@ export class StoreService {
}
async createStore(partnerAccountId: bigint, body: Record<string, unknown>) {
const account = await this.getPartnerAccount(partnerAccountId);
const { primaryId } = await this.resolvePartnerScope(partnerAccountId);
const normalizedPhone = String(body.phone).trim();
await this.assertStorePhoneAvailable(normalizedPhone);
const city = await this.resolvePartnerCity(account.partnerId, body.cityId);
const city = await this.resolvePartnerCity(partnerAccountId, body.cityId);
const coverUrl = body.coverUrl ? String(body.coverUrl).trim() : '';
const envPhotoUrls = Array.isArray(body.envPhotoUrls)
? body.envPhotoUrls.map((u) => String(u).trim()).filter(Boolean)
@@ -133,7 +142,7 @@ export class StoreService {
const store = await this.prisma.store.create({
data: {
cityId: city.id,
partnerId: account.partnerId,
partnerAccountId: primaryId,
categoryId: body.categoryId ? parseBigIntParam(body.categoryId, '分类ID') : null,
name: String(body.name),
phone: normalizedPhone,
@@ -220,7 +229,7 @@ export class StoreService {
});
this.analyticsService.trackPartnerOneSafe(partnerAccountId, 'PARTNER_H5', {
partnerId: account.partnerId,
partnerAccountId: primaryId,
eventName: 'partner_store_create',
refType: 'STORE',
refId: store.id,
@@ -240,10 +249,10 @@ export class StoreService {
storeId: bigint,
status: 'OPEN' | 'PAUSED' | 'CLOSED',
) {
const account = await this.getPartnerAccount(partnerAccountId);
const { account, primaryId } = await this.resolvePartnerScope(partnerAccountId);
this.assertPrimaryAccount(account);
const store = await this.prisma.store.findFirst({
where: { id: storeId, partnerId: account.partnerId },
where: { id: storeId, partnerAccountId: primaryId },
});
if (!store) throw new NotFoundException('门店不存在');
if (store.status === 'CLOSED') {
@@ -259,7 +268,7 @@ export class StoreService {
include: { coverResource: true },
});
this.analyticsService.trackPartnerOneSafe(partnerAccountId, 'PARTNER_H5', {
partnerId: account.partnerId,
partnerAccountId: primaryId,
eventName: 'partner_store_status_change',
refType: 'STORE',
refId: storeId,
@@ -276,10 +285,10 @@ export class StoreService {
storeId: bigint,
body: Record<string, unknown>,
) {
const account = await this.getPartnerAccount(partnerAccountId);
const { account, primaryId } = await this.resolvePartnerScope(partnerAccountId);
this.assertPrimaryAccount(account);
const store = await this.prisma.store.findFirst({
where: { id: storeId, partnerId: account.partnerId },
where: { id: storeId, partnerAccountId: primaryId },
});
if (!store) throw new NotFoundException('门店不存在');
if (store.status === 'CLOSED') {
@@ -343,20 +352,20 @@ export class StoreService {
}
async partnerDashboard(partnerAccountId: bigint) {
const account = await this.getPartnerAccount(partnerAccountId);
const { account, primaryId } = await this.resolvePartnerScope(partnerAccountId);
this.assertPrimaryAccount(account);
const partnerStoreIds = await this.prisma.store.findMany({
where: { partnerId: account.partnerId },
where: { partnerAccountId: primaryId },
select: { id: true },
});
const storeIds = partnerStoreIds.map((s) => s.id);
const [storeCount, orderCount, recentStores, pendingAuditCount] = await Promise.all([
this.prisma.store.count({ where: { partnerId: account.partnerId } }),
this.prisma.store.count({ where: { partnerAccountId: primaryId } }),
this.prisma.order.count({
where: { city: { partnerId: account.partnerId } },
where: await this.partnerCityService.buildPartnerOrderWhere(primaryId),
}),
this.prisma.store.findMany({
where: { partnerId: account.partnerId },
where: { partnerAccountId: primaryId },
select: { id: true, name: true, status: true, createdAt: true },
orderBy: { createdAt: 'desc' },
take: 10,
@@ -375,18 +384,17 @@ export class StoreService {
return {
storeCount,
orderCount,
companyName: account.partner.companyName,
companyName: account.companyName ?? '',
recentStores: serializeBigInt(recentStores),
pendingAuditCount,
};
}
async partnerLeaderboard(partnerAccountId: bigint, period: PartnerLeaderboardPeriod = 'total') {
const account = await this.getPartnerAccount(partnerAccountId);
this.assertPrimaryAccount(account);
const { primaryId } = await this.resolvePartnerScope(partnerAccountId);
const accounts = await this.prisma.partnerAccount.findMany({
where: { partnerId: account.partnerId },
where: { OR: [{ id: primaryId }, { parentAccountId: primaryId }] },
orderBy: { id: 'asc' },
});
@@ -457,10 +465,9 @@ export class StoreService {
return { period, list, self };
}
async partnerWeeklyReport(partnerAccountId: bigint, startDate?: string) {
const account = await this.getPartnerAccount(partnerAccountId);
async partnerWeeklyReport(actorAccountId: bigint, startDate?: string) {
const { account, primaryId } = await this.resolvePartnerScope(actorAccountId);
this.assertPrimaryAccount(account);
const partnerId = account.partnerId;
const currentWeekStart = this.startOfWeekMonday(new Date());
const periodStart =
@@ -472,16 +479,16 @@ export class StoreService {
const prevPeriodEnd = periodStart;
const newStoreTarget =
account.partner.weeklyStoreTarget ??
account.weeklyStoreTarget ??
Number(process.env.PARTNER_WEEKLY_STORE_TARGET ?? 20);
const orderWhere = {
city: { partnerId },
...(await this.partnerCityService.buildPartnerOrderWhere(primaryId)),
payStatus: 'PAID' as const,
paidAt: { gte: periodStart, lt: periodEnd },
};
const prevOrderWhere = {
city: { partnerId },
...(await this.partnerCityService.buildPartnerOrderWhere(primaryId)),
payStatus: 'PAID' as const,
paidAt: { gte: prevPeriodStart, lt: prevPeriodEnd },
};
@@ -498,16 +505,16 @@ export class StoreService {
] = await Promise.all([
this.prisma.order.aggregate({ where: orderWhere, _sum: { payAmount: true } }),
this.prisma.order.count({ where: orderWhere }),
this.prisma.store.count({ where: { partnerId } }),
this.prisma.store.count({ where: { partnerAccountId: primaryId } }),
this.prisma.store.count({
where: { partnerId, createdAt: { gte: periodStart, lt: periodEnd } },
where: { partnerAccountId: primaryId, createdAt: { gte: periodStart, lt: periodEnd } },
}),
this.prisma.order.aggregate({ where: prevOrderWhere, _sum: { payAmount: true } }),
this.prisma.redeemRecord.groupBy({
by: ['storeId'],
where: {
createdAt: { gte: periodStart, lt: periodEnd },
store: { partnerId },
store: { partnerAccountId: primaryId },
},
_sum: { amount: true },
orderBy: { _sum: { amount: 'desc' } },
@@ -516,7 +523,7 @@ export class StoreService {
this.prisma.redeemRecord.findMany({
where: {
createdAt: { gte: periodStart, lt: periodEnd },
store: { partnerId },
store: { partnerAccountId: primaryId },
},
select: { storeId: true },
distinct: ['storeId'],
@@ -677,22 +684,29 @@ export class StoreService {
return ['周一', '周二', '周三', '周四', '周五', '周六', '周日'][index] ?? '';
}
private async getPartnerPrimaryId(actorAccountId: bigint) {
const primary = await this.partnerCityService.resolvePrimaryAccount(actorAccountId);
return primary.id;
}
private async getPartnerAccount(partnerAccountId: bigint) {
return this.prisma.partnerAccount.findUniqueOrThrow({
where: { id: partnerAccountId },
include: { partner: true },
});
}
private async resolvePartnerCity(partnerId: bigint, cityId: unknown) {
private async resolvePartnerCity(actorAccountId: bigint, cityId: unknown) {
const primaryId = await this.getPartnerPrimaryId(actorAccountId);
if (cityId) {
const city = await this.prisma.commonCity.findFirst({
where: { id: parseBigIntParam(cityId, '城市ID'), partnerId },
});
const id = parseBigIntParam(cityId, '城市ID');
await this.partnerCityService.assertPartnerAccountBoundToCity(primaryId, id);
const city = await this.prisma.commonCity.findUnique({ where: { id } });
if (!city) throw new BadRequestException('所选地区未匹配到开城城市');
return city;
}
const city = await this.prisma.commonCity.findFirst({ where: { partnerId } });
const cityIds = await this.partnerCityService.listCityIdsForPartnerAccount(primaryId);
if (!cityIds.length) throw new BadRequestException('合伙人未绑定开城');
const city = await this.prisma.commonCity.findFirst({ where: { id: { in: cityIds } } });
if (!city) throw new BadRequestException('合伙人未绑定开城');
return city;
}