@@ -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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user