dev
This commit is contained in:
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user