feat(task1): restructure directories for turborepo monorepo

- Move backend/ to apps/server/ via git mv
- Move frontend/ to apps/admin/ via git mv
- Create packages/typescript-config/ with base, nestjs, and react-vite presets
This commit is contained in:
2026-07-02 15:05:12 +08:00
parent 4704adcba1
commit 46a817503e
137 changed files with 52 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { Bill } from './bill.entity';
@Entity('bill_items')
export class BillItem {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'bill_id' })
billId: number;
@Column({ name: 'room_id', nullable: true })
roomId: number;
@Column({ name: 'expense_type', length: 20, nullable: true })
expenseType: string;
@Column({ length: 200, nullable: true })
description: string;
@Column({ nullable: true })
days: number;
@Column({ name: 'total_room_days', nullable: true })
totalRoomDays: number;
@Column({ name: 'room_total_amount', type: 'decimal', precision: 10, scale: 2, nullable: true })
roomTotalAmount: number;
@Column({ name: 'student_amount', type: 'decimal', precision: 10, scale: 2, nullable: true })
studentAmount: number;
@ManyToOne(() => Bill, (b) => b.items)
@JoinColumn({ name: 'bill_id' })
bill: Bill;
}

View File

@@ -0,0 +1,40 @@
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 })
sharedAmount: number;
@Column({ name: 'personal_amount', type: 'decimal', precision: 10, scale: 2, default: 0 })
personalAmount: number;
@Column({ name: 'total_amount', type: 'decimal', precision: 10, scale: 2, default: 0 })
totalAmount: number;
@Column({ type: 'varchar', length: 20, default: 'draft' })
status: string;
@CreateDateColumn({ name: 'generated_at' })
generatedAt: Date;
@ManyToOne(() => Student, (s) => s.bills)
@JoinColumn({ name: 'student_id' })
student: Student;
@OneToMany(() => BillItem, (bi) => bi.bill)
items: BillItem[];
}

View File

@@ -0,0 +1,58 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';
import { Classroom } from './classroom.entity';
import { Tenant } from './tenant.entity';
@Entity('classroom_rentals')
@Index(['classroomId', 'startDate', 'endDate'])
export class ClassroomRental {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'classroom_id' })
classroomId: number;
@ManyToOne(() => Classroom)
@JoinColumn({ name: 'classroom_id' })
classroom: Classroom;
@Column({ name: 'tenant_id' })
tenantId: number;
@ManyToOne(() => Tenant)
@JoinColumn({ name: 'tenant_id' })
tenant: Tenant;
@Column({ name: 'start_date', type: 'date' })
startDate: string;
@Column({ name: 'end_date', type: 'date' })
endDate: string;
// 合同 PDF 相对路径(相对 UPLOAD_DIR仅存文件名
@Column({ name: 'contract_path', length: 255, nullable: true })
contractPath: string;
@Column({ name: 'contract_original_name', length: 255, nullable: true })
contractOriginalName: string;
@Column({ name: 'daily_rate', type: 'decimal', precision: 10, scale: 2, nullable: true })
dailyRate: number;
@Column({ name: 'total_amount', type: 'decimal', precision: 10, scale: 2, nullable: true })
totalAmount: number;
@Column({ type: 'varchar', length: 20, default: 'active' })
status: string; // active / ended / cancelled
@Column({ type: 'text', nullable: true })
notes: string;
@Column({ name: 'created_by', nullable: true })
createdBy: number;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}

View File

@@ -0,0 +1,37 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
@Entity('classrooms')
export class Classroom {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 50 })
name: string;
@Column({ length: 50, nullable: true })
building: string;
@Column({ nullable: true })
floor: number;
@Column({ default: 30 })
capacity: number;
@Column({ name: 'room_type', type: 'varchar', length: 20, default: '大' })
roomType: string; // 大 / 次大 / 小
@Column({ name: 'course_type', length: 50, nullable: true })
courseType: string;
@Column({ length: 50, nullable: true })
supervisor: string;
@Column({ type: 'varchar', length: 20, default: 'available' })
status: string;
@Column({ type: 'text', nullable: true })
notes: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
}

View File

