feat(task1): restructure directories for turborepo monorepo
- Move backend/ to apps/server/ via git mv - Move frontend/ to apps/admin/ via git mv - Create packages/typescript-config/ with base, nestjs, and react-vite presets
This commit is contained in:
55
apps/server/src/deposits/deposits.controller.ts
Normal file
55
apps/server/src/deposits/deposits.controller.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards, Request } from '@nestjs/common';
|
||||
import { DepositsService } from './deposits.service';
|
||||
import { CreateDepositDto, RefundDepositDto } from './dto/deposit.dto';
|
||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||
import { OperationLogsService } from '../operation-logs/operation-logs.service';
|
||||
import { extractRequestInfo } from '../common/request-utils';
|
||||
import { RequirePermission } from '../auth/decorators/permission.decorator';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('deposits')
|
||||
export class DepositsController {
|
||||
constructor(private service: DepositsService, private logService: OperationLogsService) {}
|
||||
|
||||
@Get()
|
||||
@RequirePermission('deposit:view')
|
||||
findAll(@Query('studentId') studentId?: string, @Query('status') status?: string) {
|
||||
return this.service.findAll({
|
||||
studentId: studentId ? +studentId : undefined,
|
||||
status: status || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('stats')
|
||||
@RequirePermission('deposit:view')
|
||||
getStats() {
|
||||
return this.service.getStats();
|
||||
}
|
||||
|
||||
@Post()
|
||||
@RequirePermission('deposit:create')
|
||||
async create(@Body() dto: CreateDepositDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.create(dto, req.user?.id);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '押金', action: '收取押金', targetId: result.id, targetType: 'deposit', detail: `学生${dto.studentId} ¥${dto.amount}`, ipAddress, userAgent });
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put(':id/refund')
|
||||
@RequirePermission('deposit:edit')
|
||||
async refund(@Param('id') id: string, @Body() dto: RefundDepositDto, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.refund(+id, dto, req.user?.id);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '押金', action: '退还押金', targetId: +id, targetType: 'deposit', detail: `退还¥${result.refundAmount}, 扣除¥${result.deductionAmount}`, ipAddress, userAgent });
|
||||
return result;
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RequirePermission('deposit:delete')
|
||||
async remove(@Param('id') id: string, @Request() req: any) {
|
||||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||||
const result = await this.service.remove(+id);
|
||||
await this.logService.log({ userId: req.user?.id, username: req.user?.username, module: '押金', action: '删除押金记录', targetId: +id, targetType: 'deposit', ipAddress, userAgent });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
14
apps/server/src/deposits/deposits.module.ts
Normal file
14
apps/server/src/deposits/deposits.module.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Deposit } from '../entities/deposit.entity';
|
||||
import { DepositsService } from './deposits.service';
|
||||
import { DepositsController } from './deposits.controller';
|
||||
import { OperationLogsModule } from '../operation-logs/operation-logs.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Deposit]), OperationLogsModule],
|
||||
controllers: [DepositsController],
|
||||
providers: [DepositsService],
|
||||
exports: [DepositsService],
|
||||
})
|
||||
export class DepositsModule {}
|
||||
66
apps/server/src/deposits/deposits.service.ts
Normal file
66
apps/server/src/deposits/deposits.service.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Deposit } from '../entities/deposit.entity';
|
||||
import { CreateDepositDto, RefundDepositDto } from './dto/deposit.dto';
|
||||
|
||||
@Injectable()
|
||||
export class DepositsService {
|
||||
constructor(@InjectRepository(Deposit) private repo: Repository<Deposit>) {}
|
||||
|
||||
async findAll(query?: { studentId?: number; status?: string }) {
|
||||
const qb = this.repo.createQueryBuilder('d')
|
||||
.leftJoinAndSelect('d.student', 'student')
|
||||
.orderBy('d.createdAt', 'DESC');
|
||||
if (query?.studentId) qb.andWhere('d.studentId = :studentId', { studentId: query.studentId });
|
||||
if (query?.status) qb.andWhere('d.status = :status', { status: query.status });
|
||||
return qb.getMany();
|
||||
}
|
||||
|
||||
async create(dto: CreateDepositDto, userId?: number) {
|
||||
return this.repo.save(this.repo.create({
|
||||
studentId: dto.studentId,
|
||||
amount: dto.amount,
|
||||
paidDate: dto.paidDate,
|
||||
notes: dto.notes,
|
||||
status: 'paid',
|
||||
recordedBy: userId,
|
||||
}));
|
||||
}
|
||||
|
||||
async refund(id: number, dto: RefundDepositDto, userId?: number) {
|
||||
const deposit = await this.repo.findOne({ where: { id } });
|
||||
if (!deposit) throw new NotFoundException('押金记录不存在');
|
||||
if (deposit.status !== 'paid') throw new BadRequestException('该押金已处理');
|
||||
|
||||
const deduction = dto.deductionAmount || 0;
|
||||
const refundAmount = Number(deposit.amount) - deduction;
|
||||
if (refundAmount < 0) throw new BadRequestException('扣除金额不能大于押金金额');
|
||||
|
||||
deposit.refundDate = dto.refundDate;
|
||||
deposit.deductionAmount = deduction;
|
||||
deposit.deductionReason = dto.deductionReason || '';
|
||||
deposit.refundAmount = refundAmount;
|
||||
deposit.status = deduction > 0 ? (refundAmount > 0 ? 'partial_refund' : 'deducted') : 'refunded';
|
||||
if (dto.notes) deposit.notes = dto.notes;
|
||||
|
||||
return this.repo.save(deposit);
|
||||
}
|
||||
|
||||
async remove(id: number) {
|
||||
const deposit = await this.repo.findOne({ where: { id } });
|
||||
if (!deposit) throw new NotFoundException('押金记录不存在');
|
||||
await this.repo.delete(id);
|
||||
return { message: '删除成功' };
|
||||
}
|
||||
|
||||
async getStats() {
|
||||
const result = await this.repo.createQueryBuilder('d')
|
||||
.select('d.status', 'status')
|
||||
.addSelect('COUNT(*)', 'count')
|
||||
.addSelect('SUM(d.amount)', 'totalAmount')
|
||||
.groupBy('d.status')
|
||||
.getRawMany();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
33
apps/server/src/deposits/dto/deposit.dto.ts
Normal file
33
apps/server/src/deposits/dto/deposit.dto.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { IsInt, IsNumber, IsString, IsOptional } from 'class-validator';
|
||||
|
||||
export class CreateDepositDto {
|
||||
@IsInt()
|
||||
studentId: number;
|
||||
|
||||
@IsNumber()
|
||||
amount: number;
|
||||
|
||||
@IsString()
|
||||
paidDate: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export class RefundDepositDto {
|
||||
@IsString()
|
||||
refundDate: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
deductionAmount?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
deductionReason?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
notes?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user