vr-commons 1.0.96 → 1.0.98
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/utils/admin.device.utils.d.ts +59 -0
- package/dist/utils/admin.device.utils.js +231 -0
- package/dist/utils/admin.devicePayment.utils.d.ts +46 -0
- package/dist/utils/admin.devicePayment.utils.js +206 -0
- package/dist/utils/index.d.ts +3 -1
- package/dist/utils/index.js +5 -1
- package/dist/utils/types.d.ts +7 -0
- package/dist/validations/admin.devicePayment.validations.d.ts +270 -0
- package/dist/validations/admin.devicePayment.validations.js +98 -0
- package/dist/validations/admin.devices.validations.d.ts +297 -0
- package/dist/validations/admin.devices.validations.js +118 -0
- package/dist/validations/appSpecs.validations.d.ts +16 -16
- package/dist/validations/appeals.validations.d.ts +8 -8
- package/dist/validations/auth.validations.d.ts +2 -2
- package/dist/validations/bans.validations.d.ts +8 -8
- package/dist/validations/devicePaymentPlan.validations.d.ts +12 -12
- package/dist/validations/index.d.ts +2 -0
- package/dist/validations/index.js +22 -1
- package/dist/validations/moderation.validations.d.ts +105 -105
- package/dist/validations/payinstallment.validations.d.ts +5 -5
- package/dist/validations/pricing.validations.d.ts +313 -0
- package/dist/validations/pricing.validations.js +376 -0
- package/dist/validations/pricings.validations.d.ts +54 -54
- package/dist/validations/product.validations.d.ts +42 -42
- package/dist/validations/profiles.validations.d.ts +46 -46
- package/dist/validations/suspensions.validations.d.ts +8 -8
- package/dist/validations/users.admin.validations.d.ts +44 -44
- package/package.json +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { UserRole } from "vr-models";
|
|
2
|
+
import { Transaction } from "sequelize";
|
|
3
|
+
import { StockChangeResult } from "./types";
|
|
4
|
+
export declare const stockUtil: {
|
|
5
|
+
/**
|
|
6
|
+
* Increment stock when devices are added/created
|
|
7
|
+
*/
|
|
8
|
+
incrementStock(productId: string, quantity?: number, options?: {
|
|
9
|
+
transaction?: Transaction;
|
|
10
|
+
reason?: string;
|
|
11
|
+
actorId?: string;
|
|
12
|
+
actorType: UserRole;
|
|
13
|
+
}): Promise<StockChangeResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Decrement stock when devices are removed/deleted/assigned
|
|
16
|
+
*/
|
|
17
|
+
decrementStock(productId: string, quantity?: number, options?: {
|
|
18
|
+
transaction?: Transaction;
|
|
19
|
+
reason?: string;
|
|
20
|
+
actorId?: string;
|
|
21
|
+
allowNegative?: boolean;
|
|
22
|
+
actorType: UserRole;
|
|
23
|
+
}): Promise<StockChangeResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Set stock to specific value
|
|
26
|
+
*/
|
|
27
|
+
setStock(productId: string, newStock: number, options?: {
|
|
28
|
+
transaction?: Transaction;
|
|
29
|
+
reason?: string;
|
|
30
|
+
actorId?: string;
|
|
31
|
+
actorType: UserRole;
|
|
32
|
+
}): Promise<StockChangeResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Validate if product has enough stock
|
|
35
|
+
*/
|
|
36
|
+
hasEnoughStock(productId: string, quantity?: number, transaction?: Transaction): Promise<boolean>;
|
|
37
|
+
/**
|
|
38
|
+
* Get current stock level
|
|
39
|
+
*/
|
|
40
|
+
getStockLevel(productId: string, transaction?: Transaction): Promise<number>;
|
|
41
|
+
/**
|
|
42
|
+
* Bulk update stock for multiple products
|
|
43
|
+
*/
|
|
44
|
+
bulkUpdateStock(updates: Array<{
|
|
45
|
+
productId: string;
|
|
46
|
+
change: number;
|
|
47
|
+
reason?: string;
|
|
48
|
+
}>, actorType: UserRole, options?: {
|
|
49
|
+
transaction?: Transaction;
|
|
50
|
+
actorId?: string;
|
|
51
|
+
}): Promise<Record<string, StockChangeResult>>;
|
|
52
|
+
/**
|
|
53
|
+
* Sync stock based on actual device count
|
|
54
|
+
*/
|
|
55
|
+
syncStockWithDeviceCount(productId: string, actorType: UserRole, options?: {
|
|
56
|
+
transaction?: Transaction;
|
|
57
|
+
actorId?: string;
|
|
58
|
+
}): Promise<StockChangeResult>;
|
|
59
|
+
};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stockUtil = void 0;
|
|
4
|
+
// src/utils/stock.util.ts
|
|
5
|
+
const vr_models_1 = require("vr-models");
|
|
6
|
+
const eventLog_utils_1 = require("./eventLog.utils");
|
|
7
|
+
exports.stockUtil = {
|
|
8
|
+
/**
|
|
9
|
+
* Increment stock when devices are added/created
|
|
10
|
+
*/
|
|
11
|
+
async incrementStock(productId, quantity = 1, options) {
|
|
12
|
+
const product = await vr_models_1.Product.findByPk(productId, {
|
|
13
|
+
transaction: options?.transaction,
|
|
14
|
+
});
|
|
15
|
+
if (!product) {
|
|
16
|
+
throw new Error(`Product with ID ${productId} not found`);
|
|
17
|
+
}
|
|
18
|
+
const previousStock = product.stock;
|
|
19
|
+
// Use the model's instance method if available, otherwise direct update
|
|
20
|
+
if (typeof product.incrementStock === "function") {
|
|
21
|
+
await product.incrementStock(quantity);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
product.stock += quantity;
|
|
25
|
+
await product.save({ transaction: options?.transaction });
|
|
26
|
+
}
|
|
27
|
+
// Log stock change
|
|
28
|
+
if (options?.actorId) {
|
|
29
|
+
await (0, eventLog_utils_1.logEvent)({
|
|
30
|
+
actorId: options.actorId,
|
|
31
|
+
actorType: options.actorType,
|
|
32
|
+
action: "PRODUCT_STOCK_INCREMENTED",
|
|
33
|
+
entity: "Product",
|
|
34
|
+
entityId: productId,
|
|
35
|
+
deviceSession: null,
|
|
36
|
+
metadata: {
|
|
37
|
+
quantity,
|
|
38
|
+
previousStock,
|
|
39
|
+
newStock: product.stock,
|
|
40
|
+
reason: options.reason || "Device added to inventory",
|
|
41
|
+
},
|
|
42
|
+
transaction: options?.transaction,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
previousStock,
|
|
48
|
+
newStock: product.stock,
|
|
49
|
+
productId: product.id,
|
|
50
|
+
productName: product.name,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* Decrement stock when devices are removed/deleted/assigned
|
|
55
|
+
*/
|
|
56
|
+
async decrementStock(productId, quantity = 1, options) {
|
|
57
|
+
const product = await vr_models_1.Product.findByPk(productId, {
|
|
58
|
+
transaction: options?.transaction,
|
|
59
|
+
});
|
|
60
|
+
if (!product) {
|
|
61
|
+
throw new Error(`Product with ID ${productId} not found`);
|
|
62
|
+
}
|
|
63
|
+
// Check if enough stock (unless allowNegative is true)
|
|
64
|
+
if (!options?.allowNegative && product.stock < quantity) {
|
|
65
|
+
throw new Error(`Insufficient stock. Available: ${product.stock}, Requested: ${quantity}`);
|
|
66
|
+
}
|
|
67
|
+
const previousStock = product.stock;
|
|
68
|
+
// Use the model's instance method if available
|
|
69
|
+
if (typeof product.decrementStock === "function") {
|
|
70
|
+
await product.decrementStock(quantity);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
product.stock -= quantity;
|
|
74
|
+
await product.save({ transaction: options?.transaction });
|
|
75
|
+
}
|
|
76
|
+
// Log stock change
|
|
77
|
+
if (options?.actorId) {
|
|
78
|
+
await (0, eventLog_utils_1.logEvent)({
|
|
79
|
+
actorId: options.actorId,
|
|
80
|
+
actorType: options.actorType,
|
|
81
|
+
action: "PRODUCT_STOCK_DECREMENTED",
|
|
82
|
+
entity: "Product",
|
|
83
|
+
entityId: productId,
|
|
84
|
+
deviceSession: null,
|
|
85
|
+
metadata: {
|
|
86
|
+
quantity,
|
|
87
|
+
previousStock,
|
|
88
|
+
newStock: product.stock,
|
|
89
|
+
reason: options.reason || "Device removed from inventory",
|
|
90
|
+
},
|
|
91
|
+
transaction: options?.transaction,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
previousStock,
|
|
97
|
+
newStock: product.stock,
|
|
98
|
+
productId: product.id,
|
|
99
|
+
productName: product.name,
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
/**
|
|
103
|
+
* Set stock to specific value
|
|
104
|
+
*/
|
|
105
|
+
async setStock(productId, newStock, options) {
|
|
106
|
+
const product = await vr_models_1.Product.findByPk(productId, {
|
|
107
|
+
transaction: options?.transaction,
|
|
108
|
+
});
|
|
109
|
+
if (!product) {
|
|
110
|
+
throw new Error(`Product with ID ${productId} not found`);
|
|
111
|
+
}
|
|
112
|
+
if (newStock < 0) {
|
|
113
|
+
throw new Error("Stock cannot be negative");
|
|
114
|
+
}
|
|
115
|
+
const previousStock = product.stock;
|
|
116
|
+
product.stock = newStock;
|
|
117
|
+
await product.save({ transaction: options?.transaction });
|
|
118
|
+
// Log stock change
|
|
119
|
+
if (options?.actorId) {
|
|
120
|
+
await (0, eventLog_utils_1.logEvent)({
|
|
121
|
+
actorId: options.actorId,
|
|
122
|
+
actorType: options.actorType,
|
|
123
|
+
action: "PRODUCT_STOCK_SET_MANUALLY",
|
|
124
|
+
entity: "Product",
|
|
125
|
+
entityId: productId,
|
|
126
|
+
deviceSession: null,
|
|
127
|
+
metadata: {
|
|
128
|
+
previousStock,
|
|
129
|
+
newStock,
|
|
130
|
+
reason: options.reason || "Manual stock adjustment",
|
|
131
|
+
},
|
|
132
|
+
transaction: options?.transaction,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
success: true,
|
|
137
|
+
previousStock,
|
|
138
|
+
newStock: product.stock,
|
|
139
|
+
productId: product.id,
|
|
140
|
+
productName: product.name,
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
/**
|
|
144
|
+
* Validate if product has enough stock
|
|
145
|
+
*/
|
|
146
|
+
async hasEnoughStock(productId, quantity = 1, transaction) {
|
|
147
|
+
const product = await vr_models_1.Product.findByPk(productId, { transaction });
|
|
148
|
+
if (!product)
|
|
149
|
+
return false;
|
|
150
|
+
return product.stock >= quantity;
|
|
151
|
+
},
|
|
152
|
+
/**
|
|
153
|
+
* Get current stock level
|
|
154
|
+
*/
|
|
155
|
+
async getStockLevel(productId, transaction) {
|
|
156
|
+
const product = await vr_models_1.Product.findByPk(productId, {
|
|
157
|
+
transaction,
|
|
158
|
+
attributes: ["stock"],
|
|
159
|
+
});
|
|
160
|
+
return product?.stock || 0;
|
|
161
|
+
},
|
|
162
|
+
/**
|
|
163
|
+
* Bulk update stock for multiple products
|
|
164
|
+
*/
|
|
165
|
+
async bulkUpdateStock(updates, actorType, options) {
|
|
166
|
+
const results = {};
|
|
167
|
+
for (const update of updates) {
|
|
168
|
+
try {
|
|
169
|
+
if (update.change > 0) {
|
|
170
|
+
results[update.productId] = await this.incrementStock(update.productId, update.change, {
|
|
171
|
+
transaction: options?.transaction,
|
|
172
|
+
reason: update.reason,
|
|
173
|
+
actorId: options?.actorId,
|
|
174
|
+
actorType: actorType,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else if (update.change < 0) {
|
|
178
|
+
results[update.productId] = await this.decrementStock(update.productId, Math.abs(update.change), {
|
|
179
|
+
transaction: options?.transaction,
|
|
180
|
+
reason: update.reason,
|
|
181
|
+
actorId: options?.actorId,
|
|
182
|
+
actorType: actorType,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
console.error(`Failed to update stock for product ${update.productId}:`, error);
|
|
188
|
+
results[update.productId] = {
|
|
189
|
+
success: false,
|
|
190
|
+
previousStock: 0,
|
|
191
|
+
newStock: 0,
|
|
192
|
+
productId: update.productId,
|
|
193
|
+
productName: "Unknown",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return results;
|
|
198
|
+
},
|
|
199
|
+
/**
|
|
200
|
+
* Sync stock based on actual device count
|
|
201
|
+
*/
|
|
202
|
+
async syncStockWithDeviceCount(productId, actorType, options) {
|
|
203
|
+
// Count total devices for this product
|
|
204
|
+
const totalDevices = await vr_models_1.Device.count({
|
|
205
|
+
where: { productId },
|
|
206
|
+
transaction: options?.transaction,
|
|
207
|
+
});
|
|
208
|
+
// Count devices that are in use (have active payment plans)
|
|
209
|
+
const inUseDevices = await vr_models_1.Device.count({
|
|
210
|
+
where: { productId },
|
|
211
|
+
include: [
|
|
212
|
+
{
|
|
213
|
+
model: vr_models_1.DevicePaymentPlan,
|
|
214
|
+
as: "paymentPlans",
|
|
215
|
+
where: { status: "ACTIVE" },
|
|
216
|
+
required: true,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
transaction: options?.transaction,
|
|
220
|
+
});
|
|
221
|
+
// Available devices = total - inUse
|
|
222
|
+
const availableDevices = totalDevices - inUseDevices;
|
|
223
|
+
// Set stock to available devices
|
|
224
|
+
return await this.setStock(productId, availableDevices, {
|
|
225
|
+
transaction: options?.transaction,
|
|
226
|
+
reason: "Stock synced with actual device count",
|
|
227
|
+
actorId: options?.actorId,
|
|
228
|
+
actorType: actorType,
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { DevicePaymentPlan } from "vr-models";
|
|
2
|
+
import { Transaction } from "sequelize";
|
|
3
|
+
export interface DeleteCheckResult {
|
|
4
|
+
canDelete: boolean;
|
|
5
|
+
reason?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const DPP_OperationsUtil: {
|
|
8
|
+
/**
|
|
9
|
+
* Check if a payment plan can be deleted
|
|
10
|
+
* Rule: Can only delete if NO payments have been made
|
|
11
|
+
*/
|
|
12
|
+
canDeletePlan(planId: string, transaction?: Transaction): Promise<DeleteCheckResult>;
|
|
13
|
+
/**
|
|
14
|
+
* Check if plan can be updated (before payments)
|
|
15
|
+
*/
|
|
16
|
+
canUpdatePlan(plan: DevicePaymentPlan): {
|
|
17
|
+
canUpdate: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Validate payment amount against next pending installment
|
|
22
|
+
*/
|
|
23
|
+
validatePaymentAmount(plan: DevicePaymentPlan, amount: number, transaction?: Transaction): Promise<{
|
|
24
|
+
valid: boolean;
|
|
25
|
+
reason?: string;
|
|
26
|
+
nextInstallment?: any;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Calculate new values after payment
|
|
30
|
+
*/
|
|
31
|
+
calculatePaymentEffects(plan: DevicePaymentPlan, amount: number): {
|
|
32
|
+
newPaidAmount: number;
|
|
33
|
+
newOutstandingAmount: number;
|
|
34
|
+
isNowCompleted: boolean;
|
|
35
|
+
progress: number;
|
|
36
|
+
statusChanged: boolean;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Generate installments for a hire purchase plan
|
|
40
|
+
*/
|
|
41
|
+
generateInstallments(plan: DevicePaymentPlan, transaction?: Transaction): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Format plan for operation response
|
|
44
|
+
*/
|
|
45
|
+
formatPlanForOps(plan: DevicePaymentPlan | null): any;
|
|
46
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DPP_OperationsUtil = void 0;
|
|
4
|
+
// src/utils/admin.devicePayment.utils.ts
|
|
5
|
+
const vr_models_1 = require("vr-models");
|
|
6
|
+
exports.DPP_OperationsUtil = {
|
|
7
|
+
/**
|
|
8
|
+
* Check if a payment plan can be deleted
|
|
9
|
+
* Rule: Can only delete if NO payments have been made
|
|
10
|
+
*/
|
|
11
|
+
async canDeletePlan(planId, transaction) {
|
|
12
|
+
// Check if any payments exist for this plan
|
|
13
|
+
const paymentCount = await vr_models_1.Payment.count({
|
|
14
|
+
where: { devicePaymentPlanId: planId },
|
|
15
|
+
transaction,
|
|
16
|
+
});
|
|
17
|
+
if (paymentCount > 0) {
|
|
18
|
+
return {
|
|
19
|
+
canDelete: false,
|
|
20
|
+
reason: `Cannot delete plan with ${paymentCount} payment(s) already recorded`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Also check if any installments are paid
|
|
24
|
+
const paidInstallments = await vr_models_1.Installment.count({
|
|
25
|
+
where: {
|
|
26
|
+
devicePaymentPlanId: planId,
|
|
27
|
+
status: "PAID",
|
|
28
|
+
},
|
|
29
|
+
transaction,
|
|
30
|
+
});
|
|
31
|
+
if (paidInstallments > 0) {
|
|
32
|
+
return {
|
|
33
|
+
canDelete: false,
|
|
34
|
+
reason: "Cannot delete plan with paid installments",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return { canDelete: true };
|
|
38
|
+
},
|
|
39
|
+
/**
|
|
40
|
+
* Check if plan can be updated (before payments)
|
|
41
|
+
*/
|
|
42
|
+
canUpdatePlan(plan) {
|
|
43
|
+
if (plan.paidAmount > 0) {
|
|
44
|
+
return {
|
|
45
|
+
canUpdate: false,
|
|
46
|
+
reason: "Cannot update plan after payments have been made",
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (plan.status !== "ACTIVE") {
|
|
50
|
+
return {
|
|
51
|
+
canUpdate: false,
|
|
52
|
+
reason: `Cannot update plan with status: ${plan.status}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { canUpdate: true };
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* Validate payment amount against next pending installment
|
|
59
|
+
*/
|
|
60
|
+
async validatePaymentAmount(plan, amount, transaction) {
|
|
61
|
+
const InstallmentModel = vr_models_1.Installment;
|
|
62
|
+
const nextInstallment = await InstallmentModel.findOne({
|
|
63
|
+
where: {
|
|
64
|
+
devicePaymentPlanId: plan.id,
|
|
65
|
+
status: "PENDING",
|
|
66
|
+
},
|
|
67
|
+
order: [["dueDate", "ASC"]],
|
|
68
|
+
transaction,
|
|
69
|
+
});
|
|
70
|
+
if (!nextInstallment) {
|
|
71
|
+
return {
|
|
72
|
+
valid: false,
|
|
73
|
+
reason: "No pending installments found",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// For now, require exact installment amount
|
|
77
|
+
// Could be modified to allow overpayment later
|
|
78
|
+
if (amount !== nextInstallment.amount) {
|
|
79
|
+
return {
|
|
80
|
+
valid: false,
|
|
81
|
+
reason: `Payment amount must equal the next installment amount of ${nextInstallment.amount}`,
|
|
82
|
+
nextInstallment,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { valid: true, nextInstallment };
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Calculate new values after payment
|
|
89
|
+
*/
|
|
90
|
+
calculatePaymentEffects(plan, amount) {
|
|
91
|
+
const newPaidAmount = plan.paidAmount + amount;
|
|
92
|
+
const newOutstandingAmount = Math.max(0, plan.outstandingAmount - amount);
|
|
93
|
+
const wasCompleted = plan.status === "COMPLETED";
|
|
94
|
+
const isNowCompleted = newOutstandingAmount <= 0;
|
|
95
|
+
const totalAmount = plan.pricingSnapshot.totalAmount;
|
|
96
|
+
const progress = (newPaidAmount / totalAmount) * 100;
|
|
97
|
+
return {
|
|
98
|
+
newPaidAmount,
|
|
99
|
+
newOutstandingAmount,
|
|
100
|
+
isNowCompleted,
|
|
101
|
+
progress,
|
|
102
|
+
statusChanged: !wasCompleted && isNowCompleted,
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
/**
|
|
106
|
+
* Generate installments for a hire purchase plan
|
|
107
|
+
*/
|
|
108
|
+
async generateInstallments(plan, transaction) {
|
|
109
|
+
const pricing = plan.pricingSnapshot;
|
|
110
|
+
if (pricing.type !== "HIRE_PURCHASE" || !pricing.installmentCount) {
|
|
111
|
+
return; // No installments for full payment plans
|
|
112
|
+
}
|
|
113
|
+
const InstallmentModel = vr_models_1.Installment;
|
|
114
|
+
const installments = [];
|
|
115
|
+
const dueDate = new Date(); // First installment due after creation
|
|
116
|
+
if (pricing.installmentFrequency === "DAILY") {
|
|
117
|
+
dueDate.setDate(dueDate.getDate() + 1);
|
|
118
|
+
}
|
|
119
|
+
else if (pricing.installmentFrequency === "WEEKLY") {
|
|
120
|
+
dueDate.setDate(dueDate.getDate() + 7);
|
|
121
|
+
}
|
|
122
|
+
else if (pricing.installmentFrequency === "MONTHLY") {
|
|
123
|
+
dueDate.setMonth(dueDate.getMonth() + 1);
|
|
124
|
+
}
|
|
125
|
+
for (let i = 1; i <= pricing.installmentCount; i++) {
|
|
126
|
+
installments.push({
|
|
127
|
+
devicePaymentPlanId: plan.id,
|
|
128
|
+
installmentNumber: i,
|
|
129
|
+
amount: Number(pricing.installmentAmount),
|
|
130
|
+
dueDate: new Date(dueDate),
|
|
131
|
+
status: "PENDING",
|
|
132
|
+
isAutoGenerated: true,
|
|
133
|
+
});
|
|
134
|
+
// Calculate next due date
|
|
135
|
+
if (pricing.installmentFrequency === "DAILY") {
|
|
136
|
+
dueDate.setDate(dueDate.getDate() + 1);
|
|
137
|
+
}
|
|
138
|
+
else if (pricing.installmentFrequency === "WEEKLY") {
|
|
139
|
+
dueDate.setDate(dueDate.getDate() + 7);
|
|
140
|
+
}
|
|
141
|
+
else if (pricing.installmentFrequency === "MONTHLY") {
|
|
142
|
+
dueDate.setMonth(dueDate.getMonth() + 1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
await InstallmentModel.bulkCreate(installments, { transaction });
|
|
146
|
+
// Set the first installment as next due
|
|
147
|
+
plan.nextInstallmentDueAt = installments[0]?.dueDate || null;
|
|
148
|
+
await plan.save({ transaction });
|
|
149
|
+
},
|
|
150
|
+
/**
|
|
151
|
+
* Format plan for operation response
|
|
152
|
+
*/
|
|
153
|
+
formatPlanForOps(plan) {
|
|
154
|
+
if (!plan)
|
|
155
|
+
return null;
|
|
156
|
+
// Safely extract data without using toJSON() which exposes internal types
|
|
157
|
+
const planData = {
|
|
158
|
+
id: plan.id,
|
|
159
|
+
userId: plan.userId,
|
|
160
|
+
pricingSnapshot: plan.pricingSnapshot,
|
|
161
|
+
paidAmount: plan.paidAmount,
|
|
162
|
+
outstandingAmount: plan.outstandingAmount,
|
|
163
|
+
lastPaymentAt: plan.lastPaymentAt,
|
|
164
|
+
nextInstallmentDueAt: plan.nextInstallmentDueAt,
|
|
165
|
+
status: plan.status,
|
|
166
|
+
completedAt: plan.completedAt,
|
|
167
|
+
createdAt: plan.createdAt,
|
|
168
|
+
updatedAt: plan.updatedAt,
|
|
169
|
+
devices: plan.devices || [],
|
|
170
|
+
user: plan.user || null,
|
|
171
|
+
installments: plan.installments || [],
|
|
172
|
+
};
|
|
173
|
+
const totalAmount = planData.pricingSnapshot?.totalAmount || 0;
|
|
174
|
+
const hasInstallments = (planData.installments?.length ?? 0) > 0;
|
|
175
|
+
return {
|
|
176
|
+
id: planData.id,
|
|
177
|
+
userId: planData.userId,
|
|
178
|
+
pricingSnapshot: {
|
|
179
|
+
type: planData.pricingSnapshot?.type || null,
|
|
180
|
+
totalAmount: planData.pricingSnapshot?.totalAmount || 0,
|
|
181
|
+
downPayment: planData.pricingSnapshot?.downPayment || 0,
|
|
182
|
+
installmentAmount: planData.pricingSnapshot?.installmentAmount || null,
|
|
183
|
+
installmentFrequency: planData.pricingSnapshot?.installmentFrequency || null,
|
|
184
|
+
installmentCount: planData.pricingSnapshot?.installmentCount || null,
|
|
185
|
+
gracePeriodDays: planData.pricingSnapshot?.gracePeriodDays || 2,
|
|
186
|
+
autoLockOnMiss: planData.pricingSnapshot?.autoLockOnMiss ?? true,
|
|
187
|
+
},
|
|
188
|
+
paidAmount: planData.paidAmount || 0,
|
|
189
|
+
outstandingAmount: planData.outstandingAmount || 0,
|
|
190
|
+
lastPaymentAt: planData.lastPaymentAt,
|
|
191
|
+
nextInstallmentDueAt: planData.nextInstallmentDueAt,
|
|
192
|
+
status: planData.status || "ACTIVE",
|
|
193
|
+
completedAt: planData.completedAt,
|
|
194
|
+
createdAt: planData.createdAt,
|
|
195
|
+
updatedAt: planData.updatedAt,
|
|
196
|
+
devices: planData.devices || [],
|
|
197
|
+
user: planData.user || null,
|
|
198
|
+
installments: planData.installments || [],
|
|
199
|
+
progress: totalAmount > 0 ? (plan.paidAmount / totalAmount) * 100 : 0,
|
|
200
|
+
canEdit: plan.paidAmount === 0,
|
|
201
|
+
canDelete: plan.paidAmount === 0,
|
|
202
|
+
isOverdue: false,
|
|
203
|
+
hasInstallments,
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
};
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -9,5 +9,7 @@ export { suspensionUtil } from "./suspension.utils";
|
|
|
9
9
|
export { createDeviceSession } from "./session.utils";
|
|
10
10
|
export { generateOTP, getOTPExpiry, getVerificationMethod, sendEmail, sendSMS, sendVerificationCode, } from "./verification.utils";
|
|
11
11
|
export { getUserLevel, isRankEqual, isSubordinate, isSubordinateOrEqual, isSuperior, isSuperiorOrEqual, validateHierarchy, } from "./hierarchy.utils";
|
|
12
|
+
export { stockUtil } from "./admin.device.utils";
|
|
12
13
|
export { cloudinaryUtil } from "./product.utils";
|
|
13
|
-
export {
|
|
14
|
+
export { DPP_OperationsUtil } from "./admin.devicePayment.utils";
|
|
15
|
+
export { PendingRegistration, VerificationMethod, ConfirmResponse, UploadResult, StockChangeResult, } from "./types";
|
package/dist/utils/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isRankEqual = exports.getUserLevel = exports.sendVerificationCode = exports.sendSMS = exports.sendEmail = exports.getVerificationMethod = exports.getOTPExpiry = exports.generateOTP = exports.createDeviceSession = exports.suspensionUtil = exports.banUtil = exports.hasHigherAuthority = exports.getModeratableRoles = exports.canModerateUser = exports.canModerate = exports.softDeleteUser = exports.hashPassword = exports.validatePassword = exports.validateUniqueFields = exports.generateJacketId = exports.getSortOrder = exports.generateEventSearchConditions = exports.generateUserSearchConditions = exports.findSecurityClearanceByRole = exports.getUsersByRole = exports.getUserById = exports.isAccountAccessible = exports.canModifyAccount = exports.hasAllPermissions = exports.hasAnyPermission = exports.hasPermission = exports.hasRole = exports.formatUserListResponse = exports.formatUserProfile = exports.sendErrorResponse = exports.sendSuccessResponse = exports.logEvent = exports.formatTimeRemaining = exports.getTokenTimeRemaining = exports.generateToken = exports.shouldRefreshToken = exports.verifyToken = exports.generateRiderToken = exports.generatePassengerToken = exports.generateAdminToken = exports.checkSuspensionStatus = exports.checkIsUserBannedOrSuspended = exports.checkBanStatus = exports.hasActiveDependencies = exports.checkAccountDependencies = void 0;
|
|
4
|
-
exports.cloudinaryUtil = exports.validateHierarchy = exports.isSuperiorOrEqual = exports.isSuperior = exports.isSubordinateOrEqual = exports.isSubordinate = void 0;
|
|
4
|
+
exports.DPP_OperationsUtil = exports.cloudinaryUtil = exports.stockUtil = exports.validateHierarchy = exports.isSuperiorOrEqual = exports.isSuperior = exports.isSubordinateOrEqual = exports.isSubordinate = void 0;
|
|
5
5
|
var account_utils_1 = require("./account.utils");
|
|
6
6
|
Object.defineProperty(exports, "checkAccountDependencies", { enumerable: true, get: function () { return account_utils_1.checkAccountDependencies; } });
|
|
7
7
|
Object.defineProperty(exports, "hasActiveDependencies", { enumerable: true, get: function () { return account_utils_1.hasActiveDependencies; } });
|
|
@@ -75,5 +75,9 @@ Object.defineProperty(exports, "isSubordinateOrEqual", { enumerable: true, get:
|
|
|
75
75
|
Object.defineProperty(exports, "isSuperior", { enumerable: true, get: function () { return hierarchy_utils_1.isSuperior; } });
|
|
76
76
|
Object.defineProperty(exports, "isSuperiorOrEqual", { enumerable: true, get: function () { return hierarchy_utils_1.isSuperiorOrEqual; } });
|
|
77
77
|
Object.defineProperty(exports, "validateHierarchy", { enumerable: true, get: function () { return hierarchy_utils_1.validateHierarchy; } });
|
|
78
|
+
var admin_device_utils_1 = require("./admin.device.utils");
|
|
79
|
+
Object.defineProperty(exports, "stockUtil", { enumerable: true, get: function () { return admin_device_utils_1.stockUtil; } });
|
|
78
80
|
var product_utils_1 = require("./product.utils");
|
|
79
81
|
Object.defineProperty(exports, "cloudinaryUtil", { enumerable: true, get: function () { return product_utils_1.cloudinaryUtil; } });
|
|
82
|
+
var admin_devicePayment_utils_1 = require("./admin.devicePayment.utils");
|
|
83
|
+
Object.defineProperty(exports, "DPP_OperationsUtil", { enumerable: true, get: function () { return admin_devicePayment_utils_1.DPP_OperationsUtil; } });
|
package/dist/utils/types.d.ts
CHANGED