This commit is contained in:
2026-06-30 10:33:56 +08:00
commit 6e047dc0a5
607 changed files with 65966 additions and 0 deletions
@@ -0,0 +1,74 @@
import { Body, Controller, Get, Param, Post, Put, Query, UseGuards } from '@nestjs/common';
import { StoreService } from './store.service';
import { RedeemService } from '../redeem/redeem.service';
import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
@Controller('stores')
export class PublicStoreController {
constructor(private readonly storeService: StoreService) {}
@Get()
list(@Query('cityCode') cityCode?: string) {
return this.storeService.listOpenStores(cityCode);
}
@Get(':id')
detail(@Param('id') id: string) {
return this.storeService.getStore(BigInt(id));
}
}
@Controller('partner/stores')
@UseGuards(JwtAuthGuard)
export class PartnerStoreController {
constructor(private readonly storeService: StoreService) {}
@Get()
list(@CurrentUser() user: AuthUser) {
return this.storeService.partnerListStores(user.actorId);
}
@Post()
create(@CurrentUser() user: AuthUser, @Body() body: Record<string, unknown>) {
return this.storeService.createStore(user.actorId, body);
}
}
@Controller('partner/dashboard')
@UseGuards(JwtAuthGuard)
export class PartnerDashboardController {
constructor(private readonly storeService: StoreService) {}
@Get()
dashboard(@CurrentUser() user: AuthUser) {
return this.storeService.partnerDashboard(user.actorId);
}
}
@Controller('shop/store')
@UseGuards(JwtAuthGuard)
export class ShopStoreController {
constructor(private readonly storeService: StoreService) {}
@Get()
info(@CurrentUser() user: AuthUser) {
return this.storeService.getShopStore(user.actorId);
}
@Put('status')
status(@CurrentUser() user: AuthUser, @Body() body: { status: 'OPEN' | 'PAUSED' }) {
return this.storeService.updateShopStatus(user.actorId, body.status);
}
}
@Controller('shop/dashboard')
@UseGuards(JwtAuthGuard)
export class ShopDashboardController {
constructor(private readonly redeemService: RedeemService) {}
@Get()
async dashboard(@CurrentUser() user: AuthUser) {
return this.redeemService.getShopDashboard(user.actorId);
}
}
@@ -0,0 +1,25 @@
import { Module, forwardRef } from '@nestjs/common';
import { IamModule } from '../iam/iam.module';
import { RedeemModule } from '../redeem/redeem.module';
import { StoreService } from './store.service';
import {
PartnerDashboardController,
PartnerStoreController,
PublicStoreController,
ShopDashboardController,
ShopStoreController,
} from './store.controller';
@Module({
imports: [IamModule, forwardRef(() => RedeemModule)],
controllers: [
PublicStoreController,
PartnerStoreController,
PartnerDashboardController,
ShopStoreController,
ShopDashboardController,
],
providers: [StoreService],
exports: [StoreService],
})
export class StoreModule {}
@@ -0,0 +1,133 @@
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { loadAppConfig } from '@dukang/shared-types';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
@Injectable()
export class StoreService {
private readonly config = loadAppConfig();
constructor(private readonly prisma: PrismaService) {}
async listOpenStores(cityCode?: string) {
const where: Record<string, unknown> = { status: 'OPEN' };
if (cityCode) {
const city = await this.prisma.city.findFirst({ where: { code: cityCode } });
if (city) where.cityId = city.id;
}
const stores = await this.prisma.store.findMany({
where: where as never,
include: { category: true },
orderBy: { createdAt: 'desc' },
});
return serializeBigInt(stores);
}
async getStore(id: bigint) {
const store = await this.prisma.store.findFirst({
where: { id, status: 'OPEN' },
include: { category: true, media: true },
});
if (!store) throw new NotFoundException('门店不存在');
return serializeBigInt(store);
}
async partnerListStores(partnerAccountId: bigint) {
const account = await this.getPartnerAccount(partnerAccountId);
const stores = await this.prisma.store.findMany({
where: { partnerId: account.partnerId },
include: { category: true, audits: { orderBy: { submittedAt: 'desc' }, take: 1 } },
orderBy: { createdAt: 'desc' },
});
return serializeBigInt(stores);
}
async createStore(partnerAccountId: bigint, body: Record<string, unknown>) {
const account = await this.getPartnerAccount(partnerAccountId);
const city = await this.prisma.city.findFirst({ where: { partnerId: account.partnerId } });
if (!city) throw new BadRequestException('合伙人未绑定开城');
const store = await this.prisma.store.create({
data: {
cityId: city.id,
partnerId: account.partnerId,
categoryId: body.categoryId ? BigInt(String(body.categoryId)) : null,
name: String(body.name),
phone: String(body.phone),
province: String(body.province ?? '河南省'),
cityName: String(body.city ?? '郑州市'),
district: String(body.district ?? ''),
address: String(body.address),
intro: body.intro ? String(body.intro) : null,
coverUrl: body.coverUrl ? String(body.coverUrl) : null,
bankAccountName: body.bankAccountName ? String(body.bankAccountName) : null,
bankAccountNo: body.bankAccountNo ? String(body.bankAccountNo) : null,
bankBranch: body.bankBranch ? String(body.bankBranch) : null,
openTime: body.openTime ? String(body.openTime) : '10:00',
closeTime: body.closeTime ? String(body.closeTime) : '22:00',
status: this.config.autoApproveStore ? 'OPEN' : 'PAUSED',
},
});
const audit = await this.prisma.storeAudit.create({
data: {
storeId: store.id,
auditType: 'NEW',
status: this.config.autoApproveStore ? 'APPROVED' : 'PENDING',
submitData: body as never,
reviewedAt: this.config.autoApproveStore ? new Date() : null,
},
});
await this.prisma.storeAccount.create({
data: {
storeId: store.id,
phone: String(body.accountPhone ?? body.phone),
name: String(body.accountName ?? body.name),
},
});
return serializeBigInt({ store, audit });
}
async getShopStore(storeAccountId: bigint) {
const account = await this.prisma.storeAccount.findUniqueOrThrow({
where: { id: storeAccountId },
include: { store: { include: { category: true } } },
});
return serializeBigInt(account.store);
}
async updateShopStatus(storeAccountId: bigint, status: 'OPEN' | 'PAUSED') {
const account = await this.prisma.storeAccount.findUniqueOrThrow({
where: { id: storeAccountId },
});
const store = await this.prisma.store.update({
where: { id: account.storeId },
data: { status },
});
return serializeBigInt(store);
}
async partnerDashboard(partnerAccountId: bigint) {
const account = await this.getPartnerAccount(partnerAccountId);
const [storeCount, orderCount] = await Promise.all([
this.prisma.store.count({ where: { partnerId: account.partnerId } }),
this.prisma.order.count({
where: { city: { partnerId: account.partnerId } },
}),
]);
return { storeCount, orderCount, companyName: account.partner.companyName };
}
private async getPartnerAccount(partnerAccountId: bigint) {
return this.prisma.partnerAccount.findUniqueOrThrow({
where: { id: partnerAccountId },
include: { partner: true },
});
}
}