Files
gongxue-base/apps/server/src/entities/bill.entity.ts
wangziqi f50301148d fix(correctness): 并发/事务/实体/时区/状态一致性修复
由 OCR(open-codereview.ai,deepseek-v4-flash)审查驱动修复:
- wallets 原子扣款防 double-spend;refund 条件更新幂等;findTransactions 分页
- financial/imports/occupancies/attendance 事务与 advisory lock;重复生成/提交幂等
- 矛盾校验器、日期区间、实体双映射/DECIMAL/nullable、时区统一(china-time)
- rbac-seed 防重激活、exam 权限恢复、状态一致性、路由顺序、N+1/IN 分块等性能项

Reviewed-by: OCR (open-codereview.ai)
2026-08-09 21:29:54 +08:00

100 lines
2.3 KiB
TypeScript

import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
ManyToOne,
JoinColumn,
OneToMany,
} from 'typeorm';
import { Student } from './student.entity';
import { BillItem } from './bill-item.entity';
@Entity('bills')
export class Bill {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'student_id' })
studentId: number;
@Column({ name: 'period_start', type: 'date' })
periodStart: string;
@Column({ name: 'period_end', type: 'date' })
periodEnd: string;
@Column({
name: 'shared_amount',
type: 'decimal',
precision: 10,
scale: 2,
default: 0,
transformer: { to: (v: number) => v, from: (v: string | null) => (v == null ? v : Number(v)) },
})
sharedAmount: number;
@Column({
name: 'personal_amount',
type: 'decimal',
precision: 10,
scale: 2,
default: 0,
transformer: { to: (v: number) => v, from: (v: string | null) => (v == null ? v : Number(v)) },
})
personalAmount: number;
@Column({
name: 'total_amount',
type: 'decimal',
precision: 10,
scale: 2,
default: 0,
transformer: { to: (v: number) => v, from: (v: string | null) => (v == null ? v : Number(v)) },
})
totalAmount: number;
@Column({ type: 'varchar', length: 30, default: 'batch' })
source: 'batch' | 'student_utility';
@Column({
name: 'paid_amount',
type: 'decimal',
precision: 10,
scale: 2,
default: 0,
transformer: { to: (v: number) => v, from: (v: string | null) => (v == null ? v : Number(v)) },
})
paidAmount: number;
@Column({
name: 'outstanding_amount',
type: 'decimal',
precision: 10,
scale: 2,
default: 0,
transformer: { to: (v: number) => v, from: (v: string | null) => (v == null ? v : Number(v)) },
})
outstandingAmount: number;
@Column({ type: 'varchar', length: 20, default: 'unpaid' })
status: string;
@Column({ name: 'cancelled_at', type: 'datetime', nullable: true })
cancelledAt: Date | null;
@Column({ name: 'cancel_reason', type: 'varchar', length: 300, nullable: true })
cancelReason: string | null;
@CreateDateColumn({ name: 'generated_at' })
generatedAt: Date;
@ManyToOne(() => Student, (s) => s.bills)
@JoinColumn({ name: 'student_id' })
student: Student;
@OneToMany(() => BillItem, (bi) => bi.bill)
items: BillItem[];
}