@@ -0,0 +1,46 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { Student } from './student.entity';
@Entity('deposits')
export class Deposit {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'student_id' })
studentId: number;
@Column({ type: 'decimal', precision: 10, scale: 2, default: 500 })
amount: number;
// paid: 已缴 | refunded: 已退 | deducted: 已扣除(部分或全部)
@Column({ type: 'varchar', length: 20, default: 'paid' })
status: string;
@Column({ name: 'paid_date', type: 'date' })
paidDate: string;
@Column({ name: 'refund_date', type: 'date', nullable: true })
refundDate: string;
@Column({ name: 'refund_amount', type: 'decimal', precision: 10, scale: 2, nullable: true })
refundAmount: number;
@Column({ name: 'deduction_amount', type: 'decimal', precision: 10, scale: 2, default: 0 })
deductionAmount: number;
@Column({ name: 'deduction_reason', type: 'text', nullable: true })
deductionReason: string;
@Column({ type: 'text', nullable: true })
notes: string;
@Column({ name: 'recorded_by', nullable: true })
recordedBy: number;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@ManyToOne(() => Student, { eager: true })
@JoinColumn({ name: 'student_id' })
student: Student;
}

View File

@@ -0,0 +1,15 @@
export { Student } from './student.entity';
export { Room } from './room.entity';
export { Occupancy } from './occupancy.entity';
export { RoomExpense } from './room-expense.entity';
export { PersonalExpense } from './personal-expense.entity';
export { Bill } from './bill.entity';
export { BillItem } from './bill-item.entity';
export { User } from './user.entity';
export { OperationLog } from './operation-log.entity';
export { Deposit } from './deposit.entity';
export { Classroom } from './classroom.entity';
export { Tenant } from './tenant.entity';
export { ClassroomRental } from './classroom-rental.entity';
export { Permission } from './permission.entity';
export { Role } from './role.entity';

View File

@@ -0,0 +1,44 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { Student } from './student.entity';
import { Room } from './room.entity';
@Entity('occupancies')
export class Occupancy {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'student_id' })
studentId: number;
@Column({ name: 'room_id' })
roomId: number;
@Column({ name: 'check_in_date', type: 'date' })
checkInDate: string;
@Column({ name: 'check_out_date', type: 'date', nullable: true })
checkOutDate: string;
@Column({ name: 'billing_start_date', type: 'date' })
billingStartDate: string;
@Column({ name: 'billing_end_date', type: 'date', nullable: true })
billingEndDate: string;
@Column({ name: 'check_out_reason', length: 100, nullable: true })
checkOutReason: string;
@Column({ type: 'text', nullable: true })
notes: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@ManyToOne(() => Student, (s) => s.occupancies)
@JoinColumn({ name: 'student_id' })
student: Student;
@ManyToOne(() => Room, (r) => r.occupancies)
@JoinColumn({ name: 'room_id' })
room: Room;
}

View File

@@ -0,0 +1,40 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
@Entity('operation_logs')
export class OperationLog {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', nullable: true })
userId: number;
@Column({ length: 50, nullable: true })
username: string;
@Column({ length: 50 })
module: string;
@Column({ length: 50 })
action: string;
@Column({ name: 'target_id', nullable: true })
targetId: number;
@Column({ name: 'target_type', length: 50, nullable: true })
targetType: string;
@Column({ type: 'text', nullable: true })
detail: string;
@Column({ name: 'ip_address', length: 50, nullable: true })
ipAddress: string;
@Column({ name: 'user_agent', length: 500, nullable: true })
userAgent: string;
@Column({ name: 'status', length: 20, nullable: true, default: 'success' })
status: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
}

View File

@@ -0,0 +1,19 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('permissions')
export class Permission {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 50, unique: true })
code: string;
@Column({ type: 'varchar', length: 50 })
name: string;
@Column({ type: 'varchar', length: 30 })
group: string;
@Column({ type: 'varchar', length: 200, nullable: true })
description: string;
}

View File

@@ -0,0 +1,36 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { Student } from './student.entity';
@Entity('personal_expenses')
export class PersonalExpense {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'student_id' })
studentId: number;
@Column({ name: 'room_id', nullable: true })
roomId: number;
@Column({ name: 'expense_type', type: 'varchar', length: 20 })
expenseType: string;
@Column({ type: 'decimal', precision: 10, scale: 2 })
amount: number;
@Column({ name: 'expense_date', type: 'date' })
expenseDate: string;
@Column({ type: 'text', nullable: true })
description: string;
@Column({ name: 'recorded_by', nullable: true })
recordedBy: number;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@ManyToOne(() => Student, (s) => s.personalExpenses)
@JoinColumn({ name: 'student_id' })
student: Student;
}

View File

