v3.5.8和v3.5.9版本更新
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-08-25 09:20:32 +08:00
parent 7304c7a8e1
commit 5935024ea8
101 changed files with 7640 additions and 5364 deletions
@@ -13,6 +13,10 @@ import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { ShopStoreGuard } from '../../common/guards/shop-store.guard';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { StoreInfoChangeService } from './store-info-change.service';
import {
HqPermissionGuard,
RequireHqPermissions,
} from '../../common/guards/hq-permission.guard';
@Controller('partner/stores')
@UseGuards(JwtAuthGuard)
@@ -56,17 +60,19 @@ export class ShopStoreInfoChangeController {
}
@Controller('admin/store-info-change-requests')
@UseGuards(HqAuthGuard)
@UseGuards(HqAuthGuard, HqPermissionGuard)
@RequireHqPermissions('store_audits')
export class AdminStoreInfoChangeController {
constructor(private readonly svc: StoreInfoChangeService) {}
@Get('summary')
summary() {
return this.svc.adminSummary();
summary(@CurrentUser() user: AuthUser) {
return this.svc.adminSummary(user.actorId);
}
@Get()
list(
@CurrentUser() user: AuthUser,
@Query('status') status?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
@@ -75,12 +81,13 @@ export class AdminStoreInfoChangeController {
status: (status as 'PENDING' | 'APPROVED' | 'REJECTED') || undefined,
page: page ? Number(page) : undefined,
pageSize: pageSize ? Number(pageSize) : undefined,
actorId: user.actorId,
});
}
@Get(':id')
detail(@Param('id') id: string) {
return this.svc.adminDetail(BigInt(id));
detail(@CurrentUser() user: AuthUser, @Param('id') id: string) {
return this.svc.adminDetail(BigInt(id), user.actorId);
}
@Put(':id/audit')
@@ -21,6 +21,7 @@ import { serializeBigInt } from '../../common/decorators/current-user.decorator'
import { StoreService } from './store.service';
import { PartnerCityService } from '../city-scope/partner-city.service';
import { WecomMessagePushService } from '../../integrations/wecom/wecom-message-push.service';
import { HqPermissionsResolver, hqStoreCityWhere } from '../../common/guards/hq-permission.guard';
type ChangeableField = (typeof STORE_INFO_CHANGEABLE_FIELDS)[number];
@@ -145,6 +146,7 @@ export class StoreInfoChangeService {
private readonly storeService: StoreService,
private readonly partnerCityService: PartnerCityService,
private readonly wecomPush: WecomMessagePushService,
private readonly hqPermissions: HqPermissionsResolver,
) {}
private async loadLiveMediaFields(storeId: bigint, coverResourceId: bigint | null) {
@@ -373,10 +375,16 @@ export class StoreInfoChangeService {
status?: StoreInfoChangeStatus;
page?: number;
pageSize?: number;
actorId: bigint;
}): Promise<{ items: StoreInfoChangeRequestDto[]; total: number; page: number; pageSize: number }> {
const page = Math.max(1, opts.page || 1);
const pageSize = Math.min(Math.max(opts.pageSize || 20, 1), 100);
const where = opts.status ? { status: opts.status } : {};
const scope = await this.hqPermissions.resolveCityScope(opts.actorId);
const storeWhere = hqStoreCityWhere(scope);
const where = {
...(opts.status ? { status: opts.status } : {}),
...(storeWhere ? { store: storeWhere } : {}),
};
const [rows, total] = await Promise.all([
this.prisma.storeInfoChangeRequest.findMany({
where,
@@ -395,20 +403,24 @@ export class StoreInfoChangeService {
return { items, total, page, pageSize };
}
async adminSummary(): Promise<{ pendingCount: number; packagePendingCount: number }> {
async adminSummary(actorId: bigint): Promise<{ pendingCount: number; packagePendingCount: number }> {
const scope = await this.hqPermissions.resolveCityScope(actorId);
const storeWhere = hqStoreCityWhere(scope);
const cityFilter = storeWhere ? { store: storeWhere } : {};
const [infoPending, packagePending] = await Promise.all([
this.prisma.storeInfoChangeRequest.count({ where: { status: 'PENDING' } }),
this.prisma.storePackageChangeRequest.count({ where: { status: 'PENDING' } }),
this.prisma.storeInfoChangeRequest.count({ where: { status: 'PENDING', ...cityFilter } }),
this.prisma.storePackageChangeRequest.count({ where: { status: 'PENDING', ...cityFilter } }),
]);
return { pendingCount: infoPending, packagePendingCount: packagePending };
}
async adminDetail(id: bigint): Promise<StoreInfoChangeRequestDto> {
async adminDetail(id: bigint, actorId: bigint): Promise<StoreInfoChangeRequestDto> {
const row = await this.prisma.storeInfoChangeRequest.findUnique({
where: { id },
include: { store: { select: { name: true } } },
include: { store: { select: { name: true, cityId: true } } },
});
if (!row) throw new NotFoundException('变更请求不存在');
await this.hqPermissions.assertStoreCityInScope(actorId, row.store.cityId);
const dto = this.toDto(
row as unknown as Record<string, unknown>,
(row as { store?: { name?: string } }).store?.name,
@@ -430,8 +442,12 @@ export class StoreInfoChangeService {
rejectReason?: string;
reviewerId: bigint;
}): Promise<StoreInfoChangeRequestDto> {
const row = await this.prisma.storeInfoChangeRequest.findUnique({ where: { id: input.id } });
const row = await this.prisma.storeInfoChangeRequest.findUnique({
where: { id: input.id },
include: { store: { select: { cityId: true } } },
});
if (!row) throw new NotFoundException('变更请求不存在');
await this.hqPermissions.assertStoreCityInScope(input.reviewerId, row.store.cityId);
if (row.status !== 'PENDING') {
throw new BadRequestException('该变更请求已处理');
}
@@ -4,6 +4,10 @@ import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { ShopStoreGuard } from '../../common/guards/shop-store.guard';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { StorePackageService } from './store-package.service';
import {
HqPermissionGuard,
RequireHqPermissions,
} from '../../common/guards/hq-permission.guard';
@Controller('partner/stores')
@UseGuards(JwtAuthGuard)
@@ -49,48 +53,58 @@ export class ShopStorePackageController {
}
@Controller('admin/stores')
@UseGuards(HqAuthGuard)
@UseGuards(HqAuthGuard, HqPermissionGuard)
@RequireHqPermissions('stores')
export class AdminStorePackageController {
constructor(private readonly packages: StorePackageService) {}
@Get(':storeId/packages')
getPackages(@Param('storeId') storeId: string) {
return this.packages.adminGetPackages(BigInt(storeId));
getPackages(@CurrentUser() user: AuthUser, @Param('storeId') storeId: string) {
return this.packages.adminGetPackages(BigInt(storeId), user.actorId);
}
@Put(':storeId/packages')
savePackages(@Param('storeId') storeId: string, @Body() body: { packages: unknown }) {
savePackages(
@CurrentUser() user: AuthUser,
@Param('storeId') storeId: string,
@Body() body: { packages: unknown },
) {
const normalized = this.packages.normalizePackages(body.packages);
return this.packages.adminDirectSave(BigInt(storeId), normalized);
return this.packages.adminDirectSave(BigInt(storeId), normalized, user.actorId);
}
}
@Controller('admin/store-package-audits')
@UseGuards(HqAuthGuard)
@UseGuards(HqAuthGuard, HqPermissionGuard)
@RequireHqPermissions('store_audits')
export class AdminStorePackageAuditController {
constructor(private readonly packages: StorePackageService) {}
@Get('summary')
summary() {
return this.packages.adminAuditSummary();
summary(@CurrentUser() user: AuthUser) {
return this.packages.adminAuditSummary(user.actorId);
}
@Get()
list(
@CurrentUser() user: AuthUser,
@Query('status') status?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.packages.adminListAudits({
status: status || undefined,
page: page ? Number(page) : undefined,
pageSize: pageSize ? Number(pageSize) : undefined,
});
return this.packages.adminListAudits(
{
status: status || undefined,
page: page ? Number(page) : undefined,
pageSize: pageSize ? Number(pageSize) : undefined,
},
user.actorId,
);
}
@Get(':requestId')
detail(@Param('requestId') requestId: string) {
return this.packages.adminGetAuditDetail(BigInt(requestId));
detail(@CurrentUser() user: AuthUser, @Param('requestId') requestId: string) {
return this.packages.adminGetAuditDetail(BigInt(requestId), user.actorId);
}
@Put(':requestId/audit')
@@ -15,6 +15,7 @@ import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import { StoreService } from './store.service';
import { WecomMessagePushService } from '../../integrations/wecom/wecom-message-push.service';
import { HqPermissionsResolver, hqStoreCityWhere } from '../../common/guards/hq-permission.guard';
type PackageInput = Record<string, unknown>;
@@ -26,6 +27,7 @@ export class StorePackageService {
private readonly prisma: PrismaService,
private readonly storeService: StoreService,
private readonly wecomPush: WecomMessagePushService,
private readonly hqPermissions: HqPermissionsResolver,
) {}
normalizePackages(raw: unknown): StorePackageItemDto[] {
@@ -237,14 +239,16 @@ export class StorePackageService {
);
}
async adminGetPackages(storeId: bigint) {
async adminGetPackages(storeId: bigint, actorId: bigint) {
await this.hqPermissions.assertStoreIdInScope(actorId, storeId);
const store = await this.prisma.store.findUnique({ where: { id: storeId } });
if (!store) throw new NotFoundException('门店不存在');
const live = await this.listLivePackages(storeId);
return serializeBigInt({ live });
}
async adminDirectSave(storeId: bigint, packages: StorePackageItemDto[]) {
async adminDirectSave(storeId: bigint, packages: StorePackageItemDto[], actorId: bigint) {
await this.hqPermissions.assertStoreIdInScope(actorId, storeId);
const store = await this.prisma.store.findUnique({ where: { id: storeId } });
if (!store) throw new NotFoundException('门店不存在');
await this.replaceLivePackages(storeId, packages);
@@ -285,12 +289,13 @@ export class StorePackageService {
]);
}
async adminGetAuditDetail(requestId: bigint) {
async adminGetAuditDetail(requestId: bigint, actorId: bigint) {
const req = await this.prisma.storePackageChangeRequest.findUnique({
where: { id: requestId },
include: { store: { select: { id: true, name: true } } },
include: { store: { select: { id: true, name: true, cityId: true } } },
});
if (!req) throw new NotFoundException('审核记录不存在');
await this.hqPermissions.assertStoreCityInScope(actorId, req.store.cityId);
const livePackages = await this.listLivePackages(req.storeId);
return serializeBigInt({
id: req.id.toString(),
@@ -307,13 +312,19 @@ export class StorePackageService {
});
}
async adminListAudits(query: { status?: string; page?: number; pageSize?: number }) {
async adminListAudits(
query: { status?: string; page?: number; pageSize?: number },
actorId: bigint,
) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const scope = await this.hqPermissions.resolveCityScope(actorId);
const storeWhere = hqStoreCityWhere(scope);
const where: Prisma.StorePackageChangeRequestWhereInput = {};
if (query.status) {
where.status = query.status as Prisma.EnumStorePackageChangeStatusFilter['equals'];
}
if (storeWhere) where.store = storeWhere;
const [items, total] = await Promise.all([
this.prisma.storePackageChangeRequest.findMany({
where,
@@ -350,8 +361,10 @@ export class StorePackageService {
) {
const req = await this.prisma.storePackageChangeRequest.findUnique({
where: { id: requestId },
include: { store: { select: { cityId: true } } },
});
if (!req) throw new NotFoundException('审核记录不存在');
await this.hqPermissions.assertStoreCityInScope(reviewerId, req.store.cityId);
if (req.status !== 'PENDING') {
throw new BadRequestException('该记录已处理');
}
@@ -400,9 +413,11 @@ export class StorePackageService {
return serializeBigInt({ id: requestId.toString(), status: 'APPROVED' });
}
async adminAuditSummary() {
async adminAuditSummary(actorId: bigint) {
const scope = await this.hqPermissions.resolveCityScope(actorId);
const storeWhere = hqStoreCityWhere(scope);
const pendingCount = await this.prisma.storePackageChangeRequest.count({
where: { status: 'PENDING' },
where: { status: 'PENDING', ...(storeWhere ? { store: storeWhere } : {}) },
});
return { pendingCount };
}