This commit is contained in:
2026-04-24 00:28:52 +08:00
parent 6be9d4afb7
commit 524ff8cd32
27 changed files with 568 additions and 314 deletions
+2
View File
@@ -16,6 +16,7 @@ import { AdminAuthModule } from './modules/admin-auth/admin-auth.module';
import { ReviewModule } from './modules/review/review.module';
import { FinanceModule } from './modules/finance/finance.module';
import { ActivityModule } from './modules/activity/activity.module';
import { PlatformConfigModule } from './modules/config/config.module';
import { ScheduleModule as TaskScheduleModule } from './schedule/schedule.module';
@Module({
@@ -53,6 +54,7 @@ import { ScheduleModule as TaskScheduleModule } from './schedule/schedule.module
RoomCalendarModule,
OrderModule,
AdminAuthModule,
PlatformConfigModule,
],
})
export class AppModule {}
+6
View File
@@ -44,6 +44,12 @@ export class Order {
@Column({ name: 'room_amount', type: 'decimal', precision: 10, scale: 2, unsigned: true, comment: '房费总额' })
roomAmount: number;
@Column({ name: 'service_fee', type: 'decimal', precision: 10, scale: 2, unsigned: true, default: 0, comment: '软件服务费' })
serviceFee: number;
@Column({ name: 'merchant_income', type: 'decimal', precision: 10, scale: 2, unsigned: true, default: 0, comment: '商家预计收入' })
merchantIncome: number;
@Column({ name: 'coupon_discount', type: 'decimal', precision: 10, scale: 2, unsigned: true, default: 0, comment: '优惠券抵扣' })
couponDiscount: number;
@@ -0,0 +1,22 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('platform_configs')
export class PlatformConfig {
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
id: number;
@Column({ name: 'config_key', length: 50, unique: true, comment: '配置键' })
configKey: string;
@Column({ name: 'config_value', length: 500, comment: '配置值' })
configValue: string;
@Column({ length: 200, nullable: true, comment: '配置说明' })
description: string;
@CreateDateColumn({ comment: '创建时间' })
createdAt: Date;
@UpdateDateColumn({ comment: '更新时间' })
updatedAt: Date;
}
@@ -124,6 +124,18 @@ export class AdminActivityController {
return this.activityService.getAdminStats();
}
@Get('invite/config')
@ApiOperation({ summary: '获取邀请活动配置' })
async getInviteConfig() {
return this.activityService.getInviteCashbackConfig();
}
@Put('invite/config')
@ApiOperation({ summary: '更新邀请活动配置' })
async updateInviteConfig(@Body() dto: UpdateActivityDto) {
return this.activityService.updateInviteCashbackConfig(dto);
}
@Get('invite/records')
@ApiOperation({ summary: '邀请记录列表' })
async getRecords(@Query() dto: QueryInviteRecordsDto) {
@@ -435,6 +435,36 @@ export class ActivityService {
// ===== 管理端功能 =====
async getInviteCashbackConfig() {
const activity = await this.activityRepo.findOne({
where: { type: 'invite_cashback' },
});
if (!activity) {
// 创建默认配置
activity = this.activityRepo.create({
name: '邀请返现',
type: 'invite_cashback',
enabled: true,
config: {
firstOrderRate: 0.05,
secondOrderRate: 0.005,
minCashback: 0.01,
maxCashback: 50,
withdrawThreshold: 10,
},
});
await this.activityRepo.save(activity);
}
return activity;
}
async updateInviteCashbackConfig(dto: UpdateActivityDto) {
const activity = await this.getInviteCashbackConfig();
if (dto.enabled !== undefined) activity.enabled = dto.enabled;
if (dto.config) activity.config = { ...activity.config, ...dto.config };
return this.activityRepo.save(activity);
}
async getAdminStats(activityId?: number) {
const where: any = {};
if (activityId) where.activityId = activityId;
@@ -0,0 +1,26 @@
import { Controller, Get, Put, Body, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { ConfigService } from './config.service';
import { JwtAuthGuard, RolesGuard, Roles } from '@/common';
@ApiTags('管理端-系统配置')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Controller('admin/config')
export class ConfigController {
constructor(private readonly configService: ConfigService) {}
@Get('service-fee')
@ApiOperation({ summary: '获取服务费配置' })
async getServiceFeeConfig() {
return this.configService.getServiceFeeConfig();
}
@Put('service-fee')
@ApiOperation({ summary: '更新服务费配置' })
async updateServiceFeeConfig(@Body() body: { rate: number }) {
await this.configService.updateServiceFeeRate(body.rate);
return this.configService.getServiceFeeConfig();
}
}
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PlatformConfig } from '@/entities/platform-config.entity';
import { ConfigService } from './config.service';
import { ConfigController } from './config.controller';
@Module({
imports: [TypeOrmModule.forFeature([PlatformConfig])],
controllers: [ConfigController],
providers: [ConfigService],
exports: [ConfigService],
})
export class ConfigModule {}
@@ -0,0 +1,57 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PlatformConfig } from '@/entities/platform-config.entity';
@Injectable()
export class ConfigService {
constructor(
@InjectRepository(PlatformConfig)
private configRepo: Repository<PlatformConfig>,
) {}
async getConfig(key: string): Promise<string | null> {
const config = await this.configRepo.findOne({ where: { configKey: key } });
return config?.configValue || null;
}
async setConfig(key: string, value: string, description?: string): Promise<void> {
let config = await this.configRepo.findOne({ where: { configKey: key } });
if (config) {
config.configValue = value;
if (description) config.description = description;
} else {
config = this.configRepo.create({ configKey: key, configValue: value, description });
}
await this.configRepo.save(config);
}
async getServiceFeeRate(): Promise<number> {
const value = await this.getConfig('service_fee_rate');
const rate = parseFloat(value || '0.05');
return isNaN(rate) ? 0.05 : rate;
}
async getCommissionRate(): Promise<number> {
const value = await this.getConfig('commission_rate');
const rate = parseFloat(value || '0.10');
return isNaN(rate) ? 0.10 : rate;
}
async getServiceFeeConfig(): Promise<{ rate: number; description: string }> {
const value = await this.getConfig('service_fee_rate');
const config = await this.configRepo.findOne({ where: { configKey: 'service_fee_rate' } });
const rate = parseFloat(value || '0.05');
return {
rate: isNaN(rate) ? 0.05 : rate,
description: config?.description || '软件服务费比例',
};
}
async updateServiceFeeRate(rate: number): Promise<void> {
if (rate < 0 || rate > 1) {
throw new Error('服务费比例必须在0-1之间');
}
await this.setConfig('service_fee_rate', rate.toString(), '软件服务费比例');
}
}
@@ -119,13 +119,6 @@ export class FinanceService {
order: { createdAt: 'ASC' },
});
// 计算手续费率
const feeRate = 0.006;
const fee = Math.round(dto.amount * feeRate * 100) / 100;
const commissionRate = Number(merchant.walletBalance) > 0 ? 0.10 : 0;
const commissionAmount = Math.round(dto.amount * commissionRate * 100) / 100;
const actualAmount = Math.round((dto.amount - fee - commissionAmount) * 100) / 100;
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
@@ -138,9 +131,9 @@ export class FinanceService {
merchantId,
settlementIds: approvedSettlements.map(s => s.id),
amount: dto.amount,
fee,
commissionAmount,
actualAmount,
fee: 0,
commissionAmount: 0,
actualAmount: dto.amount,
bankName: dto.bankName,
bankAccount: dto.bankAccount,
accountName: dto.accountName,
@@ -361,8 +354,13 @@ export class FinanceService {
const withdrawalStats = await wQb
.select('COUNT(*)', 'totalWithdrawals')
.addSelect('COALESCE(SUM(CASE WHEN w.status = \'paid\' THEN w.amount ELSE 0 END), 0)', 'paidAmount')
.addSelect('COALESCE(SUM(w.fee), 0)', 'totalFee')
.addSelect('COALESCE(SUM(w.commission_amount), 0)', 'totalWithdrawCommission')
.getRawOne();
// 服务费统计
const serviceFeeStats = await this.orderRepo
.createQueryBuilder('o')
.where('o.status IN (:...statuses)', { statuses: ['completed', 'checked_in'] })
.select('COALESCE(SUM(o.service_fee), 0)', 'totalServiceFee')
.getRawOne();
return {
@@ -371,9 +369,8 @@ export class FinanceService {
settlementAmount: Number(settlementStats?.totalSettlementAmount || 0),
totalSettlements: Number(settlementStats?.totalSettlements || 0),
paidAmount: Number(withdrawalStats?.paidAmount || 0),
totalFee: Number(withdrawalStats?.totalFee || 0),
totalWithdrawCommission: Number(withdrawalStats?.totalWithdrawCommission || 0),
totalWithdrawals: Number(withdrawalStats?.totalWithdrawals || 0),
totalServiceFee: Number(serviceFeeStats?.totalServiceFee || 0),
};
}
@@ -7,12 +7,14 @@ import { OrderService } from './order.service';
import { UserOrderController, MerchantOrderController, AdminOrderController } from './order.controller';
import { MerchantModule } from '../merchant/merchant.module';
import { ActivityModule } from '../activity/activity.module';
import { PlatformConfigModule } from '../config/config.module';
@Module({
imports: [
TypeOrmModule.forFeature([Order, Room, RoomCalendar]),
MerchantModule,
forwardRef(() => ActivityModule),
PlatformConfigModule,
],
controllers: [UserOrderController, MerchantOrderController, AdminOrderController],
providers: [OrderService],
@@ -6,6 +6,7 @@ import { Room } from '@/entities/room.entity';
import { RoomCalendar } from '@/entities/room-calendar.entity';
import { CreateOrderDto, QueryOrderDto, ConfirmOrderDto } from './dto/order.dto';
import { ActivityService } from '@/modules/activity/activity.service';
import { ConfigService } from '@/modules/config/config.service';
@Injectable()
export class OrderService {
@@ -17,6 +18,7 @@ export class OrderService {
@InjectRepository(RoomCalendar)
private calendarRepo: Repository<RoomCalendar>,
private readonly activityService: ActivityService,
private readonly configService: ConfigService,
) {}
async create(userId: number, dto: CreateOrderDto) {
@@ -36,6 +38,11 @@ export class OrderService {
const totalAmount = roomAmount;
const payAmount = totalAmount - couponDiscount;
// 计算软件服务费和商家收入
const serviceFeeRate = await this.configService.getServiceFeeRate();
const serviceFee = Math.round(payAmount * serviceFeeRate * 100) / 100;
const merchantIncome = Math.round((payAmount - serviceFee) * 100) / 100;
const now = new Date();
const orderNo = [
now.getFullYear(),
@@ -59,6 +66,8 @@ export class OrderService {
guestCount: dto.guestCount || 1,
roomPrice: room.price,
roomAmount,
serviceFee,
merchantIncome,
couponDiscount,
totalAmount,
payAmount,