@@ -0,0 +1,38 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToMany, JoinTable } from 'typeorm';
import { Permission } from './permission.entity';
import { User } from './user.entity';
@Entity('roles')
export class Role {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 30, unique: true })
name: string;
@Column({ type: 'varchar', length: 200, nullable: true })
description: string;
@Column({ name: 'is_system', type: 'boolean', default: false })
isSystem: boolean;
@Column({ type: 'tinyint', default: 1 })
status: number;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
@ManyToMany(() => Permission)
@JoinTable({
name: 'role_permissions',
joinColumn: { name: 'role_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'permission_id', referencedColumnName: 'id' },
})
permissions: Permission[];
@ManyToMany(() => User, (user) => user.roles)
users: User[];
}

View File

@@ -0,0 +1,36 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { Room } from './room.entity';
@Entity('room_expenses')
export class RoomExpense {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'room_id' })
roomId: number;
@Column({ name: 'expense_type', type: 'varchar', length: 20 })
expenseType: string;
@Column({ type: 'decimal', precision: 10, scale: 2 })
amount: number;
@Column({ name: 'period_start', type: 'date' })
periodStart: string;
@Column({ name: 'period_end', type: 'date' })
periodEnd: string;
@Column({ type: 'text', nullable: true })
description: string;
@Column({ name: 'recorded_by', nullable: true })
recordedBy: number;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@ManyToOne(() => Room, (r) => r.roomExpenses)
@JoinColumn({ name: 'room_id' })
room: Room;
}

View File

@@ -0,0 +1,39 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, OneToMany } from 'typeorm';
import { Occupancy } from './occupancy.entity';
import { RoomExpense } from './room-expense.entity';
@Entity('rooms')
export class Room {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'room_number', length: 20, unique: true })
roomNumber: string;
@Column({ length: 50, nullable: true })
building: string;
@Column({ nullable: true })
floor: number;
@Column()
capacity: number;
@Column({ type: 'varchar', length: 20, default: 'available' })
status: string;
@Column({ name: 'room_type', length: 20, nullable: true })
roomType: string;
@Column({ length: 10, nullable: true })
gender: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@OneToMany(() => Occupancy, (o) => o.room)
occupancies: Occupancy[];
@OneToMany(() => RoomExpense, (e) => e.room)
roomExpenses: RoomExpense[];
}

View File

@@ -0,0 +1,55 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
import { Occupancy } from './occupancy.entity';
import { PersonalExpense } from './personal-expense.entity';
import { Bill } from './bill.entity';
@Entity('students')
export class Student {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 50 })
name: string;
@Column({ length: 20, nullable: true })
phone: string;
@Column({ name: 'id_number', length: 30, nullable: true })
idNumber: string;
@Column({ length: 10, nullable: true })
gender: string;
@Column({ length: 20, nullable: true })
ethnicity: string;
@Column({ name: 'emergency_contact', length: 50, nullable: true })
emergencyContact: string;
@Column({ name: 'emergency_phone', length: 20, nullable: true })
emergencyPhone: string;
@Column({ type: 'varchar', length: 20, default: 'active' })
status: string;
@Column({ length: 100, nullable: true })
organization: string;
@Column({ length: 50, nullable: true })
supervisor: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
@OneToMany(() => Occupancy, (o) => o.student)
occupancies: Occupancy[];
@OneToMany(() => PersonalExpense, (e) => e.student)
personalExpenses: PersonalExpense[];
@OneToMany(() => Bill, (b) => b.student)
bills: Bill[];
}

View File

@@ -0,0 +1,32 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('tenants')
export class Tenant {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 100 })
name: string;
@Column({ length: 50, nullable: true })
contact: string;
@Column({ length: 30, nullable: true })
phone: string;
// 可视化颜色hex为空时由后端自动分配
@Column({ length: 20, nullable: true })
color: string;
@Column({ type: 'text', nullable: true })
notes: string;
@Column({ type: 'varchar', length: 20, default: 'active' })
status: string; // active / archived
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}

View File

@@ -0,0 +1,43 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToMany, JoinTable } from 'typeorm';
import { Role } from './role.entity';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 50, unique: true })
username: string;
@Column({ name: 'password_hash', length: 255 })
passwordHash: string;
// 移除: @Column({ type: 'varchar', length: 20, default: 'operator' })
// role: string;
@Column({ length: 50, nullable: true })
name: string;
// 移除: @Column({ name: 'allowed_menus', type: 'text', nullable: true })
// allowedMenus: string;
@Column({ name: 'is_active', default: true })
isActive: boolean;
@Column({ name: 'last_login_at', type: 'datetime', nullable: true })
lastLoginAt: Date;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
@ManyToMany(() => Role, (role) => role.users)
@JoinTable({
name: 'user_roles',
joinColumn: { name: 'user_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'role_id', referencedColumnName: 'id' },
})
roles: Role[];
}