@valentine-efagene/qshelter-common 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/OpenApiHelper.ts +121 -0
- package/decorator/permission.decorator.ts +4 -0
- package/decorator/tenant.decorator.ts +16 -0
- package/dist/entities/BaseEntity.d.ts +11 -0
- package/dist/entities/BaseEntity.js +54 -0
- package/dist/entities/BaseEntity.js.map +1 -0
- package/dist/entities/TenantAwareEntity.d.ts +10 -0
- package/dist/entities/TenantAwareEntity.js +52 -0
- package/dist/entities/TenantAwareEntity.js.map +1 -0
- package/dist/entities/TenantAwareRepository.d.ts +13 -0
- package/dist/entities/TenantAwareRepository.js +65 -0
- package/dist/entities/TenantAwareRepository.js.map +1 -0
- package/dist/entities/amenity.entity.d.ts +4 -0
- package/dist/entities/amenity.entity.js +27 -0
- package/dist/entities/amenity.entity.js.map +1 -0
- package/dist/entities/common.entity.d.ts +17 -0
- package/dist/entities/common.entity.js +63 -0
- package/dist/entities/common.entity.js.map +1 -0
- package/dist/entities/common.pure.entity.d.ts +11 -0
- package/dist/entities/common.pure.entity.js +51 -0
- package/dist/entities/common.pure.entity.js.map +1 -0
- package/dist/entities/index.d.ts +27 -0
- package/dist/entities/index.js +44 -0
- package/dist/entities/index.js.map +1 -0
- package/dist/entities/mortgage-document.entity.d.ts +14 -0
- package/dist/entities/mortgage-document.entity.js +58 -0
- package/dist/entities/mortgage-document.entity.js.map +1 -0
- package/dist/entities/mortgage-downpayment-installment.entity.d.ts +19 -0
- package/dist/entities/mortgage-downpayment-installment.entity.js +63 -0
- package/dist/entities/mortgage-downpayment-installment.entity.js.map +1 -0
- package/dist/entities/mortgage-downpayment-payment.entity.d.ts +28 -0
- package/dist/entities/mortgage-downpayment-payment.entity.js +84 -0
- package/dist/entities/mortgage-downpayment-payment.entity.js.map +1 -0
- package/dist/entities/mortgage-downpayment.entity.d.ts +22 -0
- package/dist/entities/mortgage-downpayment.entity.js +66 -0
- package/dist/entities/mortgage-downpayment.entity.js.map +1 -0
- package/dist/entities/mortgage-step.entity.d.ts +12 -0
- package/dist/entities/mortgage-step.entity.js +52 -0
- package/dist/entities/mortgage-step.entity.js.map +1 -0
- package/dist/entities/mortgage-type.entity.d.ts +10 -0
- package/dist/entities/mortgage-type.entity.js +46 -0
- package/dist/entities/mortgage-type.entity.js.map +1 -0
- package/dist/entities/mortgage.entity.d.ts +37 -0
- package/dist/entities/mortgage.entity.js +124 -0
- package/dist/entities/mortgage.entity.js.map +1 -0
- package/dist/entities/password_reset_tokens.entity.d.ts +7 -0
- package/dist/entities/password_reset_tokens.entity.js +43 -0
- package/dist/entities/password_reset_tokens.entity.js.map +1 -0
- package/dist/entities/permission.entity.d.ts +6 -0
- package/dist/entities/permission.entity.js +30 -0
- package/dist/entities/permission.entity.js.map +1 -0
- package/dist/entities/property-document.entity.d.ts +6 -0
- package/dist/entities/property-document.entity.js +34 -0
- package/dist/entities/property-document.entity.js.map +1 -0
- package/dist/entities/property-media.entity.d.ts +6 -0
- package/dist/entities/property-media.entity.js +36 -0
- package/dist/entities/property-media.entity.js.map +1 -0
- package/dist/entities/property.entity.d.ts +36 -0
- package/dist/entities/property.entity.js +182 -0
- package/dist/entities/property.entity.js.map +1 -0
- package/dist/entities/refresh_token.entity.d.ts +7 -0
- package/dist/entities/refresh_token.entity.js +35 -0
- package/dist/entities/refresh_token.entity.js.map +1 -0
- package/dist/entities/role.entity.d.ts +8 -0
- package/dist/entities/role.entity.js +39 -0
- package/dist/entities/role.entity.js.map +1 -0
- package/dist/entities/settings.entity.d.ts +17 -0
- package/dist/entities/settings.entity.js +79 -0
- package/dist/entities/settings.entity.js.map +1 -0
- package/dist/entities/social.entity.d.ts +8 -0
- package/dist/entities/social.entity.js +46 -0
- package/dist/entities/social.entity.js.map +1 -0
- package/dist/entities/tenant.entity.d.ts +29 -0
- package/dist/entities/tenant.entity.js +82 -0
- package/dist/entities/tenant.entity.js.map +1 -0
- package/dist/entities/transaction.entity.d.ts +17 -0
- package/dist/entities/transaction.entity.js +84 -0
- package/dist/entities/transaction.entity.js.map +1 -0
- package/dist/entities/user.entity.d.ts +26 -0
- package/dist/entities/user.entity.js +103 -0
- package/dist/entities/user.entity.js.map +1 -0
- package/dist/entities/user_suspensions.entity.d.ts +7 -0
- package/dist/entities/user_suspensions.entity.js +42 -0
- package/dist/entities/user_suspensions.entity.js.map +1 -0
- package/dist/entities/wallet.entity.d.ts +17 -0
- package/dist/entities/wallet.entity.js +79 -0
- package/dist/entities/wallet.entity.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/pagination/index.d.ts +2 -0
- package/dist/pagination/index.js +19 -0
- package/dist/pagination/index.js.map +1 -0
- package/dist/pagination/pagination.helper.d.ts +7 -0
- package/dist/pagination/pagination.helper.js +40 -0
- package/dist/pagination/pagination.helper.js.map +1 -0
- package/dist/pagination/pagination.types.d.ts +19 -0
- package/dist/pagination/pagination.types.js +3 -0
- package/dist/pagination/pagination.types.js.map +1 -0
- package/dist/standard-response.d.ts +7 -0
- package/dist/standard-response.js +27 -0
- package/dist/standard-response.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/common.type.d.ts +26 -0
- package/dist/types/common.type.js +26 -0
- package/dist/types/common.type.js.map +1 -0
- package/dist/types/mortgage-fsm.types.d.ts +180 -0
- package/dist/types/mortgage-fsm.types.js +130 -0
- package/dist/types/mortgage-fsm.types.js.map +1 -0
- package/dist/types/policy.types.d.ts +18 -0
- package/dist/types/policy.types.js +3 -0
- package/dist/types/policy.types.js.map +1 -0
- package/dist/types/property.type.d.ts +9 -0
- package/dist/types/property.type.js +15 -0
- package/dist/types/property.type.js.map +1 -0
- package/dist/types/social.enums.d.ts +15 -0
- package/dist/types/social.enums.js +22 -0
- package/dist/types/social.enums.js.map +1 -0
- package/dist/types/tenant.enums.d.ts +13 -0
- package/dist/types/tenant.enums.js +19 -0
- package/dist/types/tenant.enums.js.map +1 -0
- package/dist/types/transaction.type.d.ts +8 -0
- package/dist/types/transaction.type.js +14 -0
- package/dist/types/transaction.type.js.map +1 -0
- package/dist/types/user.enums.d.ts +10 -0
- package/dist/types/user.enums.js +16 -0
- package/dist/types/user.enums.js.map +1 -0
- package/entities/BaseEntity.ts +34 -0
- package/entities/TenantAwareEntity.ts +34 -0
- package/entities/TenantAwareRepository.ts +100 -0
- package/entities/amenity.entity.ts +10 -0
- package/entities/common.entity.ts +46 -0
- package/entities/common.pure.entity.ts +36 -0
- package/entities/index.ts +27 -0
- package/entities/mortgage-document.entity.ts +37 -0
- package/entities/mortgage-downpayment-installment.entity.ts +40 -0
- package/entities/mortgage-downpayment-payment.entity.ts +61 -0
- package/entities/mortgage-downpayment.entity.ts +43 -0
- package/entities/mortgage-step.entity.ts +33 -0
- package/entities/mortgage-type.entity.ts +31 -0
- package/entities/mortgage.entity.ts +89 -0
- package/entities/password_reset_tokens.entity.ts +25 -0
- package/entities/permission.entity.ts +12 -0
- package/entities/property-document.entity.ts +21 -0
- package/entities/property-media.entity.ts +23 -0
- package/entities/property.entity.ts +147 -0
- package/entities/refresh_token.entity.ts +16 -0
- package/entities/role.entity.ts +20 -0
- package/entities/settings.entity.ts +56 -0
- package/entities/social.entity.ts +27 -0
- package/entities/tenant.entity.ts +65 -0
- package/entities/transaction.entity.ts +56 -0
- package/entities/user.entity.ts +89 -0
- package/entities/user_suspensions.entity.ts +24 -0
- package/entities/wallet.entity.ts +54 -0
- package/guard/permission.guard.ts +42 -0
- package/guard/swagger-auth.guard.ts +9 -0
- package/helpers/ArrayHelper.ts +1 -0
- package/helpers/ConstantHelper.ts +101 -0
- package/helpers/CustomNamingStrategy.ts +27 -0
- package/helpers/DateHelper.ts +21 -0
- package/helpers/EmailHelper.ts +38 -0
- package/helpers/FileSystemHelper.ts +101 -0
- package/index.ts +9 -0
- package/middleware/TenantMiddleware.ts +52 -0
- package/package.json +46 -0
- package/pagination/index.ts +2 -0
- package/pagination/pagination.helper.ts +57 -0
- package/pagination/pagination.types.ts +21 -0
- package/standard-response.ts +16 -0
- package/tsconfig.json +33 -0
- package/types/common.type.ts +32 -0
- package/types/mortgage-fsm.types.ts +279 -0
- package/types/policy.types.ts +21 -0
- package/types/property.type.ts +10 -0
- package/types/social.enums.ts +17 -0
- package/types/tenant.enums.ts +14 -0
- package/types/transaction.type.ts +9 -0
- package/types/user.enums.ts +11 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Repository, FindManyOptions, FindOneOptions, FindOptionsWhere, DeepPartial } from 'typeorm';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base repository with automatic tenant scoping
|
|
5
|
+
* All queries automatically filter by tenantId
|
|
6
|
+
*/
|
|
7
|
+
export class TenantAwareRepository<Entity extends { tenantId: number }> extends Repository<Entity> {
|
|
8
|
+
/**
|
|
9
|
+
* Find entities with automatic tenant filtering
|
|
10
|
+
*/
|
|
11
|
+
async findByTenant(tenantId: number, options?: FindManyOptions<Entity>): Promise<Entity[]> {
|
|
12
|
+
return this.find({
|
|
13
|
+
...options,
|
|
14
|
+
where: {
|
|
15
|
+
...options?.where,
|
|
16
|
+
tenantId,
|
|
17
|
+
} as FindOptionsWhere<Entity>,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Find one entity with automatic tenant filtering
|
|
23
|
+
*/
|
|
24
|
+
async findOneByTenant(
|
|
25
|
+
tenantId: number,
|
|
26
|
+
options?: FindOneOptions<Entity>
|
|
27
|
+
): Promise<Entity | null> {
|
|
28
|
+
return this.findOne({
|
|
29
|
+
...options,
|
|
30
|
+
where: {
|
|
31
|
+
...options?.where,
|
|
32
|
+
tenantId,
|
|
33
|
+
} as FindOptionsWhere<Entity>,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Count entities with automatic tenant filtering
|
|
39
|
+
*/
|
|
40
|
+
async countByTenant(tenantId: number, options?: FindManyOptions<Entity>): Promise<number> {
|
|
41
|
+
return this.count({
|
|
42
|
+
...options,
|
|
43
|
+
where: {
|
|
44
|
+
...options?.where,
|
|
45
|
+
tenantId,
|
|
46
|
+
} as FindOptionsWhere<Entity>,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Create and save entity with automatic tenantId
|
|
52
|
+
*/
|
|
53
|
+
async createForTenant(tenantId: number, entityData: DeepPartial<Entity>): Promise<Entity> {
|
|
54
|
+
const entity = this.create({
|
|
55
|
+
...entityData,
|
|
56
|
+
tenantId,
|
|
57
|
+
} as DeepPartial<Entity>);
|
|
58
|
+
return this.save(entity);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Update entity with tenant validation
|
|
63
|
+
*/
|
|
64
|
+
async updateForTenant(
|
|
65
|
+
tenantId: number,
|
|
66
|
+
id: number,
|
|
67
|
+
entityData: DeepPartial<Entity>
|
|
68
|
+
): Promise<Entity> {
|
|
69
|
+
const entity = await this.findOneByTenant(tenantId, { where: { id } as any });
|
|
70
|
+
if (!entity) {
|
|
71
|
+
throw new Error(`Entity with ID ${id} not found for tenant ${tenantId}`);
|
|
72
|
+
}
|
|
73
|
+
Object.assign(entity, entityData);
|
|
74
|
+
return this.save(entity);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Delete entity with tenant validation
|
|
79
|
+
*/
|
|
80
|
+
async deleteForTenant(tenantId: number, id: number): Promise<void> {
|
|
81
|
+
const entity = await this.findOneByTenant(tenantId, { where: { id } as any });
|
|
82
|
+
if (!entity) {
|
|
83
|
+
throw new Error(`Entity with ID ${id} not found for tenant ${tenantId}`);
|
|
84
|
+
}
|
|
85
|
+
await this.remove(entity);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Soft delete entity with tenant validation
|
|
90
|
+
*/
|
|
91
|
+
async softDeleteForTenant(tenantId: number, id: number): Promise<void> {
|
|
92
|
+
const entity = await this.findOneByTenant(tenantId, { where: { id } as any });
|
|
93
|
+
if (!entity) {
|
|
94
|
+
throw new Error(`Entity with ID ${id} not found for tenant ${tenantId}`);
|
|
95
|
+
}
|
|
96
|
+
await this.softRemove(entity);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default TenantAwareRepository;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { User } from './user.entity';
|
|
2
|
+
import {
|
|
3
|
+
ManyToOne,
|
|
4
|
+
JoinColumn,
|
|
5
|
+
Column,
|
|
6
|
+
} from 'typeorm';
|
|
7
|
+
import { DocumentStatus } from '../types/common.type';
|
|
8
|
+
import { AbstractTenantAwareEntity } from './common.pure.entity';
|
|
9
|
+
|
|
10
|
+
export abstract class AbstractBaseReviewableEntity extends AbstractTenantAwareEntity {
|
|
11
|
+
@ManyToOne(() => User, { eager: true })
|
|
12
|
+
@JoinColumn({ name: 'reviewed_by' })
|
|
13
|
+
reviewer: User;
|
|
14
|
+
|
|
15
|
+
@Column({ type: 'timestamp', nullable: true })
|
|
16
|
+
reviewedAt: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export abstract class AbstractBaseDocumentEntity extends AbstractBaseReviewableEntity {
|
|
20
|
+
@Column({
|
|
21
|
+
type: 'enum',
|
|
22
|
+
enum: DocumentStatus,
|
|
23
|
+
default: DocumentStatus.PENDING,
|
|
24
|
+
})
|
|
25
|
+
status: DocumentStatus;
|
|
26
|
+
|
|
27
|
+
@Column({ nullable: true })
|
|
28
|
+
comment: string;
|
|
29
|
+
|
|
30
|
+
@Column({ type: 'text', nullable: false })
|
|
31
|
+
url: string;
|
|
32
|
+
|
|
33
|
+
@Column({ nullable: true })
|
|
34
|
+
description: string;
|
|
35
|
+
|
|
36
|
+
@Column({ nullable: true })
|
|
37
|
+
name: string;
|
|
38
|
+
|
|
39
|
+
@Column({ nullable: false, comment: "In bytes" })
|
|
40
|
+
size: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export abstract class AbstractBaseMediaEntity extends AbstractBaseDocumentEntity {
|
|
44
|
+
// @Column()
|
|
45
|
+
// mimeType: string;
|
|
46
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
import { CreateDateColumn, DeleteDateColumn, PrimaryGeneratedColumn, UpdateDateColumn, Column, Index, ManyToOne, JoinColumn } from "typeorm";
|
|
3
|
+
import { Tenant } from "./tenant.entity";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export abstract class AbstractBaseEntity {
|
|
7
|
+
@PrimaryGeneratedColumn()
|
|
8
|
+
id: number;
|
|
9
|
+
|
|
10
|
+
@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
|
11
|
+
createdAt: Date;
|
|
12
|
+
|
|
13
|
+
@UpdateDateColumn({
|
|
14
|
+
type: 'timestamp',
|
|
15
|
+
default: () => 'CURRENT_TIMESTAMP',
|
|
16
|
+
onUpdate: 'CURRENT_TIMESTAMP',
|
|
17
|
+
})
|
|
18
|
+
updatedAt: Date;
|
|
19
|
+
|
|
20
|
+
@DeleteDateColumn({ nullable: true, default: null })
|
|
21
|
+
deletedAt: Date;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Tenant-aware base entity
|
|
26
|
+
* All entities that need tenant isolation should extend this
|
|
27
|
+
*/
|
|
28
|
+
export abstract class AbstractTenantAwareEntity extends AbstractBaseEntity {
|
|
29
|
+
@ManyToOne(() => Tenant, { onDelete: 'CASCADE' })
|
|
30
|
+
@JoinColumn({ name: 'tenant_id' })
|
|
31
|
+
tenant: Tenant;
|
|
32
|
+
|
|
33
|
+
@Column({ name: 'tenant_id' })
|
|
34
|
+
@Index()
|
|
35
|
+
tenantId: number;
|
|
36
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export * from './BaseEntity';
|
|
2
|
+
export * from './TenantAwareEntity';
|
|
3
|
+
export * from './TenantAwareRepository';
|
|
4
|
+
export * from './amenity.entity';
|
|
5
|
+
export * from './common.entity';
|
|
6
|
+
export * from './common.pure.entity';
|
|
7
|
+
export * from './mortgage-document.entity';
|
|
8
|
+
export * from './mortgage-downpayment-installment.entity';
|
|
9
|
+
export * from './mortgage-downpayment-payment.entity';
|
|
10
|
+
export * from './mortgage-downpayment.entity';
|
|
11
|
+
export * from './mortgage-step.entity';
|
|
12
|
+
export * from './mortgage-type.entity';
|
|
13
|
+
export * from './mortgage.entity';
|
|
14
|
+
export * from './password_reset_tokens.entity';
|
|
15
|
+
export * from './permission.entity';
|
|
16
|
+
export * from './property-document.entity';
|
|
17
|
+
export * from './property-media.entity';
|
|
18
|
+
export * from './property.entity';
|
|
19
|
+
export * from './refresh_token.entity';
|
|
20
|
+
export * from './role.entity';
|
|
21
|
+
export * from './settings.entity';
|
|
22
|
+
export * from './social.entity';
|
|
23
|
+
export * from './tenant.entity';
|
|
24
|
+
export * from './transaction.entity';
|
|
25
|
+
export * from './user.entity';
|
|
26
|
+
export * from './user_suspensions.entity';
|
|
27
|
+
export * from './wallet.entity';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
|
2
|
+
import { AbstractBaseReviewableEntity } from './common.entity';
|
|
3
|
+
import { Mortgage } from './mortgage.entity';
|
|
4
|
+
import { User } from './user.entity';
|
|
5
|
+
|
|
6
|
+
@Entity({ name: 'mortgage_document' })
|
|
7
|
+
export class MortgageDocument extends AbstractBaseReviewableEntity {
|
|
8
|
+
@ManyToOne(() => Mortgage, (mortgage) => mortgage.documents, { onDelete: 'CASCADE' })
|
|
9
|
+
@JoinColumn({ name: 'mortgage_id' })
|
|
10
|
+
mortgage: Mortgage;
|
|
11
|
+
|
|
12
|
+
@Column({ nullable: true })
|
|
13
|
+
mortgageId: number;
|
|
14
|
+
|
|
15
|
+
@Column()
|
|
16
|
+
fileName: string;
|
|
17
|
+
|
|
18
|
+
// Allow null URLs for template/placeholder documents created from mortgage type templates.
|
|
19
|
+
@Column({ nullable: true })
|
|
20
|
+
url: string;
|
|
21
|
+
|
|
22
|
+
// Flag to mark this document as a template/placeholder (not yet uploaded). Template docs typically have a name but no URL.
|
|
23
|
+
@Column({ default: false })
|
|
24
|
+
isTemplate: boolean;
|
|
25
|
+
|
|
26
|
+
@Column({ nullable: true })
|
|
27
|
+
mimeType: string;
|
|
28
|
+
|
|
29
|
+
@ManyToOne(() => User, { nullable: true })
|
|
30
|
+
@JoinColumn({ name: 'uploaded_by' })
|
|
31
|
+
uploadedBy: User;
|
|
32
|
+
|
|
33
|
+
@Column({ nullable: true })
|
|
34
|
+
uploadedById: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default MortgageDocument;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
|
2
|
+
import { AbstractBaseEntity } from './common.pure.entity';
|
|
3
|
+
import { MortgageDownpaymentPlan } from './mortgage-downpayment.entity';
|
|
4
|
+
|
|
5
|
+
export enum InstallmentStatus {
|
|
6
|
+
PENDING = 'PENDING',
|
|
7
|
+
PARTIAL = 'PARTIAL',
|
|
8
|
+
PAID = 'PAID',
|
|
9
|
+
LATE = 'LATE',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@Entity({ name: 'mortgage_downpayment_installment' })
|
|
13
|
+
export class MortgageDownpaymentInstallment extends AbstractBaseEntity {
|
|
14
|
+
@ManyToOne(() => MortgageDownpaymentPlan, (p) => p.installments, { onDelete: 'CASCADE' })
|
|
15
|
+
@JoinColumn({ name: 'plan_id' })
|
|
16
|
+
plan: MortgageDownpaymentPlan;
|
|
17
|
+
|
|
18
|
+
@Column({ nullable: true })
|
|
19
|
+
planId: number;
|
|
20
|
+
|
|
21
|
+
@Column({ type: 'int' })
|
|
22
|
+
sequence: number;
|
|
23
|
+
|
|
24
|
+
@Column({ type: 'date' })
|
|
25
|
+
dueDate: Date;
|
|
26
|
+
|
|
27
|
+
@Column({ type: 'double precision' })
|
|
28
|
+
amountDue: number;
|
|
29
|
+
|
|
30
|
+
@Column({ type: 'double precision', default: 0 })
|
|
31
|
+
amountPaid: number;
|
|
32
|
+
|
|
33
|
+
@Column({ type: 'timestamp', nullable: true })
|
|
34
|
+
paidAt: Date;
|
|
35
|
+
|
|
36
|
+
@Column({ type: 'enum', enum: InstallmentStatus, default: InstallmentStatus.PENDING })
|
|
37
|
+
status: InstallmentStatus;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default MortgageDownpaymentInstallment;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
|
2
|
+
import { AbstractBaseEntity } from './common.pure.entity';
|
|
3
|
+
import { MortgageDownpaymentPlan } from './mortgage-downpayment.entity';
|
|
4
|
+
import { MortgageDownpaymentInstallment } from './mortgage-downpayment-installment.entity';
|
|
5
|
+
import { User } from './user.entity';
|
|
6
|
+
|
|
7
|
+
// Legacy enum - kept for backward compatibility
|
|
8
|
+
export enum DownpaymentPaymentStatus {
|
|
9
|
+
PENDING = 'PENDING',
|
|
10
|
+
COMPLETED = 'COMPLETED',
|
|
11
|
+
FAILED = 'FAILED',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Payment state for FSM tracking
|
|
15
|
+
export enum PaymentState {
|
|
16
|
+
INITIATED = 'INITIATED',
|
|
17
|
+
COMPLETED = 'COMPLETED',
|
|
18
|
+
FAILED = 'FAILED',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Entity({ name: 'mortgage_downpayment_payment' })
|
|
22
|
+
export class MortgageDownpaymentPayment extends AbstractBaseEntity {
|
|
23
|
+
@ManyToOne(() => MortgageDownpaymentPlan, { onDelete: 'CASCADE' })
|
|
24
|
+
@JoinColumn({ name: 'plan_id' })
|
|
25
|
+
plan: MortgageDownpaymentPlan;
|
|
26
|
+
|
|
27
|
+
@Column({ nullable: true })
|
|
28
|
+
planId: number;
|
|
29
|
+
|
|
30
|
+
@ManyToOne(() => MortgageDownpaymentInstallment, { nullable: true })
|
|
31
|
+
@JoinColumn({ name: 'installment_id' })
|
|
32
|
+
installment: MortgageDownpaymentInstallment;
|
|
33
|
+
|
|
34
|
+
@Column({ nullable: true })
|
|
35
|
+
installmentId: number;
|
|
36
|
+
|
|
37
|
+
@ManyToOne(() => User, { nullable: true })
|
|
38
|
+
@JoinColumn({ name: 'payer_id' })
|
|
39
|
+
payer: User;
|
|
40
|
+
|
|
41
|
+
@Column({ nullable: true })
|
|
42
|
+
payerId: number;
|
|
43
|
+
|
|
44
|
+
@Column({ type: 'double precision' })
|
|
45
|
+
amount: number;
|
|
46
|
+
|
|
47
|
+
@Column({ nullable: true, unique: true })
|
|
48
|
+
providerReference: string;
|
|
49
|
+
|
|
50
|
+
@Column({ type: 'enum', enum: DownpaymentPaymentStatus, default: DownpaymentPaymentStatus.PENDING })
|
|
51
|
+
status: DownpaymentPaymentStatus;
|
|
52
|
+
|
|
53
|
+
// FSM state tracking
|
|
54
|
+
@Column({ type: 'enum', enum: PaymentState, default: PaymentState.INITIATED })
|
|
55
|
+
state: PaymentState;
|
|
56
|
+
|
|
57
|
+
@Column({ type: 'text', nullable: true })
|
|
58
|
+
stateMetadata: string; // JSON metadata for state transitions
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default MortgageDownpaymentPayment;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
|
|
2
|
+
import { AbstractBaseEntity } from './common.pure.entity';
|
|
3
|
+
import { Mortgage } from './mortgage.entity';
|
|
4
|
+
import { MortgageDownpaymentInstallment } from './mortgage-downpayment-installment.entity';
|
|
5
|
+
import { Frequency } from '../types/common.type';
|
|
6
|
+
|
|
7
|
+
export enum DownpaymentPlanStatus {
|
|
8
|
+
PENDING = 'PENDING',
|
|
9
|
+
ACTIVE = 'ACTIVE',
|
|
10
|
+
COMPLETED = 'COMPLETED',
|
|
11
|
+
DEFAULTED = 'DEFAULTED',
|
|
12
|
+
CANCELLED = 'CANCELLED',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Entity({ name: 'mortgage_downpayment_plan' })
|
|
16
|
+
export class MortgageDownpaymentPlan extends AbstractBaseEntity {
|
|
17
|
+
@ManyToOne(() => Mortgage, { onDelete: 'CASCADE' })
|
|
18
|
+
@JoinColumn({ name: 'mortgage_id' })
|
|
19
|
+
mortgage: Mortgage;
|
|
20
|
+
|
|
21
|
+
@Column({ nullable: true })
|
|
22
|
+
mortgageId: number;
|
|
23
|
+
|
|
24
|
+
@Column({ type: 'double precision' })
|
|
25
|
+
totalAmount: number;
|
|
26
|
+
|
|
27
|
+
@Column({ type: 'int', nullable: true })
|
|
28
|
+
installmentCount: number;
|
|
29
|
+
|
|
30
|
+
@Column({ nullable: true, type: 'enum', enum: Frequency })
|
|
31
|
+
frequency: Frequency;
|
|
32
|
+
|
|
33
|
+
@Column({ type: 'date', nullable: true })
|
|
34
|
+
startDate: Date;
|
|
35
|
+
|
|
36
|
+
@Column({ type: 'enum', enum: DownpaymentPlanStatus, default: DownpaymentPlanStatus.PENDING })
|
|
37
|
+
status: DownpaymentPlanStatus;
|
|
38
|
+
|
|
39
|
+
@OneToMany(() => MortgageDownpaymentInstallment, (i) => i.plan, { cascade: true })
|
|
40
|
+
installments: MortgageDownpaymentInstallment[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default MortgageDownpaymentPlan;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
|
|
2
|
+
import { AbstractBaseReviewableEntity } from './common.entity';
|
|
3
|
+
import { Mortgage } from './mortgage.entity';
|
|
4
|
+
|
|
5
|
+
@Entity({ name: 'mortgage_step' })
|
|
6
|
+
export class MortgageStep extends AbstractBaseReviewableEntity {
|
|
7
|
+
@ManyToOne(() => Mortgage, (mortgage) => mortgage.steps, { onDelete: 'CASCADE' })
|
|
8
|
+
@JoinColumn({ name: 'mortgage_id' })
|
|
9
|
+
mortgage: Mortgage;
|
|
10
|
+
|
|
11
|
+
@Column({ nullable: true })
|
|
12
|
+
mortgageId: number;
|
|
13
|
+
|
|
14
|
+
@Column()
|
|
15
|
+
title: string;
|
|
16
|
+
|
|
17
|
+
@Column({ type: 'text', nullable: true })
|
|
18
|
+
description: string;
|
|
19
|
+
|
|
20
|
+
// Sequence order - easier to query and reorder than relying purely on linked-list pointers
|
|
21
|
+
@Column({ type: 'int', default: 0 })
|
|
22
|
+
sequence: number;
|
|
23
|
+
|
|
24
|
+
// We use `sequence` for ordering. Removed linked-list pointer fields (nextStep/nextStepId) to reduce redundancy.
|
|
25
|
+
|
|
26
|
+
@Column({ default: false })
|
|
27
|
+
isOptional: boolean;
|
|
28
|
+
|
|
29
|
+
@Column({ nullable: true })
|
|
30
|
+
completedAt: Date;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default MortgageStep;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Column, Entity } from 'typeorm';
|
|
2
|
+
import { AbstractBaseReviewableEntity } from './common.entity';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MortgageType stores named mortgage configurations. Examples: 'Standard Fixed', 'Interest Only', 'Buy-to-Let'.
|
|
6
|
+
* - defaultSteps: array of step templates { title, description?, sequence?, isOptional? }
|
|
7
|
+
* - requiredDocuments: array of document templates { name, required: boolean }
|
|
8
|
+
* - config: free-form JSON for additional settings
|
|
9
|
+
*/
|
|
10
|
+
@Entity({ name: 'mortgage_type' })
|
|
11
|
+
export class MortgageType extends AbstractBaseReviewableEntity {
|
|
12
|
+
@Column()
|
|
13
|
+
name: string;
|
|
14
|
+
|
|
15
|
+
@Column({ nullable: true })
|
|
16
|
+
slug: string;
|
|
17
|
+
|
|
18
|
+
@Column({ type: 'text', nullable: true })
|
|
19
|
+
description?: string;
|
|
20
|
+
|
|
21
|
+
@Column({ type: 'json', nullable: true })
|
|
22
|
+
defaultSteps?: any[];
|
|
23
|
+
|
|
24
|
+
@Column({ type: 'json', nullable: true })
|
|
25
|
+
requiredDocuments?: any[];
|
|
26
|
+
|
|
27
|
+
@Column({ type: 'json', nullable: true })
|
|
28
|
+
config?: any;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default MortgageType;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne } from 'typeorm';
|
|
2
|
+
import { AbstractBaseReviewableEntity } from './common.entity';
|
|
3
|
+
import { Property } from './property.entity';
|
|
4
|
+
import { User } from './user.entity';
|
|
5
|
+
import MortgageDocument from './mortgage-document.entity';
|
|
6
|
+
import MortgageStep from './mortgage-step.entity';
|
|
7
|
+
import { MortgageType } from './mortgage-type.entity';
|
|
8
|
+
import { MortgageDownpaymentPlan } from './mortgage-downpayment.entity';
|
|
9
|
+
import { MortgageState } from '../types/mortgage-fsm.types';
|
|
10
|
+
|
|
11
|
+
// Legacy enum - kept for backward compatibility
|
|
12
|
+
export enum MortgageStatus {
|
|
13
|
+
DRAFT = 'DRAFT',
|
|
14
|
+
PENDING = 'PENDING',
|
|
15
|
+
ACTIVE = 'ACTIVE',
|
|
16
|
+
COMPLETED = 'COMPLETED',
|
|
17
|
+
CANCELLED = 'CANCELLED',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Entity({ name: 'mortgage' })
|
|
21
|
+
export class Mortgage extends AbstractBaseReviewableEntity {
|
|
22
|
+
@ManyToOne(() => Property, (property) => property.mortgages, { onDelete: 'CASCADE' })
|
|
23
|
+
@JoinColumn({ name: 'property_id' })
|
|
24
|
+
property: Property;
|
|
25
|
+
|
|
26
|
+
@Column({ nullable: true })
|
|
27
|
+
propertyId: number;
|
|
28
|
+
|
|
29
|
+
@ManyToOne(() => User, { nullable: true })
|
|
30
|
+
@JoinColumn({ name: 'borrower_id' })
|
|
31
|
+
borrower: User;
|
|
32
|
+
|
|
33
|
+
@Column({ nullable: true })
|
|
34
|
+
borrowerId: number;
|
|
35
|
+
|
|
36
|
+
@Column({ type: 'double precision', nullable: true })
|
|
37
|
+
principal: number;
|
|
38
|
+
|
|
39
|
+
@Column({ type: 'double precision', nullable: true })
|
|
40
|
+
downPayment: number;
|
|
41
|
+
|
|
42
|
+
@Column({ type: 'int', nullable: true })
|
|
43
|
+
termMonths: number;
|
|
44
|
+
|
|
45
|
+
@Column({ type: 'double precision', nullable: true })
|
|
46
|
+
interestRate: number;
|
|
47
|
+
|
|
48
|
+
@Column({ type: 'double precision', nullable: true })
|
|
49
|
+
monthlyPayment: number;
|
|
50
|
+
|
|
51
|
+
@Column({ type: 'enum', enum: MortgageStatus, default: MortgageStatus.DRAFT })
|
|
52
|
+
status: MortgageStatus;
|
|
53
|
+
|
|
54
|
+
// FSM State - Primary state tracking
|
|
55
|
+
@Column({ type: 'varchar', default: MortgageState.DRAFT })
|
|
56
|
+
state: string; // Using string to store MortgageState enum values
|
|
57
|
+
|
|
58
|
+
// FSM Metadata - Stores transition history and context
|
|
59
|
+
@Column({ type: 'text', nullable: true })
|
|
60
|
+
stateMetadata: string; // JSON string containing last transition info
|
|
61
|
+
|
|
62
|
+
@OneToMany(() => MortgageDocument, (doc) => doc.mortgage)
|
|
63
|
+
documents: MortgageDocument[];
|
|
64
|
+
|
|
65
|
+
@OneToMany(() => MortgageStep, (step) => step.mortgage, { cascade: true })
|
|
66
|
+
steps: MortgageStep[];
|
|
67
|
+
|
|
68
|
+
@ManyToOne(() => MortgageType, { nullable: true })
|
|
69
|
+
@JoinColumn({ name: 'mortgage_type_id' })
|
|
70
|
+
mortgageType: MortgageType;
|
|
71
|
+
|
|
72
|
+
@Column({ nullable: true })
|
|
73
|
+
mortgageTypeId: number;
|
|
74
|
+
|
|
75
|
+
@Column({ type: 'timestamp', nullable: true })
|
|
76
|
+
lastReminderSentAt: Date;
|
|
77
|
+
|
|
78
|
+
@OneToOne(() => MortgageDownpaymentPlan, { nullable: true })
|
|
79
|
+
@JoinColumn({ name: 'downpayment_plan_id' })
|
|
80
|
+
downpaymentPlan: MortgageDownpaymentPlan;
|
|
81
|
+
|
|
82
|
+
@Column({ nullable: true })
|
|
83
|
+
downpaymentPlanId: number;
|
|
84
|
+
|
|
85
|
+
@Column({ type: 'double precision', nullable: true })
|
|
86
|
+
downPaymentPaid: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default Mortgage;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Column, DeleteDateColumn, Entity, JoinColumn, OneToOne } from 'typeorm';
|
|
2
|
+
import { User } from './user.entity';
|
|
3
|
+
import { BaseEntity } from './BaseEntity';
|
|
4
|
+
|
|
5
|
+
@Entity({ name: 'password_reset_token' })
|
|
6
|
+
export class PasswordResetToken extends BaseEntity {
|
|
7
|
+
@OneToOne(() => User, {
|
|
8
|
+
//eager: true,
|
|
9
|
+
onDelete: 'CASCADE',
|
|
10
|
+
onUpdate: 'CASCADE',
|
|
11
|
+
})
|
|
12
|
+
@JoinColumn({ name: 'user_id' })
|
|
13
|
+
user: User;
|
|
14
|
+
|
|
15
|
+
@Column({
|
|
16
|
+
nullable: true
|
|
17
|
+
})
|
|
18
|
+
tokenHash: string
|
|
19
|
+
|
|
20
|
+
@DeleteDateColumn({
|
|
21
|
+
nullable: true,
|
|
22
|
+
default: null
|
|
23
|
+
})
|
|
24
|
+
expiresAt: Date;
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Column, Entity, ManyToMany } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from './BaseEntity';
|
|
3
|
+
import { Role } from './role.entity';
|
|
4
|
+
|
|
5
|
+
@Entity({ name: 'permissions' })
|
|
6
|
+
export class Permission extends BaseEntity {
|
|
7
|
+
@Column({ type: 'varchar', nullable: true })
|
|
8
|
+
name: string;
|
|
9
|
+
|
|
10
|
+
@ManyToMany(() => Role, role => role.permissions)
|
|
11
|
+
roles: Role[]
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Column,
|
|
3
|
+
Entity,
|
|
4
|
+
JoinColumn,
|
|
5
|
+
ManyToOne,
|
|
6
|
+
} from 'typeorm';
|
|
7
|
+
import { Property } from './property.entity';
|
|
8
|
+
import { AbstractBaseDocumentEntity } from './common.entity';
|
|
9
|
+
|
|
10
|
+
@Entity({ name: 'property-document' })
|
|
11
|
+
export class PropertyDocument extends AbstractBaseDocumentEntity {
|
|
12
|
+
@ManyToOne(() => Property, (property) => property.documents, {
|
|
13
|
+
onDelete: 'CASCADE',
|
|
14
|
+
onUpdate: 'CASCADE',
|
|
15
|
+
})
|
|
16
|
+
@JoinColumn({ name: 'property_id' })
|
|
17
|
+
property: Property;
|
|
18
|
+
|
|
19
|
+
@Column()
|
|
20
|
+
propertyId: number;
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Column,
|
|
3
|
+
Entity,
|
|
4
|
+
JoinColumn,
|
|
5
|
+
ManyToOne,
|
|
6
|
+
} from 'typeorm';
|
|
7
|
+
import { Property } from './property.entity';
|
|
8
|
+
import { AbstractBaseMediaEntity } from './common.entity';
|
|
9
|
+
|
|
10
|
+
@Entity({ name: 'property_media' })
|
|
11
|
+
export class PropertyMedia extends AbstractBaseMediaEntity {
|
|
12
|
+
@ManyToOne(() => Property, (property) => property.media, {
|
|
13
|
+
onDelete: 'CASCADE',
|
|
14
|
+
onUpdate: 'CASCADE',
|
|
15
|
+
})
|
|
16
|
+
@JoinColumn({ name: 'property_id' })
|
|
17
|
+
property: Property;
|
|
18
|
+
|
|
19
|
+
@Column({
|
|
20
|
+
nullable: false
|
|
21
|
+
})
|
|
22
|
+
propertyId: number
|
|
23
|
+
}
|