vr-models 1.0.15 → 1.0.17
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/dist/models/device.models.d.ts +8 -5
- package/dist/models/device.models.js +20 -7
- package/dist/models/devicePaymentPlan.models.d.ts +15 -24
- package/dist/models/devicePaymentPlan.models.js +75 -66
- package/dist/models/eventLog.models.d.ts +1 -1
- package/dist/models/eventLog.models.js +3 -2
- package/dist/models/index.d.ts +2 -1
- package/dist/models/index.js +1 -0
- package/dist/models/installment.models.d.ts +59 -0
- package/dist/models/installment.models.js +181 -0
- package/dist/models/pricing.models.d.ts +18 -7
- package/dist/models/pricing.models.js +52 -7
- package/dist/models/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -3,12 +3,11 @@ import type { Product } from "./product.models";
|
|
|
3
3
|
import type { DevicePaymentPlan } from "./devicePaymentPlan.models";
|
|
4
4
|
export type DeviceStatus = "locked" | "unlocked" | "disabled";
|
|
5
5
|
export type DeviceDedication = "PASSENGER" | "RIDER" | "N/A";
|
|
6
|
-
export declare const DEVICE_STATUS: readonly ["locked", "unlocked", "disabled"];
|
|
7
|
-
export declare const DEDICATED_USER: readonly ["RIDER", "PASSENGER", "N/A"];
|
|
8
6
|
export interface DeviceAttributes {
|
|
9
7
|
id: string;
|
|
10
8
|
serialNumber: string;
|
|
11
9
|
productId: string;
|
|
10
|
+
devicePaymentPlanId: string | null;
|
|
12
11
|
status: DeviceStatus;
|
|
13
12
|
isPermanentlyUnlocked: boolean;
|
|
14
13
|
dedicatedUser: DeviceDedication;
|
|
@@ -16,17 +15,19 @@ export interface DeviceAttributes {
|
|
|
16
15
|
createdAt: Date;
|
|
17
16
|
updatedAt: Date;
|
|
18
17
|
product?: Product;
|
|
19
|
-
|
|
18
|
+
paymentPlan?: DevicePaymentPlan;
|
|
20
19
|
}
|
|
21
|
-
export interface DeviceCreationAttributes extends Omit<DeviceAttributes, "id" | "createdAt" | "updatedAt" | "status" | "isPermanentlyUnlocked"> {
|
|
20
|
+
export interface DeviceCreationAttributes extends Omit<DeviceAttributes, "id" | "createdAt" | "updatedAt" | "status" | "isPermanentlyUnlocked" | "devicePaymentPlanId"> {
|
|
22
21
|
id?: string;
|
|
23
22
|
status?: DeviceStatus;
|
|
24
23
|
isPermanentlyUnlocked?: boolean;
|
|
24
|
+
devicePaymentPlanId?: string | null;
|
|
25
25
|
}
|
|
26
26
|
export declare class Device extends Model<InferAttributes<Device>, InferCreationAttributes<Device>> implements DeviceAttributes {
|
|
27
27
|
id: CreationOptional<string>;
|
|
28
28
|
serialNumber: string;
|
|
29
29
|
productId: string;
|
|
30
|
+
devicePaymentPlanId: CreationOptional<string | null>;
|
|
30
31
|
status: CreationOptional<DeviceStatus>;
|
|
31
32
|
isPermanentlyUnlocked: CreationOptional<boolean>;
|
|
32
33
|
dedicatedUser: DeviceDedication;
|
|
@@ -34,7 +35,7 @@ export declare class Device extends Model<InferAttributes<Device>, InferCreation
|
|
|
34
35
|
readonly createdAt: CreationOptional<Date>;
|
|
35
36
|
readonly updatedAt: CreationOptional<Date>;
|
|
36
37
|
product?: NonAttribute<Product>;
|
|
37
|
-
|
|
38
|
+
paymentPlan?: NonAttribute<DevicePaymentPlan>;
|
|
38
39
|
static initialize(sequelize: any): void;
|
|
39
40
|
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
40
41
|
activate(): Promise<void>;
|
|
@@ -44,5 +45,7 @@ export declare class Device extends Model<InferAttributes<Device>, InferCreation
|
|
|
44
45
|
makePermanent(): Promise<void>;
|
|
45
46
|
isActive(): boolean;
|
|
46
47
|
canBeActivated(): boolean;
|
|
48
|
+
isAssigned(): boolean;
|
|
49
|
+
getPaymentPlan(): Promise<DevicePaymentPlan | null>;
|
|
47
50
|
}
|
|
48
51
|
export type DeviceModel = typeof Device;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Device =
|
|
3
|
+
exports.Device = void 0;
|
|
4
4
|
// src/models/device.models.ts
|
|
5
5
|
const vr_migrations_1 = require("vr-migrations");
|
|
6
|
-
exports.DEVICE_STATUS = ["locked", "unlocked", "disabled"];
|
|
7
|
-
exports.DEDICATED_USER = ["RIDER", "PASSENGER", "N/A"];
|
|
8
6
|
class Device extends vr_migrations_1.Model {
|
|
9
7
|
// Static initialization method
|
|
10
8
|
static initialize(sequelize) {
|
|
@@ -23,6 +21,10 @@ class Device extends vr_migrations_1.Model {
|
|
|
23
21
|
type: vr_migrations_1.DataTypes.UUID,
|
|
24
22
|
allowNull: false,
|
|
25
23
|
},
|
|
24
|
+
devicePaymentPlanId: {
|
|
25
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
26
|
+
allowNull: true,
|
|
27
|
+
},
|
|
26
28
|
status: {
|
|
27
29
|
type: vr_migrations_1.DataTypes.ENUM("locked", "unlocked", "disabled"),
|
|
28
30
|
allowNull: false,
|
|
@@ -66,10 +68,10 @@ class Device extends vr_migrations_1.Model {
|
|
|
66
68
|
onDelete: "RESTRICT",
|
|
67
69
|
onUpdate: "CASCADE",
|
|
68
70
|
});
|
|
69
|
-
this.
|
|
70
|
-
foreignKey: "
|
|
71
|
-
as: "
|
|
72
|
-
onDelete: "
|
|
71
|
+
this.belongsTo(models.DevicePaymentPlan, {
|
|
72
|
+
foreignKey: "devicePaymentPlanId",
|
|
73
|
+
as: "paymentPlan",
|
|
74
|
+
onDelete: "SET NULL",
|
|
73
75
|
onUpdate: "CASCADE",
|
|
74
76
|
});
|
|
75
77
|
}
|
|
@@ -101,5 +103,16 @@ class Device extends vr_migrations_1.Model {
|
|
|
101
103
|
canBeActivated() {
|
|
102
104
|
return this.status !== "disabled";
|
|
103
105
|
}
|
|
106
|
+
isAssigned() {
|
|
107
|
+
return !!this.devicePaymentPlanId;
|
|
108
|
+
}
|
|
109
|
+
// Helper to get payment plan
|
|
110
|
+
async getPaymentPlan() {
|
|
111
|
+
if (!this.devicePaymentPlanId)
|
|
112
|
+
return null;
|
|
113
|
+
const DevicePaymentPlan = this.constructor.sequelize.models
|
|
114
|
+
.DevicePaymentPlan;
|
|
115
|
+
return await DevicePaymentPlan.findByPk(this.devicePaymentPlanId);
|
|
116
|
+
}
|
|
104
117
|
}
|
|
105
118
|
exports.Device = Device;
|
|
@@ -1,66 +1,57 @@
|
|
|
1
1
|
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
|
|
2
2
|
import type { Device } from "./device.models";
|
|
3
3
|
import type { User } from "./user.models";
|
|
4
|
+
import type { Installment } from "./installment.models";
|
|
4
5
|
export type PaymentPlanStatus = "ACTIVE" | "COMPLETED" | "DEFAULTED" | "CANCELLED";
|
|
5
|
-
export type InstallmentFrequency = "DAILY" | "WEEKLY" | "MONTHLY";
|
|
6
6
|
export interface DevicePaymentPlanAttributes {
|
|
7
7
|
id: string;
|
|
8
|
-
deviceId: string;
|
|
9
8
|
userId: string;
|
|
10
9
|
pricingSnapshot: Record<string, any>;
|
|
11
|
-
totalAmount: number;
|
|
12
|
-
downPayment: number;
|
|
13
|
-
installmentAmount: number;
|
|
14
|
-
installmentFrequency: InstallmentFrequency;
|
|
15
10
|
paidAmount: number;
|
|
16
11
|
outstandingAmount: number;
|
|
17
12
|
lastPaymentAt: Date | null;
|
|
18
13
|
nextInstallmentDueAt: Date | null;
|
|
19
|
-
gracePeriodDays: number;
|
|
20
|
-
autoLockOnMiss: boolean;
|
|
21
14
|
status: PaymentPlanStatus;
|
|
22
15
|
completedAt: Date | null;
|
|
23
16
|
createdAt: Date;
|
|
24
17
|
updatedAt: Date;
|
|
25
|
-
|
|
18
|
+
devices?: Device[];
|
|
26
19
|
user?: User;
|
|
20
|
+
installments?: Installment[];
|
|
27
21
|
}
|
|
28
|
-
export interface DevicePaymentPlanCreationAttributes extends Omit<DevicePaymentPlanAttributes, "id" | "createdAt" | "updatedAt" | "paidAmount" | "status" | "
|
|
22
|
+
export interface DevicePaymentPlanCreationAttributes extends Omit<DevicePaymentPlanAttributes, "id" | "createdAt" | "updatedAt" | "paidAmount" | "outstandingAmount" | "lastPaymentAt" | "nextInstallmentDueAt" | "status" | "completedAt" | "devices" | "installments"> {
|
|
29
23
|
id?: string;
|
|
30
24
|
paidAmount?: number;
|
|
25
|
+
outstandingAmount?: number;
|
|
31
26
|
status?: PaymentPlanStatus;
|
|
32
|
-
gracePeriodDays?: number;
|
|
33
|
-
autoLockOnMiss?: boolean;
|
|
34
27
|
}
|
|
35
28
|
export declare class DevicePaymentPlan extends Model<InferAttributes<DevicePaymentPlan>, InferCreationAttributes<DevicePaymentPlan>> implements DevicePaymentPlanAttributes {
|
|
36
29
|
id: CreationOptional<string>;
|
|
37
|
-
deviceId: string;
|
|
38
30
|
userId: string;
|
|
39
31
|
pricingSnapshot: Record<string, any>;
|
|
40
|
-
totalAmount: number;
|
|
41
|
-
downPayment: number;
|
|
42
|
-
installmentAmount: number;
|
|
43
|
-
installmentFrequency: InstallmentFrequency;
|
|
44
32
|
paidAmount: CreationOptional<number>;
|
|
45
33
|
outstandingAmount: number;
|
|
46
34
|
lastPaymentAt: CreationOptional<Date | null>;
|
|
47
35
|
nextInstallmentDueAt: CreationOptional<Date | null>;
|
|
48
|
-
gracePeriodDays: CreationOptional<number>;
|
|
49
|
-
autoLockOnMiss: CreationOptional<boolean>;
|
|
50
36
|
status: CreationOptional<PaymentPlanStatus>;
|
|
51
37
|
completedAt: CreationOptional<Date | null>;
|
|
52
38
|
readonly createdAt: CreationOptional<Date>;
|
|
53
39
|
readonly updatedAt: CreationOptional<Date>;
|
|
54
|
-
|
|
40
|
+
devices?: NonAttribute<Device[]>;
|
|
55
41
|
user?: NonAttribute<User>;
|
|
42
|
+
installments?: NonAttribute<Installment[]>;
|
|
56
43
|
static initialize(sequelize: any): void;
|
|
57
44
|
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
58
|
-
|
|
45
|
+
updateProgress(): Promise<void>;
|
|
59
46
|
markAsDefaulted(): Promise<void>;
|
|
60
47
|
cancel(): Promise<void>;
|
|
61
48
|
calculateProgress(): number;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
getOverdueStatus(): Promise<{
|
|
50
|
+
isOverdue: boolean;
|
|
51
|
+
daysOverdue: number;
|
|
52
|
+
}>;
|
|
53
|
+
shouldAutoLock(): Promise<boolean>;
|
|
54
|
+
getDevices(): Promise<any[]>;
|
|
55
|
+
getInstallments(options?: any): Promise<any[]>;
|
|
65
56
|
}
|
|
66
57
|
export type DevicePaymentPlanModel = typeof DevicePaymentPlan;
|
|
@@ -12,11 +12,6 @@ class DevicePaymentPlan extends vr_migrations_1.Model {
|
|
|
12
12
|
defaultValue: vr_migrations_1.DataTypes.UUIDV4,
|
|
13
13
|
primaryKey: true,
|
|
14
14
|
},
|
|
15
|
-
deviceId: {
|
|
16
|
-
type: vr_migrations_1.DataTypes.UUID,
|
|
17
|
-
allowNull: false,
|
|
18
|
-
unique: true,
|
|
19
|
-
},
|
|
20
15
|
userId: {
|
|
21
16
|
type: vr_migrations_1.DataTypes.UUID,
|
|
22
17
|
allowNull: false,
|
|
@@ -25,23 +20,6 @@ class DevicePaymentPlan extends vr_migrations_1.Model {
|
|
|
25
20
|
type: vr_migrations_1.DataTypes.JSONB,
|
|
26
21
|
allowNull: false,
|
|
27
22
|
},
|
|
28
|
-
totalAmount: {
|
|
29
|
-
type: vr_migrations_1.DataTypes.FLOAT,
|
|
30
|
-
allowNull: false,
|
|
31
|
-
},
|
|
32
|
-
downPayment: {
|
|
33
|
-
type: vr_migrations_1.DataTypes.FLOAT,
|
|
34
|
-
allowNull: false,
|
|
35
|
-
},
|
|
36
|
-
installmentAmount: {
|
|
37
|
-
type: vr_migrations_1.DataTypes.FLOAT,
|
|
38
|
-
allowNull: false,
|
|
39
|
-
},
|
|
40
|
-
installmentFrequency: {
|
|
41
|
-
type: vr_migrations_1.DataTypes.ENUM("DAILY", "WEEKLY", "MONTHLY"),
|
|
42
|
-
allowNull: false,
|
|
43
|
-
defaultValue: "WEEKLY",
|
|
44
|
-
},
|
|
45
23
|
paidAmount: {
|
|
46
24
|
type: vr_migrations_1.DataTypes.FLOAT,
|
|
47
25
|
allowNull: false,
|
|
@@ -59,16 +37,6 @@ class DevicePaymentPlan extends vr_migrations_1.Model {
|
|
|
59
37
|
type: vr_migrations_1.DataTypes.DATE,
|
|
60
38
|
allowNull: true,
|
|
61
39
|
},
|
|
62
|
-
gracePeriodDays: {
|
|
63
|
-
type: vr_migrations_1.DataTypes.INTEGER,
|
|
64
|
-
allowNull: false,
|
|
65
|
-
defaultValue: 2,
|
|
66
|
-
},
|
|
67
|
-
autoLockOnMiss: {
|
|
68
|
-
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
69
|
-
allowNull: false,
|
|
70
|
-
defaultValue: true,
|
|
71
|
-
},
|
|
72
40
|
status: {
|
|
73
41
|
type: vr_migrations_1.DataTypes.ENUM("ACTIVE", "COMPLETED", "DEFAULTED", "CANCELLED"),
|
|
74
42
|
allowNull: false,
|
|
@@ -96,71 +64,112 @@ class DevicePaymentPlan extends vr_migrations_1.Model {
|
|
|
96
64
|
}
|
|
97
65
|
// Static association method
|
|
98
66
|
static associate(models) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
67
|
+
// One-to-many with Devices
|
|
68
|
+
this.hasMany(models.Device, {
|
|
69
|
+
foreignKey: "devicePaymentPlanId",
|
|
70
|
+
as: "devices",
|
|
71
|
+
onDelete: "SET NULL",
|
|
103
72
|
onUpdate: "CASCADE",
|
|
104
73
|
});
|
|
74
|
+
// Belongs to User
|
|
105
75
|
this.belongsTo(models.User, {
|
|
106
76
|
foreignKey: "userId",
|
|
107
77
|
as: "user",
|
|
78
|
+
onDelete: "RESTRICT",
|
|
79
|
+
onUpdate: "CASCADE",
|
|
80
|
+
});
|
|
81
|
+
// One-to-many with Installments
|
|
82
|
+
this.hasMany(models.Installment, {
|
|
83
|
+
foreignKey: "devicePaymentPlanId",
|
|
84
|
+
as: "installments",
|
|
108
85
|
onDelete: "CASCADE",
|
|
109
86
|
onUpdate: "CASCADE",
|
|
110
87
|
});
|
|
111
88
|
}
|
|
112
89
|
// Custom instance methods
|
|
113
|
-
async
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
90
|
+
async updateProgress() {
|
|
91
|
+
// Use Sequelize's built-in methods instead of mixins
|
|
92
|
+
const Installment = this.constructor.sequelize.models.Installment;
|
|
93
|
+
const installments = await Installment.findAll({
|
|
94
|
+
where: {
|
|
95
|
+
devicePaymentPlanId: this.id,
|
|
96
|
+
status: "PAID",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
this.paidAmount = installments.reduce((sum, inst) => sum + inst.amount, 0);
|
|
100
|
+
this.outstandingAmount = this.pricingSnapshot.totalAmount - this.paidAmount;
|
|
101
|
+
// Find next pending installment
|
|
102
|
+
const nextPending = await Installment.findOne({
|
|
103
|
+
where: {
|
|
104
|
+
devicePaymentPlanId: this.id,
|
|
105
|
+
status: "PENDING",
|
|
106
|
+
},
|
|
107
|
+
order: [["dueDate", "ASC"]],
|
|
108
|
+
});
|
|
109
|
+
this.nextInstallmentDueAt = nextPending?.dueDate || null;
|
|
127
110
|
// Check if completed
|
|
128
111
|
if (this.outstandingAmount <= 0) {
|
|
129
112
|
this.status = "COMPLETED";
|
|
130
113
|
this.completedAt = new Date();
|
|
114
|
+
// Permanently unlock all devices
|
|
115
|
+
const Device = this.constructor.sequelize.models.Device;
|
|
116
|
+
await Device.update({
|
|
117
|
+
isPermanentlyUnlocked: true,
|
|
118
|
+
status: "unlocked",
|
|
119
|
+
}, { where: { devicePaymentPlanId: this.id } });
|
|
131
120
|
}
|
|
132
121
|
await this.save();
|
|
133
122
|
}
|
|
134
123
|
async markAsDefaulted() {
|
|
135
124
|
this.status = "DEFAULTED";
|
|
136
125
|
await this.save();
|
|
126
|
+
// Lock all devices
|
|
127
|
+
const Device = this.constructor.sequelize.models.Device;
|
|
128
|
+
await Device.update({ status: "locked" }, { where: { devicePaymentPlanId: this.id } });
|
|
137
129
|
}
|
|
138
130
|
async cancel() {
|
|
139
131
|
this.status = "CANCELLED";
|
|
140
132
|
await this.save();
|
|
141
133
|
}
|
|
142
134
|
calculateProgress() {
|
|
143
|
-
return (this.paidAmount / this.totalAmount) * 100;
|
|
135
|
+
return (this.paidAmount / this.pricingSnapshot.totalAmount) * 100;
|
|
144
136
|
}
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
return false;
|
|
137
|
+
async getOverdueStatus() {
|
|
138
|
+
if (this.status !== "ACTIVE" || !this.nextInstallmentDueAt) {
|
|
139
|
+
return { isOverdue: false, daysOverdue: 0 };
|
|
148
140
|
}
|
|
141
|
+
const graceDays = this.pricingSnapshot.gracePeriodDays || 2;
|
|
149
142
|
const overdueDate = new Date(this.nextInstallmentDueAt);
|
|
150
|
-
overdueDate.setDate(overdueDate.getDate() +
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return
|
|
143
|
+
overdueDate.setDate(overdueDate.getDate() + graceDays);
|
|
144
|
+
const now = new Date();
|
|
145
|
+
if (now > overdueDate) {
|
|
146
|
+
const diffTime = Math.abs(now.getTime() - overdueDate.getTime());
|
|
147
|
+
const daysOverdue = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
148
|
+
return { isOverdue: true, daysOverdue };
|
|
156
149
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
150
|
+
return { isOverdue: false, daysOverdue: 0 };
|
|
151
|
+
}
|
|
152
|
+
async shouldAutoLock() {
|
|
153
|
+
if (!this.pricingSnapshot.autoLockOnMiss)
|
|
154
|
+
return false;
|
|
155
|
+
const { isOverdue, daysOverdue } = await this.getOverdueStatus();
|
|
156
|
+
const graceDays = this.pricingSnapshot.gracePeriodDays || 2;
|
|
157
|
+
return isOverdue && daysOverdue > graceDays;
|
|
158
|
+
}
|
|
159
|
+
// Helper to get devices (using Sequelize directly)
|
|
160
|
+
async getDevices() {
|
|
161
|
+
const Device = this.constructor.sequelize.models.Device;
|
|
162
|
+
return await Device.findAll({
|
|
163
|
+
where: { devicePaymentPlanId: this.id },
|
|
164
|
+
});
|
|
161
165
|
}
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
// Helper to get installments
|
|
167
|
+
async getInstallments(options) {
|
|
168
|
+
const Installment = this.constructor.sequelize.models.Installment;
|
|
169
|
+
return await Installment.findAll({
|
|
170
|
+
where: { devicePaymentPlanId: this.id },
|
|
171
|
+
...options,
|
|
172
|
+
});
|
|
164
173
|
}
|
|
165
174
|
}
|
|
166
175
|
exports.DevicePaymentPlan = DevicePaymentPlan;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
|
|
2
2
|
import type { User } from "./user.models";
|
|
3
|
-
export declare const EVENT_ACTIONS: readonly ["USER_READ", "USER_CREATED", "USER_UPDATED", "USER_DELETED", "USER_SUSPENDED", "USER_BANNED", "USERS_LISTED", "ADMIN_FORGOT_PASSWORD", "ADMIN_LOGGED_IN", "ADMIN_LOGGED_OUT", "PASSENGER_ACCOUNT_DEACTIVATED", "PASSENGER_ACCOUNT_DELETED", "AGENT_CREATED_RIDER", "AGENT_UPDATED_RIDER", "ADMIN_CREATED_USER", "ADMIN_UPDATED_USER", "SUPER_ADMIN_PASSWORD_CHANGED", "SUPER_ADMIN_CHANGED_USER_ACCOUNT_STATUS", "SUPER_ADMIN_UPDATED_USER", "SUPER_ADMIN_CREATED_USER", "PRODUCT_CREATED", "PRODUCT_READ", "PRODUCT_UPDATED", "PRODUCT_DELETED", "PRODUCTS_LISTED", "PRODUCTS_ACTIVATED", "PRODUCTS_DEACTIVATED", "PRODUCTS_LISTED", "PRODUCT_STOCK_SET_MANUALLY", "PRODUCT_STOCK_INCREMENTED", "PRODUCT_STOCK_DECREMENTED", "PRICING_CREATED", "PRICING_READ", "PRICING_UPDATED", "PRICING_DELETED", "DEVICE_CREATED", "DEVICES_BULK_CREATED", "DEVICE_ASSIGNED", "DEVICE_READ", "DEVICE_UPDATED", "DEVICES_LISTED", "DEVICE_LOCKED", "DEVICE_UNLOCKED", "DEVICE_DISABLED", "DEVICE_REACTIVATED", "DEVICE_DELETED_PERMANENTLY", "DEVICE_REPOSSESSED", "DEVICE_PAYMENT_PLAN_CREATED", "DEVICE_PAYMENT_PLAN_READ", "DEVICE_PAYMENT_PLAN_UPDATED", "
|
|
3
|
+
export declare const EVENT_ACTIONS: readonly ["USER_READ", "USER_CREATED", "USER_UPDATED", "USER_DELETED", "USER_SUSPENDED", "USER_BANNED", "USERS_LISTED", "ADMIN_FORGOT_PASSWORD", "ADMIN_LOGGED_IN", "ADMIN_LOGGED_OUT", "PASSENGER_ACCOUNT_DEACTIVATED", "PASSENGER_ACCOUNT_DELETED", "AGENT_CREATED_RIDER", "AGENT_UPDATED_RIDER", "ADMIN_CREATED_USER", "ADMIN_UPDATED_USER", "SUPER_ADMIN_PASSWORD_CHANGED", "SUPER_ADMIN_CHANGED_USER_ACCOUNT_STATUS", "SUPER_ADMIN_UPDATED_USER", "SUPER_ADMIN_CREATED_USER", "PRODUCT_CREATED", "PRODUCT_READ", "PRODUCT_UPDATED", "PRODUCT_DELETED", "PRODUCTS_LISTED", "PRODUCTS_ACTIVATED", "PRODUCTS_DEACTIVATED", "PRODUCTS_LISTED", "PRODUCT_STOCK_SET_MANUALLY", "PRODUCT_STOCK_INCREMENTED", "PRODUCT_STOCK_DECREMENTED", "PRICING_CREATED", "PRICING_READ", "PRICING_UPDATED", "PRICING_DELETED", "DEVICE_CREATED", "DEVICES_BULK_CREATED", "DEVICE_ASSIGNED", "DEVICE_READ", "DEVICE_UPDATED", "DEVICES_LISTED", "DEVICE_LOCKED", "DEVICE_UNLOCKED", "DEVICE_DISABLED", "DEVICE_REACTIVATED", "DEVICE_DELETED_PERMANENTLY", "DEVICE_REPOSSESSED", "DEVICE_PAYMENT_PLAN_CREATED", "DEVICE_PAYMENT_PLAN_READ", "DEVICE_PAYMENT_PLAN_UPDATED", "DEVICE_PAYMENT_PLAN_MARKED_DEFAULTED", "DEVICE_PAYMENT_PLAN_SOFT_DELETED", "PAYMENT_RECORDED", "PAYMENT_READ", "PAYMENTS_LISTED", "TRANSACTION_READ", "TRANSACTIONS_LISTED", "EVENT_LOG_READ", "EVENT_LOGS_LISTED", "SECURITY_CLEARANCE_MANAGED", "DEVICE_LOCK_OVERRIDDEN"];
|
|
4
4
|
export type EventAction = (typeof EVENT_ACTIONS)[number];
|
|
5
5
|
export type EventActorType = "USER" | "SYSTEM" | "ADMIN";
|
|
6
6
|
export interface EventLogAttributes {
|
|
@@ -54,8 +54,9 @@ exports.EVENT_ACTIONS = [
|
|
|
54
54
|
"DEVICE_PAYMENT_PLAN_CREATED",
|
|
55
55
|
"DEVICE_PAYMENT_PLAN_READ",
|
|
56
56
|
"DEVICE_PAYMENT_PLAN_UPDATED",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
57
|
+
"DEVICE_PAYMENT_PLAN_MARKED_DEFAULTED",
|
|
58
|
+
"DEVICE_PAYMENT_PLAN_SOFT_DELETED",
|
|
59
|
+
"PAYMENT_RECORDED",
|
|
59
60
|
"PAYMENT_READ",
|
|
60
61
|
"PAYMENTS_LISTED",
|
|
61
62
|
"TRANSACTION_READ",
|
package/dist/models/index.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export * from "./pricing.models";
|
|
|
8
8
|
export * from "./product.models";
|
|
9
9
|
export * from "./securityClearance.models";
|
|
10
10
|
export * from "./transaction.models";
|
|
11
|
-
export
|
|
11
|
+
export * from "./installment.models";
|
|
12
|
+
export type { UserModel, TransactionModel, SecurityClearanceModel, ProductModel, PricingModel, PaymentModel, IdempotencyRecordModel, EventLogModel, DevicePaymentPlanModel, DeviceModel, InstallmentModel, } from "./types";
|
package/dist/models/index.js
CHANGED
|
@@ -24,3 +24,4 @@ __exportStar(require("./pricing.models"), exports);
|
|
|
24
24
|
__exportStar(require("./product.models"), exports);
|
|
25
25
|
__exportStar(require("./securityClearance.models"), exports);
|
|
26
26
|
__exportStar(require("./transaction.models"), exports);
|
|
27
|
+
__exportStar(require("./installment.models"), exports);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
|
|
2
|
+
import type { DevicePaymentPlan } from "./devicePaymentPlan.models";
|
|
3
|
+
import type { Payment } from "./payment.models";
|
|
4
|
+
export type InstallmentStatus = "PENDING" | "PAID" | "OVERDUE" | "SKIPPED";
|
|
5
|
+
export interface InstallmentAttributes {
|
|
6
|
+
id: string;
|
|
7
|
+
devicePaymentPlanId: string;
|
|
8
|
+
installmentNumber: number;
|
|
9
|
+
amount: number;
|
|
10
|
+
dueDate: Date;
|
|
11
|
+
paidAt: Date | null;
|
|
12
|
+
paidAmount: number | null;
|
|
13
|
+
status: InstallmentStatus;
|
|
14
|
+
paymentId: string | null;
|
|
15
|
+
isAutoGenerated: boolean;
|
|
16
|
+
metadata: Record<string, any>;
|
|
17
|
+
createdAt: Date;
|
|
18
|
+
updatedAt: Date;
|
|
19
|
+
paymentPlan?: DevicePaymentPlan;
|
|
20
|
+
payment?: Payment;
|
|
21
|
+
}
|
|
22
|
+
export interface InstallmentCreationAttributes extends Omit<InstallmentAttributes, "id" | "createdAt" | "updatedAt" | "paidAt" | "paidAmount" | "status" | "paymentId" | "metadata"> {
|
|
23
|
+
id?: string;
|
|
24
|
+
paidAt?: Date | null;
|
|
25
|
+
paidAmount?: number | null;
|
|
26
|
+
status?: InstallmentStatus;
|
|
27
|
+
paymentId?: string | null;
|
|
28
|
+
metadata?: Record<string, any>;
|
|
29
|
+
}
|
|
30
|
+
export declare class Installment extends Model<InferAttributes<Installment>, InferCreationAttributes<Installment>> implements InstallmentAttributes {
|
|
31
|
+
id: CreationOptional<string>;
|
|
32
|
+
devicePaymentPlanId: string;
|
|
33
|
+
installmentNumber: number;
|
|
34
|
+
amount: number;
|
|
35
|
+
dueDate: Date;
|
|
36
|
+
paidAt: CreationOptional<Date | null>;
|
|
37
|
+
paidAmount: CreationOptional<number | null>;
|
|
38
|
+
status: CreationOptional<InstallmentStatus>;
|
|
39
|
+
paymentId: CreationOptional<string | null>;
|
|
40
|
+
isAutoGenerated: boolean;
|
|
41
|
+
metadata: CreationOptional<Record<string, any>>;
|
|
42
|
+
readonly createdAt: CreationOptional<Date>;
|
|
43
|
+
readonly updatedAt: CreationOptional<Date>;
|
|
44
|
+
paymentPlan?: NonAttribute<DevicePaymentPlan>;
|
|
45
|
+
payment?: NonAttribute<Payment>;
|
|
46
|
+
static initialize(sequelize: any): void;
|
|
47
|
+
static associate(models: Record<string, ModelStatic<Model>>): void;
|
|
48
|
+
markAsPaid(paymentId: string, paidAmount?: number): Promise<void>;
|
|
49
|
+
markAsOverdue(): Promise<void>;
|
|
50
|
+
skip(): Promise<void>;
|
|
51
|
+
isPaid(): boolean;
|
|
52
|
+
isOverdue(): boolean;
|
|
53
|
+
isPending(): boolean;
|
|
54
|
+
daysUntilDue(): number;
|
|
55
|
+
daysOverdue(): number;
|
|
56
|
+
static getOverdueInstallments(): Promise<Installment[]>;
|
|
57
|
+
static getPendingForPlan(planId: string): Promise<Installment[]>;
|
|
58
|
+
}
|
|
59
|
+
export type InstallmentModel = typeof Installment;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Installment = void 0;
|
|
4
|
+
// src/models/installment.models.ts
|
|
5
|
+
const vr_migrations_1 = require("vr-migrations");
|
|
6
|
+
class Installment extends vr_migrations_1.Model {
|
|
7
|
+
// Static initialization method
|
|
8
|
+
static initialize(sequelize) {
|
|
9
|
+
this.init({
|
|
10
|
+
id: {
|
|
11
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
12
|
+
defaultValue: vr_migrations_1.DataTypes.UUIDV4,
|
|
13
|
+
primaryKey: true,
|
|
14
|
+
},
|
|
15
|
+
devicePaymentPlanId: {
|
|
16
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
17
|
+
allowNull: false,
|
|
18
|
+
},
|
|
19
|
+
installmentNumber: {
|
|
20
|
+
type: vr_migrations_1.DataTypes.INTEGER,
|
|
21
|
+
allowNull: false,
|
|
22
|
+
},
|
|
23
|
+
amount: {
|
|
24
|
+
type: vr_migrations_1.DataTypes.DECIMAL(10, 2),
|
|
25
|
+
allowNull: false,
|
|
26
|
+
validate: { min: 0 },
|
|
27
|
+
},
|
|
28
|
+
dueDate: {
|
|
29
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
30
|
+
allowNull: false,
|
|
31
|
+
},
|
|
32
|
+
paidAt: {
|
|
33
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
34
|
+
allowNull: true,
|
|
35
|
+
},
|
|
36
|
+
paidAmount: {
|
|
37
|
+
type: vr_migrations_1.DataTypes.DECIMAL(10, 2),
|
|
38
|
+
allowNull: true,
|
|
39
|
+
validate: { min: 0 },
|
|
40
|
+
},
|
|
41
|
+
status: {
|
|
42
|
+
type: vr_migrations_1.DataTypes.ENUM("PENDING", "PAID", "OVERDUE", "SKIPPED"),
|
|
43
|
+
allowNull: false,
|
|
44
|
+
defaultValue: "PENDING",
|
|
45
|
+
},
|
|
46
|
+
paymentId: {
|
|
47
|
+
type: vr_migrations_1.DataTypes.UUID,
|
|
48
|
+
allowNull: true,
|
|
49
|
+
},
|
|
50
|
+
isAutoGenerated: {
|
|
51
|
+
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
52
|
+
allowNull: false,
|
|
53
|
+
defaultValue: true,
|
|
54
|
+
},
|
|
55
|
+
metadata: {
|
|
56
|
+
type: vr_migrations_1.DataTypes.JSONB,
|
|
57
|
+
allowNull: false,
|
|
58
|
+
defaultValue: {},
|
|
59
|
+
},
|
|
60
|
+
createdAt: {
|
|
61
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
62
|
+
defaultValue: vr_migrations_1.DataTypes.NOW,
|
|
63
|
+
},
|
|
64
|
+
updatedAt: {
|
|
65
|
+
type: vr_migrations_1.DataTypes.DATE,
|
|
66
|
+
defaultValue: vr_migrations_1.DataTypes.NOW,
|
|
67
|
+
},
|
|
68
|
+
}, {
|
|
69
|
+
sequelize,
|
|
70
|
+
modelName: "Installment",
|
|
71
|
+
tableName: "installments",
|
|
72
|
+
timestamps: true,
|
|
73
|
+
freezeTableName: true,
|
|
74
|
+
indexes: [
|
|
75
|
+
{
|
|
76
|
+
unique: true,
|
|
77
|
+
fields: ["devicePaymentPlanId", "installmentNumber"],
|
|
78
|
+
name: "unique_installment_number_per_plan",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
fields: ["status"],
|
|
82
|
+
name: "installment_status_idx",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
fields: ["dueDate"],
|
|
86
|
+
name: "installment_due_date_idx",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
validate: {
|
|
90
|
+
paidAmountMatchesAmount() {
|
|
91
|
+
if (this.status === "PAID" && this.paidAmount !== this.amount) {
|
|
92
|
+
throw new Error("Paid amount must equal installment amount when status is PAID");
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Static association method
|
|
99
|
+
static associate(models) {
|
|
100
|
+
this.belongsTo(models.DevicePaymentPlan, {
|
|
101
|
+
foreignKey: "devicePaymentPlanId",
|
|
102
|
+
as: "paymentPlan",
|
|
103
|
+
onDelete: "CASCADE",
|
|
104
|
+
onUpdate: "CASCADE",
|
|
105
|
+
});
|
|
106
|
+
this.belongsTo(models.Payment, {
|
|
107
|
+
foreignKey: "paymentId",
|
|
108
|
+
as: "payment",
|
|
109
|
+
onDelete: "SET NULL",
|
|
110
|
+
onUpdate: "CASCADE",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Custom instance methods
|
|
114
|
+
async markAsPaid(paymentId, paidAmount) {
|
|
115
|
+
this.status = "PAID";
|
|
116
|
+
this.paidAt = new Date();
|
|
117
|
+
this.paidAmount = paidAmount || this.amount;
|
|
118
|
+
this.paymentId = paymentId;
|
|
119
|
+
await this.save();
|
|
120
|
+
// Update the parent plan's progress
|
|
121
|
+
const DevicePaymentPlan = this.constructor.sequelize.models
|
|
122
|
+
.DevicePaymentPlan;
|
|
123
|
+
const plan = await DevicePaymentPlan.findByPk(this.devicePaymentPlanId);
|
|
124
|
+
if (plan) {
|
|
125
|
+
await plan.updateProgress();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async markAsOverdue() {
|
|
129
|
+
if (this.status === "PENDING" && new Date() > this.dueDate) {
|
|
130
|
+
this.status = "OVERDUE";
|
|
131
|
+
await this.save();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async skip() {
|
|
135
|
+
this.status = "SKIPPED";
|
|
136
|
+
await this.save();
|
|
137
|
+
}
|
|
138
|
+
isPaid() {
|
|
139
|
+
return this.status === "PAID";
|
|
140
|
+
}
|
|
141
|
+
isOverdue() {
|
|
142
|
+
return this.status === "OVERDUE";
|
|
143
|
+
}
|
|
144
|
+
isPending() {
|
|
145
|
+
return this.status === "PENDING";
|
|
146
|
+
}
|
|
147
|
+
daysUntilDue() {
|
|
148
|
+
const today = new Date();
|
|
149
|
+
const diffTime = this.dueDate.getTime() - today.getTime();
|
|
150
|
+
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
151
|
+
}
|
|
152
|
+
daysOverdue() {
|
|
153
|
+
if (!this.isOverdue() &&
|
|
154
|
+
!(this.status === "PENDING" && new Date() > this.dueDate)) {
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
const today = new Date();
|
|
158
|
+
const diffTime = today.getTime() - this.dueDate.getTime();
|
|
159
|
+
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
160
|
+
}
|
|
161
|
+
// Static methods
|
|
162
|
+
static async getOverdueInstallments() {
|
|
163
|
+
return await this.findAll({
|
|
164
|
+
where: {
|
|
165
|
+
status: "PENDING",
|
|
166
|
+
dueDate: { [vr_migrations_1.Op.lt]: new Date() },
|
|
167
|
+
},
|
|
168
|
+
include: ["paymentPlan"],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
static async getPendingForPlan(planId) {
|
|
172
|
+
return await this.findAll({
|
|
173
|
+
where: {
|
|
174
|
+
devicePaymentPlanId: planId,
|
|
175
|
+
status: "PENDING",
|
|
176
|
+
},
|
|
177
|
+
order: [["dueDate", "ASC"]],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
exports.Installment = Installment;
|
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
import { Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ModelStatic } from "vr-migrations";
|
|
2
2
|
import type { Product } from "./product.models";
|
|
3
|
+
export type PricingType = "HIRE_PURCHASE" | "FULL_PAYMENT";
|
|
4
|
+
export type InstallmentFrequency = "DAILY" | "WEEKLY" | "MONTHLY";
|
|
3
5
|
export interface PricingAttributes {
|
|
4
6
|
id: string;
|
|
5
7
|
productId: string;
|
|
6
8
|
name: string;
|
|
7
|
-
|
|
9
|
+
type: PricingType;
|
|
10
|
+
totalAmount: number;
|
|
8
11
|
downPayment: number;
|
|
9
12
|
installmentAmount: number | null;
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
installmentFrequency: InstallmentFrequency | null;
|
|
14
|
+
installmentCount: number | null;
|
|
15
|
+
gracePeriodDays: number;
|
|
16
|
+
autoLockOnMiss: boolean;
|
|
12
17
|
isActive: boolean;
|
|
13
18
|
createdAt: Date;
|
|
14
19
|
updatedAt: Date;
|
|
15
20
|
product?: Product;
|
|
16
21
|
}
|
|
17
|
-
export interface PricingCreationAttributes extends Omit<PricingAttributes, "id" | "createdAt" | "updatedAt" | "isActive" | "downPayment"> {
|
|
22
|
+
export interface PricingCreationAttributes extends Omit<PricingAttributes, "id" | "createdAt" | "updatedAt" | "isActive" | "downPayment" | "gracePeriodDays" | "autoLockOnMiss"> {
|
|
18
23
|
id?: string;
|
|
19
24
|
isActive?: boolean;
|
|
20
25
|
downPayment?: number;
|
|
26
|
+
gracePeriodDays?: number;
|
|
27
|
+
autoLockOnMiss?: boolean;
|
|
21
28
|
}
|
|
22
29
|
export declare class Pricing extends Model<InferAttributes<Pricing>, InferCreationAttributes<Pricing>> implements PricingAttributes {
|
|
23
30
|
id: CreationOptional<string>;
|
|
24
31
|
productId: string;
|
|
25
32
|
name: string;
|
|
26
|
-
|
|
33
|
+
type: PricingType;
|
|
34
|
+
totalAmount: number;
|
|
27
35
|
downPayment: CreationOptional<number>;
|
|
28
36
|
installmentAmount: CreationOptional<number | null>;
|
|
29
|
-
|
|
30
|
-
|
|
37
|
+
installmentFrequency: CreationOptional<InstallmentFrequency | null>;
|
|
38
|
+
installmentCount: CreationOptional<number | null>;
|
|
39
|
+
gracePeriodDays: CreationOptional<number>;
|
|
40
|
+
autoLockOnMiss: CreationOptional<boolean>;
|
|
31
41
|
isActive: CreationOptional<boolean>;
|
|
32
42
|
readonly createdAt: CreationOptional<Date>;
|
|
33
43
|
readonly updatedAt: CreationOptional<Date>;
|
|
@@ -39,5 +49,6 @@ export declare class Pricing extends Model<InferAttributes<Pricing>, InferCreati
|
|
|
39
49
|
isInstallmentPlan(): boolean;
|
|
40
50
|
getInstallmentCount(): number | null;
|
|
41
51
|
calculateRemainingAmount(paidAmount: number): number;
|
|
52
|
+
calculateNextDueDate(fromDate?: Date): Date | null;
|
|
42
53
|
}
|
|
43
54
|
export type PricingModel = typeof Pricing;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Pricing = void 0;
|
|
4
|
+
// src/models/pricing.models.ts
|
|
4
5
|
const vr_migrations_1 = require("vr-migrations");
|
|
5
6
|
class Pricing extends vr_migrations_1.Model {
|
|
6
7
|
// Static initialization method
|
|
@@ -13,15 +14,42 @@ class Pricing extends vr_migrations_1.Model {
|
|
|
13
14
|
},
|
|
14
15
|
productId: { type: vr_migrations_1.DataTypes.UUID, allowNull: false },
|
|
15
16
|
name: { type: vr_migrations_1.DataTypes.STRING(100), allowNull: false },
|
|
16
|
-
|
|
17
|
+
type: {
|
|
18
|
+
type: vr_migrations_1.DataTypes.ENUM("HIRE_PURCHASE", "FULL_PAYMENT"),
|
|
19
|
+
allowNull: false,
|
|
20
|
+
},
|
|
21
|
+
totalAmount: {
|
|
22
|
+
type: vr_migrations_1.DataTypes.DECIMAL(10, 2),
|
|
23
|
+
allowNull: false,
|
|
24
|
+
field: "total_amount", // Maps to database column
|
|
25
|
+
},
|
|
17
26
|
downPayment: {
|
|
18
27
|
type: vr_migrations_1.DataTypes.DECIMAL(10, 2),
|
|
19
28
|
allowNull: false,
|
|
20
29
|
defaultValue: 0,
|
|
21
30
|
},
|
|
22
|
-
installmentAmount: {
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
installmentAmount: {
|
|
32
|
+
type: vr_migrations_1.DataTypes.DECIMAL(10, 2),
|
|
33
|
+
allowNull: true,
|
|
34
|
+
},
|
|
35
|
+
installmentFrequency: {
|
|
36
|
+
type: vr_migrations_1.DataTypes.ENUM("DAILY", "WEEKLY", "MONTHLY"),
|
|
37
|
+
allowNull: true,
|
|
38
|
+
},
|
|
39
|
+
installmentCount: {
|
|
40
|
+
type: vr_migrations_1.DataTypes.INTEGER,
|
|
41
|
+
allowNull: true,
|
|
42
|
+
},
|
|
43
|
+
gracePeriodDays: {
|
|
44
|
+
type: vr_migrations_1.DataTypes.INTEGER,
|
|
45
|
+
allowNull: false,
|
|
46
|
+
defaultValue: 2,
|
|
47
|
+
},
|
|
48
|
+
autoLockOnMiss: {
|
|
49
|
+
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
50
|
+
allowNull: false,
|
|
51
|
+
defaultValue: true,
|
|
52
|
+
},
|
|
25
53
|
isActive: {
|
|
26
54
|
type: vr_migrations_1.DataTypes.BOOLEAN,
|
|
27
55
|
allowNull: false,
|
|
@@ -55,16 +83,33 @@ class Pricing extends vr_migrations_1.Model {
|
|
|
55
83
|
await this.save();
|
|
56
84
|
}
|
|
57
85
|
isInstallmentPlan() {
|
|
58
|
-
return
|
|
86
|
+
return this.type === "HIRE_PURCHASE";
|
|
59
87
|
}
|
|
60
88
|
getInstallmentCount() {
|
|
61
89
|
if (!this.isInstallmentPlan())
|
|
62
90
|
return null;
|
|
63
|
-
|
|
64
|
-
return Math.ceil(remainingAmount / (this.installmentAmount || 1));
|
|
91
|
+
return this.installmentCount;
|
|
65
92
|
}
|
|
66
93
|
calculateRemainingAmount(paidAmount) {
|
|
67
94
|
return Math.max(0, this.totalAmount - paidAmount);
|
|
68
95
|
}
|
|
96
|
+
// Helper to calculate next due date based on frequency
|
|
97
|
+
calculateNextDueDate(fromDate = new Date()) {
|
|
98
|
+
if (!this.installmentFrequency)
|
|
99
|
+
return null;
|
|
100
|
+
const nextDate = new Date(fromDate);
|
|
101
|
+
switch (this.installmentFrequency) {
|
|
102
|
+
case "DAILY":
|
|
103
|
+
nextDate.setDate(nextDate.getDate() + 1);
|
|
104
|
+
break;
|
|
105
|
+
case "WEEKLY":
|
|
106
|
+
nextDate.setDate(nextDate.getDate() + 7);
|
|
107
|
+
break;
|
|
108
|
+
case "MONTHLY":
|
|
109
|
+
nextDate.setMonth(nextDate.getMonth() + 1);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
return nextDate;
|
|
113
|
+
}
|
|
69
114
|
}
|
|
70
115
|
exports.Pricing = Pricing;
|
package/dist/models/types.d.ts
CHANGED
|
@@ -8,3 +8,4 @@ export type { IdempotencyRecordModel } from "./idempotencyRecord.models";
|
|
|
8
8
|
export type { EventLogModel } from "./eventLog.models";
|
|
9
9
|
export type { DevicePaymentPlanModel } from "./devicePaymentPlan.models";
|
|
10
10
|
export type { DeviceModel } from "./device.models";
|
|
11
|
+
export type { InstallmentModel } from "./installment.models";
|