This commit is contained in:
2026-07-01 08:27:26 +08:00
parent 9f4577d3d8
commit 25f0d8e97b
56 changed files with 5298 additions and 2 deletions
@@ -0,0 +1,233 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import type { AdminStoreAccountsQueryDto, AdminStoreMediaQueryDto, AdminStoresQueryDto } from './dto/admin-query.dto';
import type {
CreateStoreAccountDto,
CreateStoreDto,
CreateStoreMediaDto,
UpdateStoreAccountDto,
UpdateStoreDto,
UpdateStoreMediaDto,
UpdateStoreStatusDto,
} from './dto/admin-mutate.dto';
@Injectable()
export class AdminStoresService {
constructor(private readonly prisma: PrismaService) {}
async listStores(query: AdminStoresQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where: Prisma.StoreWhereInput = {};
if (query.name) where.name = { contains: query.name };
if (query.status) where.status = query.status as Prisma.EnumStoreStatusFilter['equals'];
if (query.cityId) where.cityId = BigInt(query.cityId);
if (query.partnerId) where.partnerId = BigInt(query.partnerId);
if (query.phone) where.phone = { contains: query.phone };
const [items, total] = await Promise.all([
this.prisma.store.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
include: {
cityRef: { select: { id: true, name: true, code: true } },
partner: { select: { id: true, companyName: true } },
account: { select: { id: true, phone: true, name: true, status: true } },
},
}),
this.prisma.store.count({ where }),
]);
return serializeBigInt({ items, total, page, pageSize });
}
async detailStore(id: bigint) {
const store = await this.prisma.store.findUnique({
where: { id },
include: {
cityRef: true,
partner: true,
category: true,
account: true,
media: { orderBy: { sortOrder: 'asc' } },
audits: { orderBy: { submittedAt: 'desc' }, take: 5 },
_count: { select: { redeemRecords: true, ratings: true } },
},
});
if (!store) throw new NotFoundException('门店不存在');
return serializeBigInt({
...store,
redeemCount: store._count.redeemRecords,
ratingCount: store._count.ratings,
_count: undefined,
});
}
async updateStoreStatus(id: bigint, dto: UpdateStoreStatusDto) {
const store = await this.prisma.store.update({
where: { id },
data: { status: dto.status as 'OPEN' | 'PAUSED' | 'CLOSED' },
});
return serializeBigInt(store);
}
async updateStore(id: bigint, dto: UpdateStoreDto) {
const store = await this.prisma.store.update({
where: { id },
data: {
...(dto.name !== undefined ? { name: dto.name } : {}),
...(dto.phone !== undefined ? { phone: dto.phone } : {}),
...(dto.intro !== undefined ? { intro: dto.intro } : {}),
...(dto.coverUrl !== undefined ? { coverUrl: dto.coverUrl } : {}),
...(dto.address !== undefined ? { address: dto.address } : {}),
...(dto.district !== undefined ? { district: dto.district } : {}),
},
});
return serializeBigInt(store);
}
async createStore(dto: CreateStoreDto) {
const partner = await this.prisma.partner.findUnique({ where: { id: BigInt(dto.partnerId) } });
if (!partner) throw new BadRequestException('开城合伙人不存在');
const city = await this.prisma.city.findUnique({ where: { id: BigInt(dto.cityId) } });
if (!city) throw new BadRequestException('开城城市不存在');
const store = await this.prisma.store.create({
data: {
cityId: city.id,
partnerId: partner.id,
categoryId: dto.categoryId ? BigInt(dto.categoryId) : null,
name: dto.name,
phone: dto.phone,
province: dto.province ?? city.province,
cityName: dto.city ?? city.name,
district: dto.district ?? '',
address: dto.address,
intro: dto.intro ?? null,
coverUrl: dto.coverUrl ?? null,
status: 'OPEN',
},
});
await this.prisma.storeAccount.create({
data: {
storeId: store.id,
phone: dto.accountPhone ?? dto.phone,
name: dto.accountName ?? dto.name,
},
});
return serializeBigInt(store);
}
async createStoreAccount(dto: CreateStoreAccountDto) {
const store = await this.prisma.store.findUnique({
where: { id: BigInt(dto.storeId) },
include: { account: true },
});
if (!store) throw new BadRequestException('门店不存在');
if (store.account) throw new BadRequestException('门店已有账户');
const account = await this.prisma.storeAccount.create({
data: { storeId: store.id, phone: dto.phone, name: dto.name },
});
return serializeBigInt(account);
}
async listStoreMedia(query: AdminStoreMediaQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where: Prisma.StoreMediaWhereInput = {};
if (query.storeId) where.storeId = BigInt(query.storeId);
if (query.mediaType) where.mediaType = query.mediaType;
const [items, total] = await Promise.all([
this.prisma.storeMedia.findMany({
where,
orderBy: [{ sortOrder: 'asc' }, { createdAt: 'desc' }],
skip: (page - 1) * pageSize,
take: pageSize,
include: { store: { select: { id: true, name: true } } },
}),
this.prisma.storeMedia.count({ where }),
]);
return serializeBigInt({ items, total, page, pageSize });
}
async createStoreMedia(dto: CreateStoreMediaDto) {
const store = await this.prisma.store.findUnique({ where: { id: BigInt(dto.storeId) } });
if (!store) throw new BadRequestException('门店不存在');
const media = await this.prisma.storeMedia.create({
data: {
storeId: store.id,
mediaType: dto.mediaType,
url: dto.url,
sortOrder: dto.sortOrder ?? 0,
},
});
return serializeBigInt(media);
}
async updateStoreMedia(id: bigint, dto: UpdateStoreMediaDto) {
const media = await this.prisma.storeMedia.update({
where: { id },
data: {
...(dto.url !== undefined ? { url: dto.url } : {}),
...(dto.mediaType !== undefined ? { mediaType: dto.mediaType } : {}),
...(dto.sortOrder !== undefined ? { sortOrder: dto.sortOrder } : {}),
},
});
return serializeBigInt(media);
}
async deleteStoreMedia(id: bigint) {
await this.prisma.storeMedia.delete({ where: { id } });
return { ok: true };
}
async listStoreAccounts(query: AdminStoreAccountsQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where: Prisma.StoreAccountWhereInput = {};
if (query.phone) where.phone = { contains: query.phone };
if (query.storeId) where.storeId = BigInt(query.storeId);
if (query.status) where.status = query.status as Prisma.EnumAccountStatusFilter['equals'];
const [items, total] = await Promise.all([
this.prisma.storeAccount.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
include: {
store: { select: { id: true, name: true, status: true, cityName: true } },
},
}),
this.prisma.storeAccount.count({ where }),
]);
return serializeBigInt({ items, total, page, pageSize });
}
async detailStoreAccount(id: bigint) {
const account = await this.prisma.storeAccount.findUnique({
where: { id },
include: { store: { include: { cityRef: true, partner: true } } },
});
if (!account) throw new NotFoundException('门店账号不存在');
return serializeBigInt(account);
}
async updateStoreAccount(id: bigint, dto: UpdateStoreAccountDto) {
const account = await this.prisma.storeAccount.update({
where: { id },
data: {
...(dto.name !== undefined ? { name: dto.name } : {}),
...(dto.phone !== undefined ? { phone: dto.phone } : {}),
...(dto.status !== undefined ? { status: dto.status as 'ACTIVE' | 'DISABLED' } : {}),
},
});
return serializeBigInt(account);
}
}