feat: add rental_category/rate to Room, rental_type/tenant_id to Occupancy, tenant_id to Student

This commit is contained in:
2026-07-05 19:31:03 +08:00
parent 92e6ba0e34
commit 881e9c6175
8 changed files with 95 additions and 2 deletions

View File

@@ -34,6 +34,7 @@ const OccupanciesPage: React.FC = () => {
const [data, setData] = useState<any[]>([]);
const [students, setStudents] = useState<any[]>([]);
const [rooms, setRooms] = useState<any[]>([]);
const [tenants, setTenants] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const [checkInModal, setCheckInModal] = useState(false);
const [checkOutModal, setCheckOutModal] = useState<any>(null);
@@ -52,14 +53,16 @@ const OccupanciesPage: React.FC = () => {
const fetchData = async () => {
setLoading(true);
try {
const [occ, stu, rm]: any[] = await Promise.all([
const [occ, stu, rm, tn]: any[] = await Promise.all([
api.get('/occupancies', { params: { active: showActive ? 'true' : undefined } }),
api.get('/students'),
api.get('/rooms/overview'),
api.get('/tenants'),
]);
setData(occ);
setStudents(stu);
setRooms(rm);
setTenants(tn);
} catch (e) {
console.error(e);
}
@@ -89,6 +92,8 @@ const OccupanciesPage: React.FC = () => {
roomId: values.roomId,
checkInDate: values.checkInDate.format('YYYY-MM-DD'),
billingStartDate: values.billingStartDate?.format('YYYY-MM-DD'),
rentalType: values.rentalType,
tenantId: values.tenantId,
notes: values.notes,
});
message.success('入住登记成功');
@@ -508,6 +513,28 @@ const OccupanciesPage: React.FC = () => {
format="YYYY-MM-DD"
/>
</Form.Item>
<Form.Item name="rentalType" label="租赁类型">
<Select
allowClear
options={[
{ value: 'short', label: '短租' },
{ value: 'long', label: '长租' },
]}
placeholder="默认为短租"
/>
</Form.Item>
<Form.Item name="tenantId" label="关联单位">
<Select
showSearch
allowClear
optionFilterProp="label"
placeholder="选择关联单位"
options={tenants.map((t: { id: number; name: string }) => ({
value: t.id,
label: t.name,
}))}
/>
</Form.Item>
<Form.Item name="notes" label="备注">
<Input.TextArea rows={2} />
</Form.Item>

View File

@@ -422,6 +422,19 @@ const RoomsPage: React.FC = () => {
placeholder="留空自动解析"
/>
</Form.Item>
<Form.Item name="rentalCategory" label="租赁类别">
<Select
allowClear
options={[
{ value: 'short', label: '短租' },
{ value: 'long', label: '长租' },
]}
placeholder="默认为短租"
/>
</Form.Item>
<Form.Item name="monthlyRate" label="月租金">
<InputNumber min={0} precision={2} style={{ width: '100%' }} placeholder="长租月租金" />
</Form.Item>
{editing && (
<Form.Item name="status" label="状态">
<Select

View File

@@ -8,6 +8,7 @@ import {
} from 'typeorm';
import { Student } from './student.entity';
import { Room } from './room.entity';
import { Tenant } from './tenant.entity';
@Entity('occupancies')
export class Occupancy {
@@ -38,6 +39,16 @@ export class Occupancy {
@Column({ type: 'text', nullable: true })
notes: string;
@Column({ name: 'rental_type', length: 10, default: 'short' })
rentalType: string;
@Column({ name: 'tenant_id', type: 'integer', nullable: true })
tenantId: number;
@ManyToOne(() => Tenant, { nullable: true })
@JoinColumn({ name: 'tenant_id' })
tenant: Tenant;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;

View File

@@ -28,6 +28,12 @@ export class Room {
@Column({ length: 10, nullable: true })
gender: string;
@Column({ name: 'rental_category', length: 10, default: 'short' })
rentalCategory: string;
@Column({ name: 'monthly_rate', type: 'decimal', precision: 10, scale: 2, default: 0 })
monthlyRate: number;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;

View File

@@ -4,11 +4,14 @@ import {
Column,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
JoinColumn,
OneToMany,
} from 'typeorm';
import { Occupancy } from './occupancy.entity';
import { PersonalExpense } from './personal-expense.entity';
import { Bill } from './bill.entity';
import { Tenant } from './tenant.entity';
@Entity('students')
export class Student {
@@ -54,6 +57,13 @@ export class Student {
@OneToMany(() => Occupancy, (o) => o.student)
occupancies: Occupancy[];
@Column({ name: 'tenant_id', type: 'integer', nullable: true })
tenantId: number;
@ManyToOne(() => Tenant, { nullable: true })
@JoinColumn({ name: 'tenant_id' })
tenant: Tenant;
@OneToMany(() => PersonalExpense, (e) => e.student)
personalExpenses: PersonalExpense[];

View File

@@ -17,6 +17,14 @@ export class CheckInDto {
@IsOptional()
@IsString()
notes?: string;
@IsOptional()
@IsString()
rentalType?: string;
@IsOptional()
@IsInt()
tenantId?: number;
}
export class CheckOutDto {

View File

@@ -65,6 +65,8 @@ export class OccupanciesService {
roomId: dto.roomId,
checkInDate: dto.checkInDate,
billingStartDate: dto.billingStartDate || dto.checkInDate,
rentalType: dto.rentalType,
tenantId: dto.tenantId,
notes: dto.notes,
});
const saved = await this.repo.save(occ);

View File

@@ -1,4 +1,4 @@
import { IsString, IsOptional, IsInt, IsEnum, Min } from 'class-validator';
import { IsString, IsOptional, IsInt, IsEnum, Min, IsNumber } from 'class-validator';
export class CreateRoomDto {
@IsString()
@@ -19,6 +19,14 @@ export class CreateRoomDto {
@IsOptional()
@IsString()
roomType?: string;
@IsOptional()
@IsString()
rentalCategory?: string;
@IsOptional()
@IsNumber()
monthlyRate?: number;
}
export class UpdateRoomDto {
@@ -50,4 +58,12 @@ export class UpdateRoomDto {
@IsOptional()
@IsEnum(['available', 'full', 'maintenance'])
status?: string;
@IsOptional()
@IsString()
rentalCategory?: string;
@IsOptional()
@IsNumber()
monthlyRate?: number;
}