feat: 迭代
This commit is contained in:
@@ -335,9 +335,14 @@ async function handlePhoneLogin() {
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/index/index' });
|
||||
}, 500);
|
||||
} catch (e) {
|
||||
} catch (error: any) {
|
||||
uni.hideLoading();
|
||||
console.error(e);
|
||||
console.error('手机号登录失败', error);
|
||||
uni.showToast({
|
||||
title: error.message || '登录失败,请重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -83,6 +83,14 @@ export function request<T = any>(options: RequestOptions): Promise<ApiResponse<T
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有 token,如果没有 token 说明是登录接口返回的 401,直接返回原始错误消息
|
||||
const hasToken = useSellerToken ? uni.getStorageSync('sellerToken') : uni.getStorageSync('token');
|
||||
if (!hasToken) {
|
||||
// 登录接口的 401 错误,直接返回后端的错误消息
|
||||
reject(new Error(responseData.message || '认证失败'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 正常 401 处理,只清除对应账户的 token,不互相干扰
|
||||
if (useSellerToken) {
|
||||
// 商家 token 失效,跳转到商家登录页
|
||||
|
||||
@@ -5,15 +5,20 @@ import { getFinancialOverview, getPlatformTransactions } from '@/api/finance';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
|
||||
interface FinancialOverview {
|
||||
systemTotalAmount: number;
|
||||
totalUserPaid: number;
|
||||
totalRefund: number;
|
||||
totalWithdrawn: number;
|
||||
platformBalance: number;
|
||||
platformTotalIncome: number;
|
||||
platformTotalExpense: number;
|
||||
totalUserBalance: number;
|
||||
totalMerchantBalance: number;
|
||||
userCount: number;
|
||||
merchantCount: number;
|
||||
todayIncome: number;
|
||||
todayExpense: number;
|
||||
monthIncome: number;
|
||||
monthExpense: number;
|
||||
pendingWithdrawals: number;
|
||||
pendingSettlements: number;
|
||||
todayOrders: number;
|
||||
}
|
||||
|
||||
interface Transaction {
|
||||
@@ -108,18 +113,21 @@ const FinanceDashboard: React.FC = () => {
|
||||
|
||||
{overview && (
|
||||
<>
|
||||
{/* 平台总账户余额 - 突出显示 */}
|
||||
{/* 系统总账户金额 - 突出显示 */}
|
||||
<Card style={{ marginBottom: 24, background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }}>
|
||||
<Statistic
|
||||
title={<span style={{ color: '#fff', fontSize: 16 }}>平台总账户余额</span>}
|
||||
value={Number(overview.platformBalance || 0) + Number(overview.totalMerchantBalance || 0) + Number(overview.totalUserBalance || 0)}
|
||||
title={<span style={{ color: '#fff', fontSize: 16 }}>系统总账户金额</span>}
|
||||
value={Number(overview.systemTotalAmount || 0)}
|
||||
prefix={<WalletOutlined style={{ color: '#fff' }} />}
|
||||
suffix={<span style={{ color: '#fff' }}>元</span>}
|
||||
precision={2}
|
||||
valueStyle={{ color: '#fff', fontSize: 36, fontWeight: 'bold' }}
|
||||
/>
|
||||
<div style={{ color: 'rgba(255,255,255,0.8)', fontSize: 12, marginTop: 8 }}>
|
||||
平台内所有未提现的资金总额(包含平台钱包、商家账户、用户账户)
|
||||
用户实付(¥{Number(overview.totalUserPaid || 0).toFixed(2)})- 退款(¥{Number(overview.totalRefund || 0).toFixed(2)})- 提现(¥{Number(overview.totalWithdrawn || 0).toFixed(2)})
|
||||
</div>
|
||||
<div style={{ color: 'rgba(255,255,255,0.6)', fontSize: 11, marginTop: 4 }}>
|
||||
= 商家账户余额(¥{Number(overview.totalMerchantBalance || 0).toFixed(2)})+ 用户账户余额(¥{Number(overview.totalUserBalance || 0).toFixed(2)})+ 平台净收益(¥{Number(overview.platformBalance || 0).toFixed(2)})
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -127,13 +135,16 @@ const FinanceDashboard: React.FC = () => {
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="平台账户余额"
|
||||
title="平台净收益"
|
||||
value={overview.platformBalance}
|
||||
precision={2}
|
||||
prefix={<WalletOutlined />}
|
||||
suffix="元"
|
||||
valueStyle={{ color: '#1890ff' }}
|
||||
/>
|
||||
<div style={{ fontSize: 12, color: '#999', marginTop: 8 }}>
|
||||
服务费收入 - 邀请返现
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
@@ -146,6 +157,9 @@ const FinanceDashboard: React.FC = () => {
|
||||
suffix="元"
|
||||
valueStyle={{ color: '#52c41a' }}
|
||||
/>
|
||||
<div style={{ fontSize: 12, color: '#999', marginTop: 8 }}>
|
||||
{overview.userCount} 个用户账户
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
@@ -158,23 +172,25 @@ const FinanceDashboard: React.FC = () => {
|
||||
suffix="元"
|
||||
valueStyle={{ color: '#faad14' }}
|
||||
/>
|
||||
<div style={{ fontSize: 12, color: '#999', marginTop: 8 }}>
|
||||
{overview.merchantCount} 个商家账户
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="待处理提现"
|
||||
value={overview.pendingWithdrawals}
|
||||
precision={2}
|
||||
suffix="元"
|
||||
valueStyle={{ color: '#ff4d4f' }}
|
||||
title="今日订单数"
|
||||
value={overview.todayOrders}
|
||||
suffix="单"
|
||||
valueStyle={{ color: '#722ed1' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={24} style={{ marginBottom: 24 }}>
|
||||
<Col span={6}>
|
||||
<Col span={8}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="今日收入"
|
||||
@@ -186,7 +202,7 @@ const FinanceDashboard: React.FC = () => {
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Col span={8}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="今日支出"
|
||||
@@ -198,6 +214,19 @@ const FinanceDashboard: React.FC = () => {
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="今日净收益"
|
||||
value={Number(overview.todayIncome || 0) - Number(overview.todayExpense || 0)}
|
||||
precision={2}
|
||||
suffix="元"
|
||||
valueStyle={{
|
||||
color: (Number(overview.todayIncome || 0) - Number(overview.todayExpense || 0)) >= 0 ? '#52c41a' : '#ff4d4f'
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
|
||||
@@ -8,21 +8,18 @@ export class PlatformAccount {
|
||||
@Column({ type: 'varchar', length: 50, comment: '账户名称(如:主账户、备用账户)' })
|
||||
account_name: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '可用余额' })
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '可用余额(平台净收益 = total_income - total_expense)' })
|
||||
balance: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '冻结余额(提现中)' })
|
||||
frozen_balance: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计收入(订单收入)' })
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计收入(服务费收入)' })
|
||||
total_income: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计支出(商家结算+退款)' })
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计支出(邀请返现支出)' })
|
||||
total_expense: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计服务费收入' })
|
||||
total_service_fee: number;
|
||||
|
||||
@VersionColumn({ comment: '乐观锁版本号' })
|
||||
version: number;
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index, VersionColumn } from 'typeorm';
|
||||
|
||||
@Entity('system_accounts')
|
||||
export class SystemAccount {
|
||||
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true })
|
||||
id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, comment: '账户名称(如:主账户)' })
|
||||
account_name: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '可用余额(total_income - total_refund - total_withdrawn)' })
|
||||
balance: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计收入(用户实付总额)' })
|
||||
total_income: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计退款' })
|
||||
total_refund: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, default: 0, comment: '累计提现(所有提现)' })
|
||||
total_withdrawn: number;
|
||||
|
||||
@VersionColumn({ comment: '乐观锁版本号' })
|
||||
version: number;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: ['active', 'frozen', 'closed'],
|
||||
default: 'active',
|
||||
comment: '状态'
|
||||
})
|
||||
@Index()
|
||||
status: 'active' | 'frozen' | 'closed';
|
||||
|
||||
@CreateDateColumn({ comment: '创建时间' })
|
||||
created_at: Date;
|
||||
|
||||
@UpdateDateColumn({ comment: '更新时间' })
|
||||
updated_at: Date;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index } from 'typeorm';
|
||||
|
||||
@Entity('system_transactions')
|
||||
@Index('uk_transaction_no', ['transactionNo'], { unique: true })
|
||||
@Index('idx_account_id', ['accountId'])
|
||||
@Index('idx_transaction_type', ['transactionType'])
|
||||
@Index('idx_business', ['businessType', 'businessId'])
|
||||
@Index('idx_created_at', ['createdAt'])
|
||||
export class SystemTransaction {
|
||||
@PrimaryGeneratedColumn({ type: 'bigint', unsigned: true, comment: '流水ID' })
|
||||
id: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 32, name: 'transaction_no', comment: '交易流水号(全局唯一)' })
|
||||
transactionNo: string;
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, name: 'account_id', comment: '系统账户ID' })
|
||||
accountId: number;
|
||||
|
||||
@Column({ type: 'enum', enum: ['income', 'expense'], comment: '方向:income-收入/expense-支出' })
|
||||
direction: 'income' | 'expense';
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, comment: '金额(正数)' })
|
||||
amount: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, name: 'balance_before', comment: '交易前余额' })
|
||||
balanceBefore: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 12, scale: 2, name: 'balance_after', comment: '交易后余额' })
|
||||
balanceAfter: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, name: 'transaction_type', comment: '交易类型' })
|
||||
transactionType: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, name: 'business_type', comment: '业务类型:order_payment/refund/withdraw' })
|
||||
businessType: string;
|
||||
|
||||
@Column({ type: 'bigint', unsigned: true, nullable: true, name: 'business_id', comment: '业务ID(订单ID/退款ID/提现ID等)' })
|
||||
businessId: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 32, nullable: true, name: 'business_no', comment: '业务单号' })
|
||||
businessNo: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 500, nullable: true, comment: '备注' })
|
||||
remark: string;
|
||||
|
||||
@CreateDateColumn({ type: 'datetime', name: 'created_at', comment: '创建时间' })
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { MktUserInviteStats } from '@/entities/mkt-user-invite-stats.entity';
|
||||
import { MktInviteWithdrawal } from '@/entities/mkt-invite-withdrawal.entity';
|
||||
import { Order } from '@/entities/order.entity';
|
||||
import { User } from '@/entities/user.entity';
|
||||
import { FinanceModule } from '@/modules/shared/finance/finance.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -21,6 +22,7 @@ import { User } from '@/entities/user.entity';
|
||||
Order,
|
||||
User,
|
||||
]),
|
||||
FinanceModule,
|
||||
],
|
||||
controllers: [ActivityController],
|
||||
providers: [ActivityService],
|
||||
|
||||
@@ -8,6 +8,8 @@ import { MktUserInviteStats } from '@/entities/mkt-user-invite-stats.entity';
|
||||
import { MktInviteWithdrawal } from '@/entities/mkt-invite-withdrawal.entity';
|
||||
import { Order } from '@/entities/order.entity';
|
||||
import { User } from '@/entities/user.entity';
|
||||
import { AccountService } from '@/modules/shared/finance/account.service';
|
||||
import { TransactionService } from '@/modules/shared/finance/transaction.service';
|
||||
import {
|
||||
BindInvitationDto,
|
||||
CreateInviteWithdrawalDto,
|
||||
@@ -33,6 +35,8 @@ export class ActivityService {
|
||||
private orderRepo: Repository<Order>,
|
||||
@InjectRepository(User)
|
||||
private userRepo: Repository<User>,
|
||||
private accountService: AccountService,
|
||||
private transactionService: TransactionService,
|
||||
private dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
@@ -415,6 +419,30 @@ export class ActivityService {
|
||||
);
|
||||
}
|
||||
|
||||
// 账户操作:给邀请人用户账户增加返现金额,从平台账户扣减
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
|
||||
// 给用户账户增加返现
|
||||
await this.accountService.addUserBalance(
|
||||
invitation.inviterId,
|
||||
amount,
|
||||
transactionNo,
|
||||
'invite_cashback',
|
||||
cashback.id,
|
||||
order.orderNo,
|
||||
`邀请返现 - 订单${order.orderNo}第${orderIndex}单`,
|
||||
);
|
||||
|
||||
// 从平台账户扣减返现支出
|
||||
await this.accountService.deductPlatformCashback(
|
||||
amount,
|
||||
transactionNo,
|
||||
'invite_cashback',
|
||||
cashback.id,
|
||||
order.orderNo,
|
||||
`邀请返现支出 - 用户${invitation.inviterId}订单${order.orderNo}`,
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
this.logger.log(
|
||||
`订单 ${order.orderNo} 完成,邀请人 ${invitation.inviterId} 获得第${orderIndex}单返现 ${amount} 元(比例${rate * 100}%)`,
|
||||
|
||||
@@ -329,11 +329,10 @@ export class OrderService {
|
||||
paidAt: new Date(),
|
||||
});
|
||||
|
||||
// 记录平台账户收入
|
||||
// 记录系统总账户收入(用户实付金额)
|
||||
const transactionNo = `TXN${Date.now()}${Math.floor(Math.random() * 10000)}`;
|
||||
await this.accountService.addPlatformBalance(
|
||||
await this.accountService.addSystemIncome(
|
||||
order.payAmount,
|
||||
order.serviceFee || 0,
|
||||
transactionNo,
|
||||
'order_payment',
|
||||
order.id,
|
||||
@@ -378,11 +377,10 @@ export class OrderService {
|
||||
paidAt: new Date(),
|
||||
});
|
||||
|
||||
// 记录平台账户收入
|
||||
// 记录系统总账户收入(用户实付金额)
|
||||
const transactionNo = `TXN${Date.now()}${Math.floor(Math.random() * 10000)}`;
|
||||
await this.accountService.addPlatformBalance(
|
||||
await this.accountService.addSystemIncome(
|
||||
order.payAmount,
|
||||
order.serviceFee || 0,
|
||||
transactionNo,
|
||||
'order_payment',
|
||||
order.id,
|
||||
|
||||
@@ -4,9 +4,11 @@ import { Repository, DataSource, FindOptionsWhere } from 'typeorm';
|
||||
import { UserAccount } from '@/entities/user-account.entity';
|
||||
import { MerchantAccount } from '@/entities/merchant-account.entity';
|
||||
import { PlatformAccount } from '@/entities/platform-account.entity';
|
||||
import { SystemAccount } from '@/entities/system-account.entity';
|
||||
import { UserTransaction } from '@/entities/user-transaction.entity';
|
||||
import { MerchantTransaction } from '@/entities/merchant-transaction.entity';
|
||||
import { PlatformTransaction } from '@/entities/platform-transaction.entity';
|
||||
import { SystemTransaction } from '@/entities/system-transaction.entity';
|
||||
import { QueryUserAccountsDto, QueryMerchantAccountsDto } from './dto/account.dto';
|
||||
|
||||
@Injectable()
|
||||
@@ -18,12 +20,16 @@ export class AccountService {
|
||||
private merchantAccountRepo: Repository<MerchantAccount>,
|
||||
@InjectRepository(PlatformAccount)
|
||||
private platformAccountRepo: Repository<PlatformAccount>,
|
||||
@InjectRepository(SystemAccount)
|
||||
private systemAccountRepo: Repository<SystemAccount>,
|
||||
@InjectRepository(UserTransaction)
|
||||
private userTransactionRepo: Repository<UserTransaction>,
|
||||
@InjectRepository(MerchantTransaction)
|
||||
private merchantTransactionRepo: Repository<MerchantTransaction>,
|
||||
@InjectRepository(PlatformTransaction)
|
||||
private platformTransactionRepo: Repository<PlatformTransaction>,
|
||||
@InjectRepository(SystemTransaction)
|
||||
private systemTransactionRepo: Repository<SystemTransaction>,
|
||||
private dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
@@ -429,10 +435,9 @@ export class AccountService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台账户增加余额(订单收入)
|
||||
* 平台账户增加服务费收入
|
||||
*/
|
||||
async addPlatformBalance(
|
||||
amount: number,
|
||||
async addPlatformServiceFee(
|
||||
serviceFee: number,
|
||||
transactionNo: string,
|
||||
businessType: string,
|
||||
@@ -454,14 +459,12 @@ export class AccountService {
|
||||
throw new NotFoundException('平台账户不存在');
|
||||
}
|
||||
|
||||
const balanceBefore = Number(account.balance);
|
||||
const amountNum = Number(amount);
|
||||
const serviceFeeNum = Number(serviceFee);
|
||||
const balanceAfter = parseFloat((balanceBefore + amountNum).toFixed(2));
|
||||
const balanceBefore = Number(account.balance);
|
||||
const balanceAfter = parseFloat((balanceBefore + serviceFeeNum).toFixed(2));
|
||||
|
||||
account.balance = balanceAfter;
|
||||
account.total_income = parseFloat((Number(account.total_income) + amountNum).toFixed(2));
|
||||
account.total_service_fee = parseFloat((Number(account.total_service_fee) + serviceFeeNum).toFixed(2));
|
||||
account.total_income = parseFloat((Number(account.total_income) + serviceFeeNum).toFixed(2));
|
||||
account.version += 1;
|
||||
|
||||
await queryRunner.manager.save(account);
|
||||
@@ -470,10 +473,10 @@ export class AccountService {
|
||||
transaction_no: transactionNo,
|
||||
account_id: account.id,
|
||||
direction: 'income',
|
||||
amount,
|
||||
amount: serviceFeeNum,
|
||||
balance_before: balanceBefore,
|
||||
balance_after: balanceAfter,
|
||||
transaction_type: '订单收入',
|
||||
transaction_type: '服务费收入',
|
||||
business_type: businessType,
|
||||
business_id: businessId,
|
||||
business_no: businessNo,
|
||||
@@ -491,12 +494,11 @@ export class AccountService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台账户扣减余额(结算/返现/提现)
|
||||
* 平台账户扣减邀请返现支出
|
||||
*/
|
||||
async deductPlatformBalance(
|
||||
amount: number,
|
||||
async deductPlatformCashback(
|
||||
cashbackAmount: number,
|
||||
transactionNo: string,
|
||||
transactionType: string,
|
||||
businessType: string,
|
||||
businessId: number,
|
||||
businessNo: string,
|
||||
@@ -516,21 +518,12 @@ export class AccountService {
|
||||
throw new NotFoundException('平台账户不存在');
|
||||
}
|
||||
|
||||
const cashbackNum = Number(cashbackAmount);
|
||||
const balanceBefore = Number(account.balance);
|
||||
if (balanceBefore < amount) {
|
||||
throw new BadRequestException('平台账户余额不足');
|
||||
}
|
||||
|
||||
const balanceAfter = balanceBefore - amount;
|
||||
const balanceAfter = parseFloat((balanceBefore - cashbackNum).toFixed(2));
|
||||
|
||||
account.balance = balanceAfter;
|
||||
account.total_expense = Number(account.total_expense) + amount;
|
||||
|
||||
if (transactionType === '商家结算') {
|
||||
} else if (transactionType === '邀请返现') {
|
||||
} else if (transactionType === '提现') {
|
||||
}
|
||||
|
||||
account.total_expense = parseFloat((Number(account.total_expense) + cashbackNum).toFixed(2));
|
||||
account.version += 1;
|
||||
|
||||
await queryRunner.manager.save(account);
|
||||
@@ -539,10 +532,73 @@ export class AccountService {
|
||||
transaction_no: transactionNo,
|
||||
account_id: account.id,
|
||||
direction: 'expense',
|
||||
amount,
|
||||
amount: cashbackNum,
|
||||
balance_before: balanceBefore,
|
||||
balance_after: balanceAfter,
|
||||
transaction_type: transactionType,
|
||||
transaction_type: '邀请返现',
|
||||
business_type: businessType,
|
||||
business_id: businessId,
|
||||
business_no: businessNo,
|
||||
remark,
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(transaction);
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台账户扣减提现
|
||||
*/
|
||||
async deductPlatformWithdrawal(
|
||||
amount: number,
|
||||
transactionNo: string,
|
||||
businessType: string,
|
||||
businessId: number,
|
||||
businessNo: string,
|
||||
remark: string,
|
||||
): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const account = await queryRunner.manager.findOne(PlatformAccount, {
|
||||
where: { account_name: '主账户' },
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new NotFoundException('平台账户不存在');
|
||||
}
|
||||
|
||||
const amountNum = Number(amount);
|
||||
const balanceBefore = Number(account.balance);
|
||||
|
||||
if (balanceBefore < amountNum) {
|
||||
throw new BadRequestException('平台账户余额不足');
|
||||
}
|
||||
|
||||
const balanceAfter = parseFloat((balanceBefore - amountNum).toFixed(2));
|
||||
|
||||
account.balance = balanceAfter;
|
||||
account.version += 1;
|
||||
|
||||
await queryRunner.manager.save(account);
|
||||
|
||||
const transaction = this.platformTransactionRepo.create({
|
||||
transaction_no: transactionNo,
|
||||
account_id: account.id,
|
||||
direction: 'expense',
|
||||
amount: amountNum,
|
||||
balance_before: balanceBefore,
|
||||
balance_after: balanceAfter,
|
||||
transaction_type: '提现',
|
||||
business_type: businessType,
|
||||
business_id: businessId,
|
||||
business_no: businessNo,
|
||||
@@ -709,4 +765,200 @@ export class AccountService {
|
||||
totalPendingSettlement: Number(result?.totalPendingSettlement || 0),
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 系统总账户操作方法
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 获取系统总账户
|
||||
*/
|
||||
async getSystemAccount(accountName: string = '主账户'): Promise<SystemAccount> {
|
||||
const account = await this.systemAccountRepo.findOne({ where: { account_name: accountName } });
|
||||
|
||||
if (!account) {
|
||||
throw new NotFoundException(
|
||||
`系统总账户"${accountName}"不存在,请先执行数据库初始化脚本创建系统总账户`
|
||||
);
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统总账户增加收入(用户支付订单)
|
||||
*/
|
||||
async addSystemIncome(
|
||||
amount: number,
|
||||
transactionNo: string,
|
||||
businessType: string,
|
||||
businessId: number,
|
||||
businessNo: string,
|
||||
remark: string,
|
||||
): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const account = await queryRunner.manager.findOne(SystemAccount, {
|
||||
where: { account_name: '主账户' },
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new NotFoundException('系统总账户不存在');
|
||||
}
|
||||
|
||||
const amountNum = Number(amount);
|
||||
const balanceBefore = Number(account.balance);
|
||||
const balanceAfter = parseFloat((balanceBefore + amountNum).toFixed(2));
|
||||
|
||||
account.balance = balanceAfter;
|
||||
account.total_income = parseFloat((Number(account.total_income) + amountNum).toFixed(2));
|
||||
account.version += 1;
|
||||
|
||||
await queryRunner.manager.save(account);
|
||||
|
||||
const transaction = this.systemTransactionRepo.create({
|
||||
transactionNo: transactionNo,
|
||||
accountId: account.id,
|
||||
direction: 'income',
|
||||
amount: amountNum,
|
||||
balanceBefore: balanceBefore,
|
||||
balanceAfter: balanceAfter,
|
||||
transactionType: '用户支付',
|
||||
businessType: businessType,
|
||||
businessId: businessId,
|
||||
businessNo: businessNo,
|
||||
remark,
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(transaction);
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统总账户增加退款支出
|
||||
*/
|
||||
async addSystemRefund(
|
||||
amount: number,
|
||||
transactionNo: string,
|
||||
businessType: string,
|
||||
businessId: number,
|
||||
businessNo: string,
|
||||
remark: string,
|
||||
): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const account = await queryRunner.manager.findOne(SystemAccount, {
|
||||
where: { account_name: '主账户' },
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new NotFoundException('系统总账户不存在');
|
||||
}
|
||||
|
||||
const amountNum = Number(amount);
|
||||
const balanceBefore = Number(account.balance);
|
||||
const balanceAfter = parseFloat((balanceBefore - amountNum).toFixed(2));
|
||||
|
||||
account.balance = balanceAfter;
|
||||
account.total_refund = parseFloat((Number(account.total_refund) + amountNum).toFixed(2));
|
||||
account.version += 1;
|
||||
|
||||
await queryRunner.manager.save(account);
|
||||
|
||||
const transaction = this.systemTransactionRepo.create({
|
||||
transactionNo: transactionNo,
|
||||
accountId: account.id,
|
||||
direction: 'expense',
|
||||
amount: amountNum,
|
||||
balanceBefore: balanceBefore,
|
||||
balanceAfter: balanceAfter,
|
||||
transactionType: '退款',
|
||||
businessType: businessType,
|
||||
businessId: businessId,
|
||||
businessNo: businessNo,
|
||||
remark,
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(transaction);
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统总账户增加提现支出
|
||||
*/
|
||||
async addSystemWithdrawal(
|
||||
amount: number,
|
||||
transactionNo: string,
|
||||
businessType: string,
|
||||
businessId: number,
|
||||
businessNo: string,
|
||||
remark: string,
|
||||
): Promise<void> {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const account = await queryRunner.manager.findOne(SystemAccount, {
|
||||
where: { account_name: '主账户' },
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new NotFoundException('系统总账户不存在');
|
||||
}
|
||||
|
||||
const amountNum = Number(amount);
|
||||
const balanceBefore = Number(account.balance);
|
||||
const balanceAfter = parseFloat((balanceBefore - amountNum).toFixed(2));
|
||||
|
||||
account.balance = balanceAfter;
|
||||
account.total_withdrawn = parseFloat((Number(account.total_withdrawn) + amountNum).toFixed(2));
|
||||
account.version += 1;
|
||||
|
||||
await queryRunner.manager.save(account);
|
||||
|
||||
const transaction = this.systemTransactionRepo.create({
|
||||
transactionNo: transactionNo,
|
||||
accountId: account.id,
|
||||
direction: 'expense',
|
||||
amount: amountNum,
|
||||
balanceBefore: balanceBefore,
|
||||
balanceAfter: balanceAfter,
|
||||
transactionType: '提现',
|
||||
businessType: businessType,
|
||||
businessId: businessId,
|
||||
businessNo: businessNo,
|
||||
remark,
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(transaction);
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ import { SettlementItem } from '@/entities/settlement-item.entity';
|
||||
import { UserWithdrawal } from '@/entities/user-withdrawal.entity';
|
||||
import { MerchantWithdrawal } from '@/entities/merchant-withdrawal.entity';
|
||||
import { PlatformWithdrawal } from '@/entities/platform-withdrawal.entity';
|
||||
import { MktInviteWithdrawal } from '@/entities/mkt-invite-withdrawal.entity';
|
||||
import { UserAccount } from '@/entities/user-account.entity';
|
||||
import { MerchantAccount } from '@/entities/merchant-account.entity';
|
||||
import { PlatformAccount } from '@/entities/platform-account.entity';
|
||||
import { SystemAccount } from '@/entities/system-account.entity';
|
||||
import { UserTransaction } from '@/entities/user-transaction.entity';
|
||||
import { MerchantTransaction } from '@/entities/merchant-transaction.entity';
|
||||
import { PlatformTransaction } from '@/entities/platform-transaction.entity';
|
||||
import { SystemTransaction } from '@/entities/system-transaction.entity';
|
||||
import { DailyReconciliation } from '@/entities/daily-reconciliation.entity';
|
||||
import { PlatformConfig } from '@/entities/platform-config.entity';
|
||||
import { Merchant } from '@/entities/merchant.entity';
|
||||
@@ -33,12 +36,15 @@ import { MerchantModule } from '@/modules/merchant/merchant.module';
|
||||
UserWithdrawal,
|
||||
MerchantWithdrawal,
|
||||
PlatformWithdrawal,
|
||||
MktInviteWithdrawal,
|
||||
UserAccount,
|
||||
MerchantAccount,
|
||||
PlatformAccount,
|
||||
SystemAccount,
|
||||
UserTransaction,
|
||||
MerchantTransaction,
|
||||
PlatformTransaction,
|
||||
SystemTransaction,
|
||||
DailyReconciliation,
|
||||
PlatformConfig,
|
||||
Merchant,
|
||||
|
||||
@@ -3,8 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository, DataSource } from 'typeorm';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Order } from '@/entities/order.entity';
|
||||
import { PlatformAccount } from '@/entities/platform-account.entity';
|
||||
import { PlatformTransaction } from '@/entities/platform-transaction.entity';
|
||||
import { AccountService } from './account.service';
|
||||
import { TransactionService } from './transaction.service';
|
||||
import Wechatpay = require('wechatpay-node-v3');
|
||||
|
||||
@Injectable()
|
||||
@@ -14,10 +14,8 @@ export class RefundService {
|
||||
constructor(
|
||||
@InjectRepository(Order)
|
||||
private orderRepo: Repository<Order>,
|
||||
@InjectRepository(PlatformAccount)
|
||||
private platformAccountRepo: Repository<PlatformAccount>,
|
||||
@InjectRepository(PlatformTransaction)
|
||||
private platformTransactionRepo: Repository<PlatformTransaction>,
|
||||
private accountService: AccountService,
|
||||
private transactionService: TransactionService,
|
||||
private dataSource: DataSource,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
@@ -179,7 +177,7 @@ export class RefundService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 平台账户记录退款支出(仅用于记账)
|
||||
* 记录系统总账户退款
|
||||
* @param order 订单信息
|
||||
* @param queryRunner 事务查询器
|
||||
*/
|
||||
@@ -187,34 +185,17 @@ export class RefundService {
|
||||
order: Order,
|
||||
queryRunner: any,
|
||||
): Promise<void> {
|
||||
// 获取平台主账户
|
||||
const platformAccount = await queryRunner.manager.findOne(PlatformAccount, {
|
||||
where: { accountType: 'main' },
|
||||
});
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
|
||||
if (!platformAccount) {
|
||||
throw new BadRequestException('平台账户不存在');
|
||||
}
|
||||
|
||||
// 创建退款支出交易记录(仅记账,实际退款通过第三方支付)
|
||||
const transaction = queryRunner.manager.create(PlatformTransaction, {
|
||||
accountId: platformAccount.id,
|
||||
type: 'refund_expense',
|
||||
amount: order.payAmount,
|
||||
balance: platformAccount.balance, // 余额不变,仅记录
|
||||
relatedType: 'order',
|
||||
relatedId: order.id,
|
||||
description: `订单退款 - ${order.orderNo}`,
|
||||
metadata: {
|
||||
orderId: order.id,
|
||||
orderNo: order.orderNo,
|
||||
userId: order.userId,
|
||||
paymentMethod: order.paymentMethod,
|
||||
refundAmount: order.payAmount,
|
||||
},
|
||||
});
|
||||
|
||||
await queryRunner.manager.save(transaction);
|
||||
// 记录系统总账户退款
|
||||
await this.accountService.addSystemRefund(
|
||||
order.payAmount,
|
||||
transactionNo,
|
||||
'order_refund',
|
||||
order.id,
|
||||
order.orderNo,
|
||||
`订单退款 - ${order.orderNo}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SystemAccount } from '@/entities/system-account.entity';
|
||||
import { PlatformAccount } from '@/entities/platform-account.entity';
|
||||
import { UserAccount } from '@/entities/user-account.entity';
|
||||
import { MerchantAccount } from '@/entities/merchant-account.entity';
|
||||
import { PlatformTransaction } from '@/entities/platform-transaction.entity';
|
||||
import { Order } from '@/entities/order.entity';
|
||||
import { Settlement } from '@/entities/settlement.entity';
|
||||
import { MerchantWithdrawal } from '@/entities/merchant-withdrawal.entity';
|
||||
import { UserWithdrawal } from '@/entities/user-withdrawal.entity';
|
||||
import { PlatformWithdrawal } from '@/entities/platform-withdrawal.entity';
|
||||
import { MktInviteWithdrawal } from '@/entities/mkt-invite-withdrawal.entity';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@Injectable()
|
||||
export class ReportService {
|
||||
constructor(
|
||||
@InjectRepository(SystemAccount)
|
||||
private systemAccountRepo: Repository<SystemAccount>,
|
||||
@InjectRepository(PlatformAccount)
|
||||
private platformAccountRepo: Repository<PlatformAccount>,
|
||||
@InjectRepository(UserAccount)
|
||||
@@ -24,12 +31,26 @@ export class ReportService {
|
||||
private orderRepo: Repository<Order>,
|
||||
@InjectRepository(Settlement)
|
||||
private settlementRepo: Repository<Settlement>,
|
||||
@InjectRepository(MerchantWithdrawal)
|
||||
private merchantWithdrawalRepo: Repository<MerchantWithdrawal>,
|
||||
@InjectRepository(UserWithdrawal)
|
||||
private userWithdrawalRepo: Repository<UserWithdrawal>,
|
||||
@InjectRepository(PlatformWithdrawal)
|
||||
private platformWithdrawalRepo: Repository<PlatformWithdrawal>,
|
||||
@InjectRepository(MktInviteWithdrawal)
|
||||
private mktInviteWithdrawalRepo: Repository<MktInviteWithdrawal>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 财务总览
|
||||
*/
|
||||
async getOverview() {
|
||||
// 获取系统总账户
|
||||
const systemAccount = await this.systemAccountRepo.findOne({
|
||||
where: { account_name: '系统总账户' },
|
||||
});
|
||||
|
||||
// 获取平台账户
|
||||
const platformAccount = await this.platformAccountRepo.findOne({
|
||||
where: { account_name: '主账户' },
|
||||
});
|
||||
@@ -68,11 +89,26 @@ export class ReportService {
|
||||
.getRawOne();
|
||||
|
||||
return {
|
||||
// 系统总账户金额(用户实付 - 退款 - 提现)
|
||||
systemTotalAmount: Number(systemAccount?.balance || 0),
|
||||
totalUserPaid: Number(systemAccount?.total_income || 0),
|
||||
totalRefund: Number(systemAccount?.total_refund || 0),
|
||||
totalWithdrawn: Number(systemAccount?.total_withdrawn || 0),
|
||||
|
||||
// 平台净收益(服务费 - 邀请返现)
|
||||
platformBalance: Number(platformAccount?.balance || 0),
|
||||
merchantTotalBalance: Number(merchantStats?.totalBalance || 0),
|
||||
platformTotalIncome: Number(platformAccount?.total_income || 0),
|
||||
platformTotalExpense: Number(platformAccount?.total_expense || 0),
|
||||
|
||||
// 商家账户统计
|
||||
totalMerchantBalance: Number(merchantStats?.totalBalance || 0),
|
||||
merchantCount: Number(merchantStats?.count || 0),
|
||||
userTotalBalance: Number(userStats?.totalBalance || 0),
|
||||
|
||||
// 用户账户统计
|
||||
totalUserBalance: Number(userStats?.totalBalance || 0),
|
||||
userCount: Number(userStats?.count || 0),
|
||||
|
||||
// 今日统计
|
||||
todayOrders,
|
||||
todayIncome: Number(todayIncome?.sum || 0),
|
||||
todayExpense: Number(todayExpense?.sum || 0),
|
||||
|
||||
@@ -167,6 +167,7 @@ export class SettlementService {
|
||||
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
|
||||
// 给商家账户增加余额(订单金额 - 服务费)
|
||||
await this.accountService.addMerchantBalance(
|
||||
merchantId,
|
||||
settlementAmount,
|
||||
@@ -177,14 +178,14 @@ export class SettlementService {
|
||||
`商家周结算:${periodStart} ~ ${periodEnd}`
|
||||
);
|
||||
|
||||
await this.accountService.deductPlatformBalance(
|
||||
settlementAmount,
|
||||
// 给平台账户增加服务费收入
|
||||
await this.accountService.addPlatformServiceFee(
|
||||
serviceFee,
|
||||
transactionNo,
|
||||
'商家结算',
|
||||
'settlement',
|
||||
settlement.id,
|
||||
settlementNo,
|
||||
`商家 ${merchantId} 周结算:${periodStart} ~ ${periodEnd}`
|
||||
`商家 ${merchantId} 周结算服务费:${periodStart} ~ ${periodEnd}`
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
@@ -155,10 +155,10 @@ export class WithdrawalService {
|
||||
|
||||
const account = await this.accountService.getPlatformAccount();
|
||||
|
||||
// 计算可提现金额 = 累计服务费 - 冻结余额
|
||||
// 注意:这里使用 total_service_fee 是为了确保只提现平台收入
|
||||
// 计算可提现金额 = 累计收入 - 冻结余额
|
||||
// 注意:这里使用 total_income 是为了确保只提现平台收入
|
||||
// 未来如果有其他收入(广告费、会员费等),需要累加到可提现金额中
|
||||
const availableAmount = Number(account.total_service_fee) - Number(account.frozen_balance);
|
||||
const availableAmount = Number(account.total_income) - Number(account.frozen_balance);
|
||||
|
||||
if (availableAmount < amount) {
|
||||
throw new BadRequestException(`可提现金额不足,当前可提现:${availableAmount.toFixed(2)}元`);
|
||||
@@ -294,6 +294,7 @@ export class WithdrawalService {
|
||||
try {
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
|
||||
// 扣减用户账户余额
|
||||
await this.accountService.deductUserBalance(
|
||||
withdrawal.userId,
|
||||
Number(withdrawal.actualAmount),
|
||||
@@ -304,6 +305,16 @@ export class WithdrawalService {
|
||||
`用户提现 - ${withdrawal.paymentChannel}`
|
||||
);
|
||||
|
||||
// 记录系统总账户提现
|
||||
await this.accountService.addSystemWithdrawal(
|
||||
Number(withdrawal.actualAmount),
|
||||
transactionNo,
|
||||
'user_withdraw',
|
||||
withdrawal.id,
|
||||
withdrawal.withdrawNo,
|
||||
`用户 ${withdrawal.userId} 提现 - ${withdrawal.paymentChannel}`,
|
||||
);
|
||||
|
||||
await queryRunner.manager.update(UserWithdrawal, id, {
|
||||
status: 'paid',
|
||||
paymentNo,
|
||||
@@ -415,6 +426,7 @@ export class WithdrawalService {
|
||||
try {
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
|
||||
// 扣减商家账户余额
|
||||
await this.accountService.deductMerchantBalance(
|
||||
withdrawal.merchantId,
|
||||
Number(withdrawal.actualAmount),
|
||||
@@ -425,6 +437,16 @@ export class WithdrawalService {
|
||||
`商家提现 - ${withdrawal.bankName}`
|
||||
);
|
||||
|
||||
// 记录系统总账户提现
|
||||
await this.accountService.addSystemWithdrawal(
|
||||
Number(withdrawal.actualAmount),
|
||||
transactionNo,
|
||||
'merchant_withdraw',
|
||||
withdrawal.id,
|
||||
withdrawal.withdrawNo,
|
||||
`商家 ${withdrawal.merchantId} 提现 - ${withdrawal.bankName}`,
|
||||
);
|
||||
|
||||
await queryRunner.manager.update(MerchantWithdrawal, id, {
|
||||
status: 'paid',
|
||||
paymentNo,
|
||||
@@ -536,10 +558,9 @@ export class WithdrawalService {
|
||||
try {
|
||||
const transactionNo = this.transactionService.generateTransactionNo();
|
||||
|
||||
await this.accountService.deductPlatformBalance(
|
||||
await this.accountService.deductPlatformWithdrawal(
|
||||
Number(withdrawal.actualAmount),
|
||||
transactionNo,
|
||||
'提现',
|
||||
'withdraw',
|
||||
withdrawal.id,
|
||||
withdrawal.withdrawNo,
|
||||
|
||||
@@ -645,19 +645,14 @@ CREATE TABLE IF NOT EXISTS `merchant_accounts` (
|
||||
KEY `idx_last_settlement_at` (`last_settlement_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商家账户表';
|
||||
|
||||
-- 3. 平台账户表
|
||||
CREATE TABLE IF NOT EXISTS `platform_accounts` (
|
||||
-- 3. 系统总账户表(记录整个系统的资金流入流出)
|
||||
CREATE TABLE IF NOT EXISTS `system_accounts` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '账户ID',
|
||||
`account_name` VARCHAR(50) NOT NULL COMMENT '账户名称(如:主账户、备用账户)',
|
||||
`balance` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '可用余额',
|
||||
`frozen_balance` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额(提现中)',
|
||||
`total_income` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计收入(订单收入)',
|
||||
`total_expense` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计支出(结算+返现+提现)',
|
||||
`total_order_income` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计订单收入',
|
||||
`total_service_fee` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计服务费收入',
|
||||
`total_settlement` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计商家结算支出',
|
||||
`total_cashback` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计返现支出',
|
||||
`total_withdraw` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计提现金额',
|
||||
`account_name` VARCHAR(50) NOT NULL COMMENT '账户名称(如:主账户)',
|
||||
`balance` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '可用余额(total_income - total_refund - total_withdrawn)',
|
||||
`total_income` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计收入(用户实付总额)',
|
||||
`total_refund` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计退款',
|
||||
`total_withdrawn` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计提现(所有提现)',
|
||||
`version` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
`status` ENUM('active','frozen','closed') NOT NULL DEFAULT 'active' COMMENT '账户状态',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
@@ -665,13 +660,53 @@ CREATE TABLE IF NOT EXISTS `platform_accounts` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_account_name` (`account_name`),
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='平台账户表';
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统总账户表(资金守恒验证:balance应等于所有用户账户+商家账户+平台账户余额之和)';
|
||||
|
||||
-- 4. 平台账户表(记录平台净收益)
|
||||
CREATE TABLE IF NOT EXISTS `platform_accounts` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '账户ID',
|
||||
`account_name` VARCHAR(50) NOT NULL COMMENT '账户名称(如:主账户、备用账户)',
|
||||
`balance` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '可用余额(平台净收益 = total_income - total_expense)',
|
||||
`frozen_balance` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额(提现中)',
|
||||
`total_income` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计收入(服务费收入)',
|
||||
`total_expense` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计支出(邀请返现支出)',
|
||||
`version` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
`status` ENUM('active','frozen','closed') NOT NULL DEFAULT 'active' COMMENT '账户状态',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_account_name` (`account_name`),
|
||||
KEY `idx_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='平台账户表(平台净收益 = 服务费 - 邀请返现)';
|
||||
|
||||
-- ============================================================
|
||||
-- 第二部分:交易流水表(按角色拆分)
|
||||
-- ============================================================
|
||||
|
||||
-- 4. 用户交易流水表
|
||||
-- 4. 系统总账户交易流水表
|
||||
CREATE TABLE IF NOT EXISTS `system_transactions` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '流水ID',
|
||||
`transaction_no` VARCHAR(32) NOT NULL COMMENT '交易流水号(全局唯一)',
|
||||
`account_id` BIGINT UNSIGNED NOT NULL COMMENT '系统账户ID',
|
||||
`direction` ENUM('income','expense') NOT NULL COMMENT '方向:income-收入/expense-支出',
|
||||
`amount` DECIMAL(12,2) NOT NULL COMMENT '金额(正数)',
|
||||
`balance_before` DECIMAL(12,2) NOT NULL COMMENT '交易前余额',
|
||||
`balance_after` DECIMAL(12,2) NOT NULL COMMENT '交易后余额',
|
||||
`transaction_type` VARCHAR(50) NOT NULL COMMENT '交易类型',
|
||||
`business_type` VARCHAR(50) NOT NULL COMMENT '业务类型:order_payment/refund/withdraw',
|
||||
`business_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '业务ID(订单ID/退款ID/提现ID等)',
|
||||
`business_no` VARCHAR(32) DEFAULT NULL COMMENT '业务单号',
|
||||
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_transaction_no` (`transaction_no`),
|
||||
KEY `idx_account_id` (`account_id`),
|
||||
KEY `idx_transaction_type` (`transaction_type`),
|
||||
KEY `idx_business` (`business_type`, `business_id`),
|
||||
KEY `idx_created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统总账户交易流水表';
|
||||
|
||||
-- 5. 用户交易流水表
|
||||
CREATE TABLE IF NOT EXISTS `user_transactions` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '流水ID',
|
||||
`transaction_no` VARCHAR(32) NOT NULL COMMENT '交易流水号(全局唯一)',
|
||||
@@ -698,7 +733,7 @@ CREATE TABLE IF NOT EXISTS `user_transactions` (
|
||||
KEY `idx_created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户交易流水表';
|
||||
|
||||
-- 5. 商家交易流水表
|
||||
-- 6. 商家交易流水表
|
||||
CREATE TABLE IF NOT EXISTS `merchant_transactions` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '流水ID',
|
||||
`transaction_no` VARCHAR(32) NOT NULL COMMENT '交易流水号(全局唯一)',
|
||||
@@ -725,7 +760,7 @@ CREATE TABLE IF NOT EXISTS `merchant_transactions` (
|
||||
KEY `idx_created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商家交易流水表';
|
||||
|
||||
-- 6. 平台交易流水表
|
||||
-- 7. 平台交易流水表
|
||||
CREATE TABLE IF NOT EXISTS `platform_transactions` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '流水ID',
|
||||
`transaction_no` VARCHAR(32) NOT NULL COMMENT '交易流水号(全局唯一)',
|
||||
@@ -735,7 +770,7 @@ CREATE TABLE IF NOT EXISTS `platform_transactions` (
|
||||
`balance_before` DECIMAL(12,2) NOT NULL COMMENT '交易前余额',
|
||||
`balance_after` DECIMAL(12,2) NOT NULL COMMENT '交易后余额',
|
||||
`transaction_type` VARCHAR(50) NOT NULL COMMENT '交易类型',
|
||||
`business_type` VARCHAR(50) NOT NULL COMMENT '业务类型:order/settlement/cashback/withdraw/refund',
|
||||
`business_type` VARCHAR(50) NOT NULL COMMENT '业务类型:service_fee/cashback/withdraw',
|
||||
`business_id` BIGINT UNSIGNED DEFAULT NULL COMMENT '业务ID',
|
||||
`business_no` VARCHAR(32) DEFAULT NULL COMMENT '业务单号',
|
||||
`related_account_type` ENUM('user','merchant') DEFAULT NULL COMMENT '对方账户类型',
|
||||
|
||||
+52
-32
@@ -17,9 +17,9 @@
|
||||
### 配置指南
|
||||
- 💳 [微信支付配置](WECHAT_PAYMENT_SETUP.md) - 微信支付接入指南
|
||||
|
||||
### 功能模块
|
||||
### 核心系统设计
|
||||
- 💰 [财务系统完整设计](finance-system.md) - 账户体系、资金流转、核心服务、安全保障(v2.0 重构版)
|
||||
- 🎁 [邀请码系统](features/invite-system.md) - 邀请码生成、绑定和返现机制
|
||||
- 💰 [结算系统](features/settlement-system.md) - 订单支付、资金流转、周结算和商家提现
|
||||
|
||||
---
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
```
|
||||
docs/
|
||||
├── README.md # 本文件 - 文档索引
|
||||
├── finance-system.md # 财务系统完整设计文档(v2.0)
|
||||
├── DEVELOPMENT_SUMMARY.md # 开发总结(项目完成情况)
|
||||
├── WECHAT_PAYMENT_SETUP.md # 微信支付配置指南
|
||||
├── requirements/
|
||||
@@ -35,17 +36,33 @@ docs/
|
||||
├── planning/
|
||||
│ └── TODO2.md # 任务清单(已完成100%)
|
||||
├── database/
|
||||
│ └── finance-database.md # 财务数据库设计
|
||||
│ └── finance-database.md # 财务数据库设计(v2.0)
|
||||
└── features/
|
||||
├── invite-system.md # 邀请码系统设计文档
|
||||
└── settlement-system.md # 结算系统设计文档
|
||||
└── invite-system.md # 邀请码系统设计文档
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 核心文档说明
|
||||
### 1. [财务系统完整设计](features/finance-system.md) - v3.0 合并版
|
||||
**内容**:
|
||||
- 四层账户体系设计(系统总账户、平台账户、商家账户、用户账户)
|
||||
- 完整的资金流转流程(订单支付、结算、退款、提现)
|
||||
- 结算周期和服务费计算
|
||||
- 核心服务详解(AccountService、SettlementService、RefundService、WithdrawalService、ReportService)
|
||||
- 数据库表结构和SQL定义
|
||||
- API接口文档
|
||||
- 前端页面实现(5个页面)
|
||||
- 扩展方案(未来增加其他收入)
|
||||
- 安全保障机制(事务、悲观锁、乐观锁、金额精度控制)
|
||||
- 数据一致性保证
|
||||
- 测试验证和部署说明
|
||||
- 常见问题解答
|
||||
|
||||
### 1. [项目需求文档](requirements/项目需求文档.md)
|
||||
**适合**:后端开发人员、架构师、财务人员、测试人员
|
||||
|
||||
---
|
||||
|
||||
### 2. [项目需求文档](requirements/项目需求文档.md)
|
||||
**内容**:
|
||||
- 项目概述和技术栈
|
||||
- 系统架构设计
|
||||
@@ -58,18 +75,19 @@ docs/
|
||||
|
||||
---
|
||||
|
||||
### 2. [财务数据库设计](database/finance-database.md)
|
||||
### 3. [财务数据库设计](database/finance-database.md) - v2.0 更新
|
||||
**内容**:
|
||||
- 完整的数据库表结构
|
||||
- 账户体系设计(用户、商家、平台)
|
||||
- 资金流转逻辑
|
||||
- 财务交易记录
|
||||
- 完整的数据库表结构(v2.0 重构版)
|
||||
- 四层账户体系(system_accounts、platform_accounts、merchant_accounts、user_accounts)
|
||||
- 资金流转逻辑和业务流程
|
||||
- 数据一致性保证机制
|
||||
- 索引说明和版本更新记录
|
||||
|
||||
**适合**:后端开发人员、数据库管理员
|
||||
|
||||
---
|
||||
|
||||
### 3. [任务清单 TODO2.md](planning/TODO2.md)
|
||||
### 4. [任务清单 TODO2.md](planning/TODO2.md)
|
||||
**内容**:
|
||||
- 项目开发任务分解
|
||||
- 各阶段完成情况(100%)
|
||||
@@ -80,7 +98,7 @@ docs/
|
||||
|
||||
---
|
||||
|
||||
### 4. [开发总结](DEVELOPMENT_SUMMARY.md)
|
||||
### 5. [开发总结](DEVELOPMENT_SUMMARY.md)
|
||||
**内容**:
|
||||
- 项目整体完成情况
|
||||
- 已实现的功能模块
|
||||
@@ -91,7 +109,7 @@ docs/
|
||||
|
||||
---
|
||||
|
||||
### 5. [微信支付配置](WECHAT_PAYMENT_SETUP.md)
|
||||
### 6. [微信支付配置](WECHAT_PAYMENT_SETUP.md)
|
||||
**内容**:
|
||||
- 微信支付商户配置
|
||||
- API密钥设置
|
||||
@@ -102,7 +120,7 @@ docs/
|
||||
|
||||
---
|
||||
|
||||
### 6. [邀请码系统](features/invite-system.md)
|
||||
### 7. [邀请码系统](features/invite-system.md)
|
||||
**内容**:
|
||||
- 邀请码生成规则(6位数字+大写字母)
|
||||
- 邀请关系绑定流程
|
||||
@@ -114,37 +132,38 @@ docs/
|
||||
|
||||
---
|
||||
|
||||
### 7. [结算系统](features/settlement-system.md)
|
||||
**内容**:
|
||||
- 完整的资金流转流程
|
||||
- 周结算机制(每周一凌晨2点自动执行)
|
||||
- 服务费计算规则和配置
|
||||
- 订单支付、商家结算、提现流程
|
||||
- 数据库设计和API接口
|
||||
- 问题修复记录
|
||||
|
||||
**适合**:产品经理、后端开发人员、财务人员
|
||||
|
||||
---
|
||||
## 🗄️ 数据库初始化
|
||||
|
||||
### 初始化脚本
|
||||
位置:`database/migrations/001_init_schema.sql`
|
||||
|
||||
**包含内容**:
|
||||
- 所有数据库表结构
|
||||
- 所有数据库表结构(v2.0 重构版)
|
||||
- 系统总账户表(system_accounts)
|
||||
- 平台账户表(platform_accounts)
|
||||
- 商家账户表(merchant_accounts)
|
||||
- 用户账户表(user_accounts)
|
||||
- 各类交易流水表
|
||||
- 索引和外键约束
|
||||
- 初始化数据
|
||||
- 常住人表(guests)
|
||||
- 实名认证字段
|
||||
- 触发器
|
||||
|
||||
### 执行方式
|
||||
```bash
|
||||
# 方式1:命令行执行
|
||||
# 方式1:命令行执行(会清空所有数据)
|
||||
mysql -u root -p < database/migrations/001_init_schema.sql
|
||||
|
||||
# 方式2:登录后执行
|
||||
mysql -u root -p
|
||||
source database/migrations/001_init_schema.sql;
|
||||
|
||||
# 方式3:初始化系统总账户和平台账户
|
||||
USE your_database_name;
|
||||
INSERT INTO system_accounts (account_name, balance, total_income, total_refund, total_withdrawn)
|
||||
VALUES ('系统总账户', 0.00, 0.00, 0.00, 0.00);
|
||||
|
||||
INSERT INTO platform_accounts (account_name, balance, total_income, total_expense)
|
||||
VALUES ('主账户', 0.00, 0.00, 0.00);
|
||||
```
|
||||
|
||||
---
|
||||
@@ -196,6 +215,7 @@ source database/migrations/001_init_schema.sql;
|
||||
|
||||
| 日期 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| 2024-01-XX | v5.0 | 财务系统账户体系重构(v2.0),新增系统总账户,重构平台账户 |
|
||||
| 2026-05-13 | v4.2 | 新增结算系统设计文档,修复结算逻辑问题 |
|
||||
| 2026-05-13 | v4.1 | 新增邀请码系统设计文档 |
|
||||
| 2026-05-13 | v4.0 | 整合文档,删除重复内容,项目完成100% |
|
||||
|
||||
+452
-146
@@ -1,43 +1,175 @@
|
||||
# 财务系统数据库表结构说明
|
||||
# 财务系统数据库设计文档
|
||||
|
||||
> **最后更新**: 2024-01-XX - 账户体系重构完成
|
||||
> **版本**: v2.0
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了租房平台财务系统的数据库设计,包括账户体系、资金流转和数据一致性保障机制。
|
||||
|
||||
## 数据库表与 Entity 映射关系
|
||||
|
||||
| 数据库表 | Entity 文件 | 说明 | 状态 |
|
||||
|---------|------------|------|------|
|
||||
| `user_accounts` | `user-account.entity.ts` | 用户账户表 | ✅ 已完成 |
|
||||
| `merchant_accounts` | `merchant-account.entity.ts` | 商家账户表 | ✅ 已完成 |
|
||||
| `platform_accounts` | `platform-account.entity.ts` | 平台账户表 | ✅ 已完成 |
|
||||
| `user_transactions` | `user-transaction.entity.ts` | 用户交易流水表 | ✅ 已完成 |
|
||||
| `merchant_transactions` | `merchant-transaction.entity.ts` | 商家交易流水表 | ✅ 已完成 |
|
||||
| `system_accounts` | `system-account.entity.ts` | 系统总账户表 | ✅ v2.0 新增 |
|
||||
| `system_transactions` | `system-transaction.entity.ts` | 系统总账户交易流水表 | ✅ v2.0 新增 |
|
||||
| `platform_accounts` | `platform-account.entity.ts` | 平台账户表 | ✅ v2.0 重构 |
|
||||
| `platform_transactions` | `platform-transaction.entity.ts` | 平台交易流水表 | ✅ 已完成 |
|
||||
| `merchant_accounts` | `merchant-account.entity.ts` | 商家账户表 | ✅ 已完成 |
|
||||
| `merchant_transactions` | `merchant-transaction.entity.ts` | 商家交易流水表 | ✅ 已完成 |
|
||||
| `user_accounts` | `user-account.entity.ts` | 用户账户表 | ✅ 已完成 |
|
||||
| `user_transactions` | `user-transaction.entity.ts` | 用户交易流水表 | ✅ 已完成 |
|
||||
| `user_withdrawals` | `user-withdrawal.entity.ts` | 用户提现表 | ✅ 已完成 |
|
||||
| `merchant_withdrawals` | `merchant-withdrawal.entity.ts` | 商家提现表 | ✅ 已完成 |
|
||||
| `platform_withdrawals` | `platform-withdrawal.entity.ts` | 平台提现表 | ✅ 已完成 |
|
||||
| `settlements` | `settlement.entity.ts` | 结算单表 | ✅ 已完成 |
|
||||
| `settlement_items` | `settlement-item.entity.ts` | 结算明细表 | ✅ 已完成 |
|
||||
| `daily_reconciliations` | `daily-reconciliation.entity.ts` | 日对账表 | ✅ 已完成 |
|
||||
|
||||
## 数据库初始化说明
|
||||
## 账户体系设计
|
||||
|
||||
### 1. 迁移脚本位置
|
||||
### 四层账户结构
|
||||
|
||||
`database/migrations/001_init_schema.sql` 已包含完整的财务系统表结构(第 606-901 行)
|
||||
系统采用四层账户结构,确保资金流转清晰、职责明确:
|
||||
|
||||
### 2. 自动创建的数据
|
||||
#### 1. 系统总账户 (System Account) - v2.0 新增
|
||||
|
||||
执行迁移脚本后会自动创建:
|
||||
**职责**: 记录平台内所有未提现的资金总额
|
||||
|
||||
1. **触发器**
|
||||
- `trg_user_create_account`: 用户注册时自动创建用户账户
|
||||
- `trg_merchant_create_account`: 商家审核通过时自动创建商家账户
|
||||
**计算公式**:
|
||||
```
|
||||
系统总账户余额 = 用户实付总额 - 累计退款 - 累计提现
|
||||
```
|
||||
|
||||
### 3. 表结构版本说明
|
||||
**字段说明**:
|
||||
- `balance`: 当前余额
|
||||
- `total_income`: 累计收入(用户实付总额)
|
||||
- `total_refund`: 累计退款
|
||||
- `total_withdrawn`: 累计提现
|
||||
|
||||
⚠️ **重要**: 旧版本的 `settlements` 和 `withdrawals` 表定义已删除。
|
||||
#### 2. 平台账户 (Platform Account) - v2.0 重构
|
||||
|
||||
**当前使用**: 按角色分离的账户和交易表(user_accounts、merchant_accounts、platform_accounts 等)
|
||||
**职责**: 记录平台净收益(服务费收入 - 邀请返现支出)
|
||||
|
||||
## 字段说明
|
||||
**计算公式**:
|
||||
```
|
||||
平台账户余额 = 累计服务费收入 - 累计邀请返现支出
|
||||
```
|
||||
|
||||
**字段说明**:
|
||||
- `balance`: 当前余额
|
||||
- `total_income`: 累计收入(服务费收入)
|
||||
- `total_expense`: 累计支出(邀请返现支出)
|
||||
|
||||
**v2.0 变更**:
|
||||
- ❌ 删除字段: `total_order_income`, `total_service_fee`, `total_cashback`, `total_settlement`
|
||||
- ✅ 字段含义变更: `total_income` 从"订单收入"改为"服务费收入"
|
||||
- ✅ 字段含义变更: `total_expense` 从"所有支出"改为"邀请返现支出"
|
||||
|
||||
#### 3. 商家账户 (Merchant Account)
|
||||
|
||||
**职责**: 记录商家的订单收入和提现
|
||||
|
||||
**计算公式**:
|
||||
```
|
||||
商家账户余额 = 累计订单收入 - 累计提现
|
||||
订单收入 = 订单金额 - 服务费
|
||||
```
|
||||
|
||||
#### 4. 用户账户 (User Account)
|
||||
|
||||
**职责**: 记录用户的邀请返现收入和提现
|
||||
|
||||
**计算公式**:
|
||||
```
|
||||
用户账户余额 = 累计邀请返现 - 累计提现
|
||||
```
|
||||
|
||||
### 资金守恒验证
|
||||
|
||||
系统通过以下公式验证资金守恒:
|
||||
|
||||
```
|
||||
系统总账户余额 = 商家账户余额总和 + 用户账户余额总和 + 平台账户余额
|
||||
```
|
||||
|
||||
这个公式确保平台内所有资金都有明确的归属,不会出现资金丢失或凭空产生的情况。
|
||||
|
||||
## 数据表结构详解
|
||||
|
||||
### system_accounts(系统总账户表)- v2.0 新增
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS `system_accounts` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`account_name` VARCHAR(50) NOT NULL COMMENT '账户名称',
|
||||
`balance` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '可用余额',
|
||||
`total_income` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计收入(用户实付总额)',
|
||||
`total_refund` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计退款',
|
||||
`total_withdrawn` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计提现',
|
||||
`version` INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_account_name` (`account_name`)
|
||||
) COMMENT='系统总账户表';
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- 记录平台内所有未提现的资金
|
||||
- `balance` = `total_income` - `total_refund` - `total_withdrawn`
|
||||
- 用于验证资金守恒
|
||||
|
||||
### system_transactions(系统总账户交易流水表)- v2.0 新增
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS `system_transactions` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`transaction_no` VARCHAR(64) NOT NULL COMMENT '交易流水号',
|
||||
`account_id` BIGINT UNSIGNED NOT NULL COMMENT '账户ID',
|
||||
`direction` ENUM('income','expense') NOT NULL COMMENT '交易方向',
|
||||
`amount` DECIMAL(12,2) NOT NULL COMMENT '交易金额',
|
||||
`balance_before` DECIMAL(12,2) NOT NULL COMMENT '交易前余额',
|
||||
`balance_after` DECIMAL(12,2) NOT NULL COMMENT '交易后余额',
|
||||
`transaction_type` VARCHAR(50) NOT NULL COMMENT '交易类型',
|
||||
`business_type` VARCHAR(50) NOT NULL COMMENT '业务类型',
|
||||
`business_id` BIGINT UNSIGNED NOT NULL COMMENT '业务ID',
|
||||
`business_no` VARCHAR(64) NOT NULL COMMENT '业务单号',
|
||||
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_transaction_no` (`transaction_no`),
|
||||
KEY `idx_account_id` (`account_id`),
|
||||
KEY `idx_business` (`business_type`, `business_id`),
|
||||
KEY `idx_created_at` (`created_at`)
|
||||
) COMMENT='系统总账户交易流水表';
|
||||
```
|
||||
|
||||
**交易类型**:
|
||||
- `用户支付`: 用户支付订单(income)
|
||||
- `订单退款`: 订单退款(expense)
|
||||
- `用户提现`: 用户提现(expense)
|
||||
- `商家提现`: 商家提现(expense)
|
||||
|
||||
### platform_accounts(平台账户表)- v2.0 重构
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS `platform_accounts` (
|
||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`account_name` VARCHAR(50) NOT NULL COMMENT '账户名称',
|
||||
`balance` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '可用余额',
|
||||
`total_income` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计收入(服务费收入)',
|
||||
`total_expense` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计支出(邀请返现支出)',
|
||||
`version` INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_account_name` (`account_name`)
|
||||
) COMMENT='平台账户表';
|
||||
```
|
||||
|
||||
**v2.0 变更**:
|
||||
- `total_income` 含义变更: 从"订单收入"改为"服务费收入"
|
||||
- `total_expense` 含义变更: 从"所有支出"改为"邀请返现支出"
|
||||
- 删除冗余字段: `total_order_income`, `total_service_fee`, `total_cashback`, `total_settlement`
|
||||
|
||||
### user_accounts(用户账户表)
|
||||
|
||||
@@ -56,6 +188,11 @@ CREATE TABLE `user_accounts` (
|
||||
);
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- 用户账户主要用于邀请返现
|
||||
- `balance` = `total_income` - `total_expense`
|
||||
- 提现时先冻结余额,打款后扣减
|
||||
|
||||
### merchant_accounts(商家账户表)
|
||||
|
||||
```sql
|
||||
@@ -78,6 +215,121 @@ CREATE TABLE `merchant_accounts` (
|
||||
);
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- 商家账户记录订单结算收入
|
||||
- 结算金额 = 订单金额 - 服务费
|
||||
- 每周一凌晨2点自动执行周结算
|
||||
|
||||
## 资金流转流程
|
||||
|
||||
### 1. 订单支付流程 - v2.0 更新
|
||||
|
||||
```
|
||||
用户支付订单
|
||||
↓
|
||||
系统总账户 +实付金额 (income)
|
||||
↓
|
||||
订单状态:pending_pay → pending_confirm
|
||||
```
|
||||
|
||||
**涉及表**:
|
||||
- `system_accounts`: balance += 实付金额, total_income += 实付金额
|
||||
- `system_transactions`: 插入交易流水(income)
|
||||
|
||||
**代码位置**: `apps/server/src/modules/app/order/order.service.ts`
|
||||
|
||||
### 2. 订单结算流程 - v2.0 更新
|
||||
|
||||
```
|
||||
订单完成(用户离店)
|
||||
↓
|
||||
商家账户 +(实付金额 - 服务费)
|
||||
↓
|
||||
平台账户 +服务费
|
||||
↓
|
||||
如果有邀请关系:
|
||||
用户账户(邀请人)+返现金额
|
||||
平台账户 -返现金额
|
||||
```
|
||||
|
||||
**涉及表**:
|
||||
- `merchant_accounts`: balance += 结算金额
|
||||
- `merchant_transactions`: 插入交易流水
|
||||
- `platform_accounts`: balance += 服务费, total_income += 服务费
|
||||
- `platform_transactions`: 插入交易流水
|
||||
- `user_accounts`: balance += 返现金额(如有邀请)
|
||||
- `user_transactions`: 插入交易流水(如有邀请)
|
||||
- `platform_accounts`: balance -= 返现金额, total_expense += 返现金额(如有邀请)
|
||||
- `settlements`: 插入结算单记录
|
||||
- `settlement_items`: 插入结算明细
|
||||
|
||||
**代码位置**:
|
||||
- `apps/server/src/modules/shared/finance/settlement.service.ts`
|
||||
- `apps/server/src/modules/app/activity/activity.service.ts`
|
||||
|
||||
### 3. 订单退款流程 - v2.0 更新
|
||||
|
||||
```
|
||||
用户申请退款
|
||||
↓
|
||||
调用第三方支付退款API
|
||||
↓
|
||||
系统总账户 +退款金额 (expense)
|
||||
↓
|
||||
订单状态:pending_confirm/pending_checkin → refunded
|
||||
```
|
||||
|
||||
**涉及表**:
|
||||
- `system_accounts`: balance -= 退款金额, total_refund += 退款金额
|
||||
- `system_transactions`: 插入交易流水(expense)
|
||||
|
||||
**代码位置**: `apps/server/src/modules/shared/finance/refund.service.ts`
|
||||
|
||||
### 4. 提现流程 - v2.0 更新
|
||||
|
||||
#### 用户提现
|
||||
|
||||
```
|
||||
用户申请提现
|
||||
↓
|
||||
用户账户:balance → frozen_balance
|
||||
↓
|
||||
管理员审核通过
|
||||
↓
|
||||
管理员确认打款
|
||||
↓
|
||||
用户账户 -提现金额(从frozen_balance扣减)
|
||||
系统总账户 +提现金额 (expense)
|
||||
↓
|
||||
提现状态:pending → approved → paid
|
||||
```
|
||||
|
||||
#### 商家提现
|
||||
|
||||
```
|
||||
商家申请提现
|
||||
↓
|
||||
商家账户:balance → frozen_balance
|
||||
↓
|
||||
管理员审核通过
|
||||
↓
|
||||
管理员确认打款
|
||||
↓
|
||||
商家账户 -提现金额(从frozen_balance扣减)
|
||||
系统总账户 +提现金额 (expense)
|
||||
↓
|
||||
提现状态:pending → approved → paid
|
||||
```
|
||||
|
||||
**涉及表**:
|
||||
- `user_accounts` / `merchant_accounts`: frozen_balance → balance扣减
|
||||
- `user_transactions` / `merchant_transactions`: 插入交易流水
|
||||
- `system_accounts`: balance -= 提现金额, total_withdrawn += 提现金额
|
||||
- `system_transactions`: 插入交易流水
|
||||
- `user_withdrawals` / `merchant_withdrawals`: 更新提现状态
|
||||
|
||||
**代码位置**: `apps/server/src/modules/shared/finance/withdrawal.service.ts`
|
||||
|
||||
### platform_accounts(平台账户表)
|
||||
|
||||
```sql
|
||||
@@ -126,9 +378,9 @@ CREATE TABLE `user_transactions` (
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- 复式记账:每笔转账生成两条流水(一条支出 + 一条收入)
|
||||
- `transaction_no` 相同的两条流水表示同一笔转账
|
||||
- `related_account_type` 和 `related_account_id` 关联对方账户
|
||||
- 所有账户操作都记录详细的交易流水
|
||||
- `transaction_no` 全局唯一,用于追踪资金流向
|
||||
- 记录交易前后余额,便于对账和审计
|
||||
|
||||
### settlements(结算单表)
|
||||
|
||||
@@ -152,123 +404,71 @@ CREATE TABLE `settlements` (
|
||||
**关键点**:
|
||||
- 每周一凌晨2点自动生成上周的结算单
|
||||
- `settlement_amount` = `order_amount` - `service_fee`
|
||||
- 结算时执行转账:平台账户 → 商家账户
|
||||
|
||||
### daily_reconciliations(日对账表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `daily_reconciliations` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT,
|
||||
`reconciliation_date` DATE UNIQUE, -- 对账日期
|
||||
`platform_balance` DECIMAL(12,2), -- 平台余额
|
||||
`merchant_balance_sum` DECIMAL(12,2), -- 商家余额总和
|
||||
`user_balance_sum` DECIMAL(12,2), -- 用户余额总和
|
||||
`total_balance` DECIMAL(12,2), -- 总余额
|
||||
`order_income` DECIMAL(12,2) DEFAULT 0.00, -- 订单收入
|
||||
`service_fee` DECIMAL(12,2) DEFAULT 0.00, -- 服务费
|
||||
`merchant_settlement` DECIMAL(12,2) DEFAULT 0.00, -- 商家结算
|
||||
`cashback_expense` DECIMAL(12,2) DEFAULT 0.00, -- 返现支出
|
||||
`withdraw_expense` DECIMAL(12,2) DEFAULT 0.00, -- 提现支出
|
||||
`refund_expense` DECIMAL(12,2) DEFAULT 0.00, -- 退款支出
|
||||
`status` ENUM('balanced','unbalanced'), -- 状态
|
||||
`error_message` TEXT, -- 异常信息
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- 每天凌晨3点自动执行对账
|
||||
- 检查借贷平衡:收入总额 = 支出总额
|
||||
- 如有异常记录 `error_message` 并发送告警
|
||||
|
||||
## 业务流程与数据库操作
|
||||
|
||||
### 1. 用户支付订单
|
||||
|
||||
```
|
||||
1. 用户支付 → 平台账户收入
|
||||
2. 插入 transactions 记录(平台账户 income)
|
||||
3. 更新 accounts.balance(平台账户)
|
||||
```
|
||||
|
||||
### 2. 周结算
|
||||
|
||||
```
|
||||
1. 查询上周已完成订单
|
||||
2. 按商家分组计算结算金额
|
||||
3. 插入 settlements 记录
|
||||
4. 插入 settlement_items 记录(订单明细)
|
||||
5. 执行转账:平台账户 → 商家账户
|
||||
- 插入 2 条 transactions 记录(复式记账)
|
||||
- 更新 2 个 accounts.balance
|
||||
```
|
||||
|
||||
### 3. 商家提现
|
||||
|
||||
```
|
||||
1. 商家申请提现
|
||||
- 插入 merchant_withdrawals 记录
|
||||
- 冻结商家账户余额(frozen_balance += amount)
|
||||
2. 管理员审核通过
|
||||
- 更新 merchant_withdrawals.status = 'approved'
|
||||
3. 管理员确认打款
|
||||
- 扣减余额(balance -= amount, frozen_balance -= amount)
|
||||
- 插入 transactions 记录(商家账户 expense)
|
||||
- 更新 merchant_withdrawals.status = 'paid'
|
||||
```
|
||||
|
||||
### 4. 日对账
|
||||
|
||||
```
|
||||
1. 统计各账户余额
|
||||
- SELECT SUM(balance) FROM accounts WHERE account_type = 'platform'
|
||||
- SELECT SUM(balance) FROM accounts WHERE account_type = 'merchant'
|
||||
- SELECT SUM(balance) FROM accounts WHERE account_type = 'user'
|
||||
2. 统计当日交易金额
|
||||
- SELECT SUM(amount) FROM transactions WHERE direction = 'income'
|
||||
- SELECT SUM(amount) FROM transactions WHERE direction = 'expense'
|
||||
3. 检查借贷平衡
|
||||
- 收入总额 = 支出总额(允许 0.01 元误差)
|
||||
4. 插入 daily_reconciliations 记录
|
||||
```
|
||||
|
||||
## 索引说明
|
||||
|
||||
### 关键索引
|
||||
|
||||
1. **accounts 表**
|
||||
- `uk_type_owner`: 唯一索引,确保每个用户/商家只有一个账户
|
||||
- `idx_status`: 按状态查询
|
||||
|
||||
2. **transactions 表**
|
||||
- `uk_transaction_no`: 唯一索引,防止重复交易
|
||||
- `idx_account_id`: 按账户查询流水
|
||||
- `idx_business`: 按业务类型和业务ID查询
|
||||
- `idx_created_at`: 按时间范围查询
|
||||
|
||||
3. **settlements 表**
|
||||
- `uk_settlement_no`: 唯一索引
|
||||
- `idx_merchant_id`: 按商家查询
|
||||
- `idx_period`: 按周期查询
|
||||
|
||||
4. **withdrawals 表**
|
||||
- `uk_withdraw_no`: 唯一索引
|
||||
- `idx_status`: 按状态查询(待审核、已打款等)
|
||||
- 结算时执行转账:给商家账户增加余额,给平台账户增加服务费
|
||||
|
||||
## 数据一致性保证
|
||||
|
||||
### 1. 事务保证
|
||||
|
||||
所有涉及金额变动的操作都在事务中执行:
|
||||
- 转账操作(账户余额变动 + 交易流水插入)
|
||||
- 提现操作(余额冻结/扣减 + 提现记录更新)
|
||||
- 结算操作(结算单生成 + 转账执行)
|
||||
|
||||
### 2. 乐观锁
|
||||
```typescript
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
`accounts` 表使用 `version` 字段实现乐观锁,防止并发更新导致余额错误。
|
||||
try {
|
||||
// 账户操作
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. CHECK 约束
|
||||
### 2. 悲观锁
|
||||
|
||||
在查询账户时使用悲观锁,防止并发修改:
|
||||
|
||||
```typescript
|
||||
const account = await queryRunner.manager.findOne(SystemAccount, {
|
||||
where: { account_name: '系统总账户' },
|
||||
lock: { mode: 'pessimistic_write' },
|
||||
});
|
||||
```
|
||||
|
||||
### 3. 乐观锁
|
||||
|
||||
使用 `version` 字段实现乐观锁,检测并发冲突:
|
||||
|
||||
```typescript
|
||||
account.version += 1;
|
||||
await queryRunner.manager.save(account);
|
||||
```
|
||||
|
||||
### 4. 金额精度控制
|
||||
|
||||
所有金额计算都使用 `Number()` 转换和 `toFixed(2)` 保留两位小数:
|
||||
|
||||
```typescript
|
||||
const amountNum = Number(amount);
|
||||
const balanceBefore = Number(account.balance);
|
||||
const balanceAfter = parseFloat((balanceBefore + amountNum).toFixed(2));
|
||||
```
|
||||
|
||||
### 5. 余额校验
|
||||
|
||||
在扣减余额前检查余额是否足够:
|
||||
|
||||
```typescript
|
||||
if (balanceBefore < amount) {
|
||||
throw new BadRequestException('账户余额不足');
|
||||
}
|
||||
```
|
||||
|
||||
### 6. CHECK 约束
|
||||
|
||||
```sql
|
||||
CONSTRAINT `chk_balance` CHECK (`balance` >= 0)
|
||||
@@ -277,29 +477,135 @@ CONSTRAINT `chk_frozen_balance` CHECK (`frozen_balance` >= 0)
|
||||
|
||||
确保余额不会为负数。
|
||||
|
||||
### 4. 触发器
|
||||
### 7. 触发器
|
||||
|
||||
- 用户注册时自动创建账户
|
||||
- 商家审核通过时自动创建账户
|
||||
|
||||
## 下一步工作
|
||||
## 数据库初始化
|
||||
|
||||
1. ✅ 数据库表结构已完成
|
||||
2. ✅ Entity 层已完成
|
||||
3. ✅ Service 层已完成
|
||||
4. ✅ Controller 层已完成
|
||||
5. ✅ DTO 层已完成
|
||||
6. ⏳ 执行数据库迁移脚本
|
||||
7. ⏳ 集成到订单模块(订单支付时调用账户服务)
|
||||
8. ⏳ 编写单元测试
|
||||
9. ⏳ 前端页面开发
|
||||
### 1. 迁移脚本位置
|
||||
|
||||
`database/migrations/001_init_schema.sql` 包含完整的财务系统表结构
|
||||
|
||||
### 2. 初始化步骤
|
||||
|
||||
```bash
|
||||
# 1. 执行数据库迁移脚本(会清空所有数据)
|
||||
mysql -u root -p < database/migrations/001_init_schema.sql
|
||||
|
||||
# 2. 初始化系统总账户
|
||||
mysql -u root -p
|
||||
USE your_database_name;
|
||||
INSERT INTO system_accounts (account_name, balance, total_income, total_refund, total_withdrawn)
|
||||
VALUES ('系统总账户', 0.00, 0.00, 0.00, 0.00);
|
||||
|
||||
# 3. 初始化平台账户
|
||||
INSERT INTO platform_accounts (account_name, balance, total_income, total_expense)
|
||||
VALUES ('主账户', 0.00, 0.00, 0.00);
|
||||
```
|
||||
|
||||
### 3. 自动创建的数据
|
||||
|
||||
执行迁移脚本后会自动创建:
|
||||
|
||||
1. **触发器**
|
||||
- `trg_user_create_account`: 用户注册时自动创建用户账户
|
||||
- `trg_merchant_create_account`: 商家审核通过时自动创建商家账户
|
||||
|
||||
## 索引说明
|
||||
|
||||
### 关键索引
|
||||
|
||||
1. **system_accounts 表**
|
||||
- `uk_account_name`: 唯一索引,确保账户名称唯一
|
||||
|
||||
2. **system_transactions 表**
|
||||
- `uk_transaction_no`: 唯一索引,防止重复交易
|
||||
- `idx_account_id`: 按账户查询流水
|
||||
- `idx_business`: 按业务类型和业务ID查询
|
||||
- `idx_created_at`: 按时间范围查询
|
||||
|
||||
3. **platform_accounts 表**
|
||||
- `uk_account_name`: 唯一索引,确保账户名称唯一
|
||||
|
||||
4. **user_accounts / merchant_accounts 表**
|
||||
- `uk_user_id` / `uk_merchant_id`: 唯一索引,确保每个用户/商家只有一个账户
|
||||
- `idx_status`: 按状态查询
|
||||
|
||||
5. **transactions 表**
|
||||
- `uk_transaction_no`: 唯一索引,防止重复交易
|
||||
- `idx_account_id`: 按账户查询流水
|
||||
- `idx_business`: 按业务类型和业务ID查询
|
||||
- `idx_created_at`: 按时间范围查询
|
||||
|
||||
6. **settlements 表**
|
||||
- `uk_settlement_no`: 唯一索引
|
||||
- `idx_merchant_id`: 按商家查询
|
||||
- `idx_period`: 按周期查询
|
||||
|
||||
7. **withdrawals 表**
|
||||
- `uk_withdraw_no`: 唯一索引
|
||||
- `idx_status`: 按状态查询(待审核、已打款等)
|
||||
|
||||
## 版本更新记录
|
||||
|
||||
### v2.0 (2024-01-XX) - 账户体系重构
|
||||
|
||||
**重大变更**:
|
||||
1. ✅ 新增 `system_accounts` 表 - 记录系统总资金
|
||||
2. ✅ 新增 `system_transactions` 表 - 系统总账户交易流水
|
||||
3. ✅ 重构 `platform_accounts` 表 - 只记录平台净收益
|
||||
- 字段含义变更: `total_income` 改为"服务费收入"
|
||||
- 字段含义变更: `total_expense` 改为"邀请返现支出"
|
||||
- 删除冗余字段: `total_order_income`, `total_service_fee`, `total_cashback`, `total_settlement`
|
||||
4. ✅ 修改订单支付逻辑 - 更新系统总账户
|
||||
5. ✅ 修改订单结算逻辑 - 同时更新商家账户和平台账户
|
||||
6. ✅ 修改邀请返现逻辑 - 从平台账户扣减
|
||||
7. ✅ 修改退款逻辑 - 更新系统总账户
|
||||
8. ✅ 修改提现逻辑 - 更新系统总账户
|
||||
|
||||
**资金守恒验证**:
|
||||
```
|
||||
系统总账户余额 = 商家账户余额总和 + 用户账户余额总和 + 平台账户余额
|
||||
```
|
||||
|
||||
**升级步骤**:
|
||||
1. 备份现有数据库
|
||||
2. 执行新的数据库迁移脚本
|
||||
3. 初始化系统总账户和平台账户
|
||||
4. 更新后端代码
|
||||
5. 重启服务
|
||||
6. 验证资金守恒
|
||||
|
||||
### v1.0 (2024-04-24) - 初始版本
|
||||
|
||||
**功能**:
|
||||
- 用户账户、商家账户、平台账户
|
||||
- 交易流水记录
|
||||
- 周结算功能
|
||||
- 提现功能
|
||||
- 日对账功能
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [财务系统完整设计文档](../finance-system.md) - 包含业务流程、核心服务、前端展示等完整内容
|
||||
- [项目需求文档](../requirements/项目需求文档.md) - 了解项目功能和业务逻辑
|
||||
- [开发总结](../DEVELOPMENT_SUMMARY.md) - 查看项目完成情况
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **删除旧版 settlements 表定义**: 数据库脚本中第 394-417 行的旧版本定义应该删除,避免混淆。
|
||||
1. **执行迁移脚本**: 确保按顺序执行 `001_init_schema.sql`,会自动创建所有表和触发器。
|
||||
|
||||
2. **执行迁移脚本**: 确保按顺序执行 `001_init_schema.sql`,会自动创建所有表和触发器。
|
||||
2. **初始化账户**: 迁移脚本执行后需要手动初始化系统总账户和平台账户。
|
||||
|
||||
3. **平台账户**: 迁移脚本会自动创建平台账户(`owner_id=0`),无需手动创建。
|
||||
3. **定时任务**: 需要在 NestJS 中启用 `@nestjs/schedule` 模块,定时任务才会执行。
|
||||
|
||||
4. **定时任务**: 需要在 NestJS 中启用 `@nestjs/schedule` 模块,定时任务才会执行。
|
||||
4. **资金安全**: 所有账户操作都使用事务、悲观锁和乐观锁保证数据一致性。
|
||||
|
||||
5. **金额精度**: 所有金额计算使用 `Number()` 和 `toFixed(2)` 确保精度。
|
||||
|
||||
---
|
||||
|
||||
**维护团队**: 开发团队
|
||||
**最后更新**: 2024-01-XX
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,789 +0,0 @@
|
||||
# 结算与财务系统设计文档
|
||||
|
||||
> **版本**:v2.0
|
||||
> **最后更新**:2026-05-21
|
||||
> **状态**:✅ 已实现并优化
|
||||
|
||||
---
|
||||
|
||||
## 📋 目录
|
||||
|
||||
1. [系统概述](#系统概述)
|
||||
2. [核心概念](#核心概念)
|
||||
3. [资金流转流程](#资金流转流程)
|
||||
4. [平台钱包设计](#平台钱包设计)
|
||||
5. [结算周期](#结算周期)
|
||||
6. [服务费计算](#服务费计算)
|
||||
7. [数据库设计](#数据库设计)
|
||||
8. [API接口](#api接口)
|
||||
9. [技术实现](#技术实现)
|
||||
10. [前端页面](#前端页面)
|
||||
11. [扩展方案](#扩展方案)
|
||||
|
||||
---
|
||||
|
||||
## 系统概述
|
||||
|
||||
结算与财务系统是平台的核心模块,负责处理订单支付、资金流转、商家结算、平台钱包和提现等业务。
|
||||
|
||||
### 核心功能
|
||||
|
||||
- ✅ 订单支付时记录平台账户收入
|
||||
- ✅ 自动周结算(每周一凌晨2点)
|
||||
- ✅ 手动执行周结算(管理员触发)
|
||||
- ✅ 结算预览功能
|
||||
- ✅ 防止重复结算
|
||||
- ✅ 服务费自动计算和扣除
|
||||
- ✅ 平台钱包管理
|
||||
- ✅ 商家账户余额管理
|
||||
- ✅ 提现申请和审核
|
||||
- ✅ 完整的交易流水记录
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 平台账户 vs 平台钱包
|
||||
|
||||
#### 平台账户(Platform Account)
|
||||
- **定位**:资金汇总账户,记录所有资金流转
|
||||
- **包含内容**:
|
||||
- 用户支付的订单金额(全额)
|
||||
- 待结算给商家的金额
|
||||
- 平台的收入(服务费、广告费等)
|
||||
- 平台的支出(商家结算、邀请返现等)
|
||||
|
||||
#### 平台钱包(Platform Wallet)
|
||||
- **定位**:平台可支配的资金
|
||||
- **包含内容**:
|
||||
- 服务费收入
|
||||
- 其他收入(广告费、会员费等)
|
||||
- 扣除:邀请返现等支出
|
||||
- **计算公式**:
|
||||
```
|
||||
钱包余额 = 账户总收入 - 账户总支出
|
||||
= (订单总额) - (商家结算 + 邀请返现)
|
||||
= (商家应得 + 服务费) - 商家应得 - 邀请返现
|
||||
= 服务费 + 其他收入 - 邀请返现
|
||||
```
|
||||
|
||||
### 三方账户体系
|
||||
|
||||
```
|
||||
系统账户体系:
|
||||
├─ 平台账户(PlatformAccount)
|
||||
│ └─ 主账户(全局唯一,资金中转)
|
||||
├─ 商家账户(MerchantAccount)
|
||||
│ └─ 每个商家一个账户
|
||||
└─ 用户账户(UserAccount)
|
||||
└─ 每个用户一个账户(用于返现、提现)
|
||||
```
|
||||
|
||||
### 可提现金额
|
||||
|
||||
| 角色 | 可提现金额计算 | 说明 |
|
||||
|------|---------------|------|
|
||||
| **商家** | `balance - frozen_balance` | 已结算金额(扣除服务费后) |
|
||||
| **用户** | `balance - frozen_balance` | 邀请返现所得 |
|
||||
| **平台** | `total_service_fee - frozen_balance` | 服务费收入(未来可扩展其他收入) |
|
||||
|
||||
---
|
||||
|
||||
## 资金流转流程
|
||||
|
||||
### 完整流程图
|
||||
|
||||
```
|
||||
┌─────────────┐
|
||||
│ 用户支付 │
|
||||
└──────┬──────┘
|
||||
│ 订单金额(全额)
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ 平台账户收入 │
|
||||
│ - 记录交易流水 │
|
||||
│ - 更新账户余额 │
|
||||
│ - 累加服务费 │
|
||||
└──────┬──────────────┘
|
||||
│ 等待订单完成
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ 商家确认离店 │
|
||||
│ - 订单状态: completed│
|
||||
│ - 记录离店时间 │
|
||||
└──────┬──────────────┘
|
||||
│ 等待周结算
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ 周结算(自动) │
|
||||
│ - 每周一凌晨2点 │
|
||||
│ - 统计上周完成订单 │
|
||||
│ - 计算结算金额 │
|
||||
└──────┬──────────────┘
|
||||
│ 结算金额 = 订单金额 - 服务费
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ 商家账户入账 │
|
||||
│ - 平台账户扣减 │
|
||||
│ - 商家账户增加 │
|
||||
│ - 复式记账 │
|
||||
└──────┬──────────────┘
|
||||
│ 商家可提现
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ 商家申请提现 │
|
||||
│ - 冻结余额 │
|
||||
│ - 管理员审核 │
|
||||
│ - 确认打款 │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
### 详细流程说明
|
||||
|
||||
#### 1. 用户支付订单
|
||||
|
||||
```
|
||||
用户支付 1000元
|
||||
├─ 平台账户收入:+1000元
|
||||
│ ├─ balance: +1000
|
||||
│ ├─ total_income: +1000
|
||||
│ └─ total_service_fee: +50(5%)
|
||||
└─ 商家账户:无变化(等待结算)
|
||||
```
|
||||
|
||||
**代码位置**:[order.service.ts](../../apps/server/src/modules/app/order/order.service.ts)
|
||||
|
||||
```typescript
|
||||
await this.accountService.addPlatformBalance(
|
||||
order.payAmount, // 订单金额(全额)
|
||||
order.serviceFee, // 服务费(5%)
|
||||
transactionNo,
|
||||
'order_payment',
|
||||
order.id,
|
||||
order.orderNo,
|
||||
`订单 ${order.orderNo} 支付收入`
|
||||
);
|
||||
```
|
||||
|
||||
#### 2. 订单完成后周结算
|
||||
|
||||
```
|
||||
每周一凌晨2点自动结算
|
||||
├─ 计算商家应得:1000 × 95% = 950元
|
||||
├─ 平台账户支出:-950元
|
||||
│ ├─ balance: 1000 - 950 = 50
|
||||
│ └─ total_expense: +950
|
||||
├─ 商家账户收入:+950元
|
||||
│ ├─ balance: +950
|
||||
│ └─ total_income: +950
|
||||
└─ 平台保留服务费:50元
|
||||
```
|
||||
|
||||
**代码位置**:[settlement.service.ts](../../apps/server/src/modules/shared/finance/settlement.service.ts)
|
||||
|
||||
#### 3. 邀请返现(可选)
|
||||
|
||||
```
|
||||
用户邀请新用户注册
|
||||
├─ 平台账户支出:-10元
|
||||
│ ├─ balance: 50 - 10 = 40
|
||||
│ └─ total_expense: +10
|
||||
└─ 用户账户收入:+10元
|
||||
├─ balance: +10
|
||||
└─ total_income: +10
|
||||
```
|
||||
|
||||
#### 4. 各方提现
|
||||
|
||||
```
|
||||
商家提现:500元
|
||||
├─ 商家账户:balance -500, frozen_balance +500
|
||||
└─ 审核通过后:frozen_balance -500, total_expense +500
|
||||
|
||||
用户提现:10元
|
||||
├─ 用户账户:balance -10, frozen_balance +10
|
||||
└─ 审核通过后:frozen_balance -10, total_expense +10
|
||||
|
||||
平台提现:30元
|
||||
├─ 平台账户:balance -30, frozen_balance +30
|
||||
└─ 审核通过后:frozen_balance -30, total_expense +30
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 平台钱包设计
|
||||
|
||||
### 钱包余额计算
|
||||
|
||||
```typescript
|
||||
// 钱包余额 = 账户总收入 - 账户总支出
|
||||
balance = total_income - total_expense
|
||||
|
||||
// 展开计算
|
||||
balance = (订单总额) - (商家结算 + 邀请返现)
|
||||
= (商家应得 + 服务费) - 商家应得 - 邀请返现
|
||||
= 服务费 - 邀请返现
|
||||
```
|
||||
|
||||
### 可提现金额计算
|
||||
|
||||
**当前实现:**
|
||||
```typescript
|
||||
// 可提现金额 = 累计服务费 - 冻结余额
|
||||
const withdrawableAmount = total_service_fee - frozen_balance;
|
||||
```
|
||||
|
||||
**未来扩展:**
|
||||
```typescript
|
||||
// 方案1:使用钱包余额(推荐)
|
||||
const withdrawableAmount = balance - frozen_balance;
|
||||
|
||||
// 方案2:累加所有收入类型
|
||||
const withdrawableAmount = (total_service_fee + total_ad_revenue + ...) - frozen_balance;
|
||||
```
|
||||
|
||||
### 前端展示
|
||||
|
||||
#### 核心指标卡片
|
||||
|
||||
```
|
||||
┌─────────────┬─────────────┬─────────────┬─────────────┐
|
||||
│ 钱包余额 │ 可提现金额 │ 冻结金额 │ 服务费收入 │
|
||||
│ 40.00元 │ 40.00元 │ 0.00元 │ 50.00元 │
|
||||
│ 平台可支配 │ 余额-冻结 │ 提现申请中 │ 累计服务费 │
|
||||
└─────────────┴─────────────┴─────────────┴─────────────┘
|
||||
```
|
||||
|
||||
#### 收入支出统计
|
||||
|
||||
```
|
||||
┌─────────────────┬─────────────────┬─────────────────┐
|
||||
│ 账户总收入 │ 账户总支出 │ 其他收入 │
|
||||
│ 1000.00元 │ 960.00元 │ 0.00元 │
|
||||
│ 所有订单支付 │ 结算+返现 │ 广告费、会员费 │
|
||||
└─────────────────┴─────────────────┴─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 结算周期
|
||||
|
||||
### 周结算机制
|
||||
|
||||
**执行时间**:每周一凌晨 2:00
|
||||
**结算周期**:上周一 00:00:00 ~ 上周日 23:59:59
|
||||
**结算对象**:状态为 `completed` 的订单
|
||||
**判断依据**:订单的 `checkout_at`(离店时间)字段
|
||||
|
||||
### 定时任务配置
|
||||
|
||||
**文件位置**:[settlement.schedule.ts](../../apps/server/src/schedule/settlement.schedule.ts)
|
||||
|
||||
```typescript
|
||||
@Cron('0 2 * * 1') // 每周一凌晨2点
|
||||
async handleWeeklySettlement() {
|
||||
await this.settlementService.handleWeeklySettlement();
|
||||
}
|
||||
```
|
||||
|
||||
### 结算逻辑
|
||||
|
||||
1. **查询上周已完成订单**
|
||||
```typescript
|
||||
const orders = await this.orderRepo
|
||||
.createQueryBuilder('o')
|
||||
.where('o.status = :status', { status: 'completed' })
|
||||
.andWhere('o.checkout_at BETWEEN :start AND :end', {
|
||||
start: `${lastWeekStart} 00:00:00`,
|
||||
end: `${lastWeekEnd} 23:59:59`
|
||||
})
|
||||
.getMany();
|
||||
```
|
||||
|
||||
2. **按商家分组统计**
|
||||
- 订单数量:`orderCount`
|
||||
- 订单总额:`orderAmount`
|
||||
- 服务费总额:`serviceFee`
|
||||
- 结算金额:`settlementAmount = orderAmount - serviceFee`
|
||||
|
||||
3. **生成结算单**
|
||||
- 创建 `Settlement` 记录
|
||||
- 创建 `SettlementItem` 记录
|
||||
|
||||
4. **执行资金转账**
|
||||
- 平台账户扣减
|
||||
- 商家账户增加
|
||||
- 复式记账
|
||||
|
||||
### 防止重复结算
|
||||
|
||||
```typescript
|
||||
// 检查该周期是否已经结算过
|
||||
const existingSettlements = await this.settlementRepo.count({
|
||||
where: {
|
||||
periodStart: lastWeekStart,
|
||||
periodEnd: lastWeekEnd
|
||||
}
|
||||
});
|
||||
|
||||
if (existingSettlements > 0) {
|
||||
throw new Error(`该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过,无法重复结算`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 服务费计算
|
||||
|
||||
### 计算公式
|
||||
|
||||
```typescript
|
||||
// 用户实付金额
|
||||
payAmount = totalAmount - couponDiscount
|
||||
|
||||
// 服务费
|
||||
serviceFee = Math.round(payAmount * serviceFeeRate * 100) / 100
|
||||
|
||||
// 商家实收金额
|
||||
merchantIncome = payAmount - serviceFee
|
||||
```
|
||||
|
||||
### 服务费率配置
|
||||
|
||||
**默认值**:5% (0.05)
|
||||
**配置位置**:`platform_configs` 表
|
||||
**配置键**:`service_fee_rate`
|
||||
|
||||
### 示例计算
|
||||
|
||||
```
|
||||
房费总额:1000元
|
||||
优惠券抵扣:100元
|
||||
服务费率:5%
|
||||
|
||||
用户实付:1000 - 100 = 900元
|
||||
服务费:900 × 0.05 = 45元
|
||||
商家实收:900 - 45 = 855元
|
||||
平台收入:45元
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### 核心表结构
|
||||
|
||||
#### 1. platform_accounts(平台账户表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `platform_accounts` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT,
|
||||
`account_name` VARCHAR(50) COMMENT '账户名称',
|
||||
`balance` DECIMAL(12,2) DEFAULT 0 COMMENT '钱包余额',
|
||||
`frozen_balance` DECIMAL(12,2) DEFAULT 0 COMMENT '冻结余额',
|
||||
`total_income` DECIMAL(12,2) DEFAULT 0 COMMENT '账户总收入',
|
||||
`total_expense` DECIMAL(12,2) DEFAULT 0 COMMENT '账户总支出',
|
||||
`total_service_fee` DECIMAL(12,2) DEFAULT 0 COMMENT '累计服务费',
|
||||
`version` INT UNSIGNED DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
`status` ENUM('active', 'frozen', 'closed') DEFAULT 'active',
|
||||
PRIMARY KEY (`id`)
|
||||
);
|
||||
```
|
||||
|
||||
**字段说明:**
|
||||
|
||||
| 字段 | 说明 | 当前用途 | 未来扩展 |
|
||||
|------|------|----------|----------|
|
||||
| `balance` | 钱包余额 | 服务费 - 邀请返现 | 所有收入 - 所有支出 |
|
||||
| `frozen_balance` | 冻结余额 | 提现申请中的金额 | 同左 |
|
||||
| `total_income` | 账户总收入 | 所有订单支付金额 | 订单 + 广告 + 会员等 |
|
||||
| `total_expense` | 账户总支出 | 商家结算 + 邀请返现 | 同左 + 其他支出 |
|
||||
| `total_service_fee` | 服务费收入 | 订单服务费累计 | 同左 |
|
||||
|
||||
#### 2. merchant_accounts(商家账户表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `merchant_accounts` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT,
|
||||
`merchant_id` BIGINT UNSIGNED,
|
||||
`balance` DECIMAL(12,2) DEFAULT 0 COMMENT '可用余额',
|
||||
`frozen_balance` DECIMAL(12,2) DEFAULT 0 COMMENT '冻结余额',
|
||||
`debt_amount` DECIMAL(12,2) DEFAULT 0 COMMENT '欠款金额',
|
||||
`total_income` DECIMAL(12,2) DEFAULT 0 COMMENT '累计收入',
|
||||
`total_expense` DECIMAL(12,2) DEFAULT 0 COMMENT '累计支出',
|
||||
`total_settlement` DECIMAL(12,2) DEFAULT 0 COMMENT '累计结算',
|
||||
`total_withdraw` DECIMAL(12,2) DEFAULT 0 COMMENT '累计提现',
|
||||
`pending_settlement` DECIMAL(12,2) DEFAULT 0 COMMENT '待结算',
|
||||
`last_settlement_at` DATETIME COMMENT '最后结算时间',
|
||||
`version` INT UNSIGNED DEFAULT 0,
|
||||
`status` ENUM('active', 'frozen', 'closed') DEFAULT 'active',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_merchant_id` (`merchant_id`)
|
||||
);
|
||||
```
|
||||
|
||||
#### 3. settlements(结算单表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `settlements` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT,
|
||||
`settlement_no` VARCHAR(32) UNIQUE,
|
||||
`merchant_id` BIGINT UNSIGNED,
|
||||
`period_start` DATE,
|
||||
`period_end` DATE,
|
||||
`order_count` INT UNSIGNED DEFAULT 0,
|
||||
`order_amount` DECIMAL(12,2) DEFAULT 0,
|
||||
`service_fee` DECIMAL(12,2) DEFAULT 0,
|
||||
`settlement_amount` DECIMAL(12,2) DEFAULT 0,
|
||||
`status` ENUM('pending','settled','failed'),
|
||||
`settled_at` DATETIME,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_merchant_id` (`merchant_id`),
|
||||
KEY `idx_period` (`period_start`, `period_end`)
|
||||
);
|
||||
```
|
||||
|
||||
#### 4. platform_transactions(平台交易流水表)
|
||||
|
||||
```sql
|
||||
CREATE TABLE `platform_transactions` (
|
||||
`id` BIGINT UNSIGNED AUTO_INCREMENT,
|
||||
`transaction_no` VARCHAR(32) UNIQUE,
|
||||
`account_id` BIGINT UNSIGNED,
|
||||
`direction` ENUM('income','expense'),
|
||||
`amount` DECIMAL(12,2),
|
||||
`balance_before` DECIMAL(12,2),
|
||||
`balance_after` DECIMAL(12,2),
|
||||
`transaction_type` VARCHAR(50),
|
||||
`business_type` VARCHAR(50),
|
||||
`business_id` BIGINT UNSIGNED,
|
||||
`business_no` VARCHAR(32),
|
||||
`related_account_type` ENUM('merchant','user'),
|
||||
`related_account_id` BIGINT UNSIGNED,
|
||||
`remark` VARCHAR(255),
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_account_id` (`account_id`),
|
||||
KEY `idx_business` (`business_type`, `business_id`)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API接口
|
||||
|
||||
### 平台钱包接口
|
||||
|
||||
#### 1. 查询平台账户
|
||||
|
||||
**接口**:`GET /api/admin/finance/platform-accounts`
|
||||
**权限**:平台管理员
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"account_name": "主账户",
|
||||
"balance": 40.00,
|
||||
"frozen_balance": 0.00,
|
||||
"total_income": 1000.00,
|
||||
"total_expense": 960.00,
|
||||
"total_service_fee": 50.00,
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 平台申请提现
|
||||
|
||||
**接口**:`POST /api/admin/finance/platform-withdrawals`
|
||||
**权限**:平台管理员
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"amount": 30.00,
|
||||
"bankName": "中国工商银行",
|
||||
"bankAccount": "6222021234567890123",
|
||||
"accountName": "某某科技有限公司",
|
||||
"remark": "提现备注"
|
||||
}
|
||||
```
|
||||
|
||||
### 结算管理接口
|
||||
|
||||
#### 3. 预览周结算
|
||||
|
||||
**接口**:`GET /api/admin/finance/settlements/preview-weekly`
|
||||
**权限**:平台管理员
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"periodStart": "2026-05-13",
|
||||
"periodEnd": "2026-05-19",
|
||||
"totalOrders": 45,
|
||||
"totalAmount": 35000.00,
|
||||
"totalServiceFee": 1750.00,
|
||||
"totalSettlementAmount": 33250.00,
|
||||
"merchants": [...]
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 执行周结算
|
||||
|
||||
**接口**:`POST /api/admin/finance/settlements/execute-weekly`
|
||||
**权限**:平台管理员
|
||||
|
||||
---
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 核心服务
|
||||
|
||||
#### 1. AccountService(账户服务)
|
||||
|
||||
**文件位置**:[account.service.ts](../../apps/server/src/modules/shared/finance/account.service.ts)
|
||||
|
||||
**核心方法**:
|
||||
|
||||
```typescript
|
||||
// 平台账户增加余额(订单支付)
|
||||
async addPlatformBalance(
|
||||
amount: number,
|
||||
serviceFee: number,
|
||||
transactionNo: string,
|
||||
businessType: string,
|
||||
businessId: number,
|
||||
businessNo: string,
|
||||
remark: string,
|
||||
): Promise<void> {
|
||||
// 更新字段:
|
||||
// - balance: +amount
|
||||
// - total_income: +amount
|
||||
// - total_service_fee: +serviceFee
|
||||
}
|
||||
|
||||
// 平台账户扣减余额(结算/返现)
|
||||
async deductPlatformBalance(
|
||||
amount: number,
|
||||
transactionNo: string,
|
||||
transactionType: string,
|
||||
businessType: string,
|
||||
businessId: number,
|
||||
businessNo: string,
|
||||
remark: string,
|
||||
): Promise<void> {
|
||||
// 更新字段:
|
||||
// - balance: -amount
|
||||
// - total_expense: +amount
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. WithdrawalService(提现服务)
|
||||
|
||||
**文件位置**:[withdrawal.service.ts](../../apps/server/src/modules/shared/finance/withdrawal.service.ts)
|
||||
|
||||
**平台提现逻辑**:
|
||||
|
||||
```typescript
|
||||
async createPlatformWithdrawal(dto: {
|
||||
amount: number;
|
||||
bankName: string;
|
||||
bankAccount: string;
|
||||
accountName: string;
|
||||
}) {
|
||||
const account = await this.accountService.getPlatformAccount();
|
||||
|
||||
// 计算可提现金额
|
||||
const availableAmount = Number(account.total_service_fee) - Number(account.frozen_balance);
|
||||
|
||||
if (availableAmount < amount) {
|
||||
throw new BadRequestException(`可提现金额不足,当前可提现:${availableAmount.toFixed(2)}元`);
|
||||
}
|
||||
|
||||
// 冻结余额
|
||||
account.balance -= amount;
|
||||
account.frozen_balance += amount;
|
||||
|
||||
// 创建提现记录
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 并发控制
|
||||
|
||||
#### 悲观锁
|
||||
|
||||
```typescript
|
||||
const account = await queryRunner.manager.findOne(PlatformAccount, {
|
||||
where: { account_name: '主账户' },
|
||||
lock: { mode: 'pessimistic_write' } // 行级锁
|
||||
});
|
||||
```
|
||||
|
||||
#### 乐观锁
|
||||
|
||||
```typescript
|
||||
await this.accountRepo.update(
|
||||
{ id: accountId, version: currentVersion },
|
||||
{ balance: newBalance, version: currentVersion + 1 }
|
||||
);
|
||||
```
|
||||
|
||||
### 复式记账
|
||||
|
||||
每笔转账生成两条流水记录:
|
||||
- 一条支出记录(平台账户)
|
||||
- 一条收入记录(商家账户)
|
||||
|
||||
两条流水的 `transaction_no` 相同,通过 `related_account_type` 和 `related_account_id` 关联。
|
||||
|
||||
---
|
||||
|
||||
## 前端页面
|
||||
|
||||
### 1. 平台钱包页面
|
||||
|
||||
**文件位置**:[PlatformWallet.tsx](../../apps/platform-admin/src/pages/finance/PlatformWallet.tsx)
|
||||
|
||||
**功能特性**:
|
||||
- 钱包余额、可提现金额、冻结金额、服务费收入展示
|
||||
- 账户总收入、总支出、其他收入统计
|
||||
- 钱包详情查看
|
||||
- 申请提现功能(集成在页面内)
|
||||
|
||||
### 2. 平台交易记录
|
||||
|
||||
**文件位置**:[PlatformTransactions.tsx](../../apps/platform-admin/src/pages/finance/PlatformTransactions.tsx)
|
||||
|
||||
**功能特性**:
|
||||
- 交易流水查询(按流水号、方向、类型、时间筛选)
|
||||
- 交易详情展示
|
||||
- 分页展示
|
||||
|
||||
### 3. 结算管理
|
||||
|
||||
**文件位置**:[Settlements.tsx](../../apps/platform-admin/src/pages/finance/Settlements.tsx)
|
||||
|
||||
**功能特性**:
|
||||
- 结算单列表查询
|
||||
- 结算单详情查看
|
||||
- 预览周结算数据
|
||||
- 手动执行周结算
|
||||
|
||||
### 4. 商家提现审核
|
||||
|
||||
**文件位置**:[Withdrawals.tsx](../../apps/platform-admin/src/pages/finance/Withdrawals.tsx)
|
||||
|
||||
**功能特性**:
|
||||
- 商家提现申请列表
|
||||
- 审核通过/拒绝
|
||||
- 确认打款
|
||||
|
||||
---
|
||||
|
||||
## 扩展方案
|
||||
|
||||
### 未来增加其他收入
|
||||
|
||||
#### 1. 数据库增加字段
|
||||
|
||||
```sql
|
||||
ALTER TABLE platform_accounts
|
||||
ADD COLUMN total_ad_revenue DECIMAL(12,2) DEFAULT 0 COMMENT '累计广告收入',
|
||||
ADD COLUMN total_membership_fee DECIMAL(12,2) DEFAULT 0 COMMENT '累计会员费收入',
|
||||
ADD COLUMN total_other_income DECIMAL(12,2) DEFAULT 0 COMMENT '累计其他收入';
|
||||
```
|
||||
|
||||
#### 2. 后端增加方法
|
||||
|
||||
```typescript
|
||||
// 广告收入
|
||||
async addPlatformAdRevenue(amount: number, ...): Promise<void> {
|
||||
account.balance += amount;
|
||||
account.total_income += amount;
|
||||
account.total_ad_revenue += amount;
|
||||
}
|
||||
|
||||
// 会员费收入
|
||||
async addPlatformMembershipFee(amount: number, ...): Promise<void> {
|
||||
account.balance += amount;
|
||||
account.total_income += amount;
|
||||
account.total_membership_fee += amount;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 前端增加展示
|
||||
|
||||
```typescript
|
||||
<Statistic
|
||||
title="广告收入"
|
||||
value={account.total_ad_revenue}
|
||||
precision={2}
|
||||
suffix="元"
|
||||
/>
|
||||
|
||||
<Statistic
|
||||
title="会员费收入"
|
||||
value={account.total_membership_fee}
|
||||
precision={2}
|
||||
suffix="元"
|
||||
/>
|
||||
```
|
||||
|
||||
#### 4. 调整可提现计算
|
||||
|
||||
```typescript
|
||||
// 方案1:继续使用各项收入累加
|
||||
const withdrawableAmount = (
|
||||
total_service_fee +
|
||||
total_ad_revenue +
|
||||
total_membership_fee
|
||||
) - frozen_balance;
|
||||
|
||||
// 方案2:直接使用钱包余额(推荐)
|
||||
const withdrawableAmount = balance - frozen_balance;
|
||||
```
|
||||
|
||||
**推荐方案2**,因为:
|
||||
- `balance` 已经包含了所有收入和支出
|
||||
- 不需要每次增加新收入类型都修改代码
|
||||
- 更简洁、更易维护
|
||||
|
||||
---
|
||||
|
||||
## 数据一致性保证
|
||||
|
||||
### 1. 事务保证
|
||||
|
||||
所有涉及金额变动的操作都在事务中执行:
|
||||
- 订单支付:订单状态更新 + 平台账户收入
|
||||
- 结算转账:平台账户扣减 + 商家账户增加 + 结算单生成
|
||||
- 提现:余额冻结/扣减 + 提现记录更新
|
||||
|
||||
### 2. 乐观锁
|
||||
|
||||
账户表使用 `version` 字段实现乐观锁,防止并发更新导致余额错误。
|
||||
|
||||
### 3. 复式记账
|
||||
|
||||
每笔转账生成两条流水记录,确保资金流向可追溯。
|
||||
|
||||
### 4. CHECK 约束
|
||||
|
||||
```sql
|
||||
CONSTRAINT `chk_balance` CHECK (`balance` >= 0)
|
||||
CONSTRAINT `chk_frozen_balance` CHECK (`frozen_balance` >= 0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [项目需求文档](../requirements/项目需求文档.md)
|
||||
- [数据库设计](../database/finance-database.md)
|
||||
- [开发总结](../DEVELOPMENT_SUMMARY.md)
|
||||
|
||||
---
|
||||
|
||||
**维护团队**:开发团队
|
||||
**最后更新**:2026-05-21
|
||||
Reference in New Issue
Block a user