payservedb 6.4.4 → 6.4.6
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/index.js
CHANGED
|
@@ -203,6 +203,7 @@ const models = {
|
|
|
203
203
|
NegativeBalance: require("./src/models/water_meter_negative_amounts"),
|
|
204
204
|
MasterWorkplan: require("./src/models/master_workplan"),
|
|
205
205
|
MasterWorkplanChild: require("./src/models/master_workplan_child"),
|
|
206
|
+
DutyRosterChecklist: require("./src/models/dutyRoasterChecklist"),
|
|
206
207
|
};
|
|
207
208
|
|
|
208
209
|
// Function to get models dynamically from a specific database connection
|
package/package.json
CHANGED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
|
|
3
|
+
const DutyRosterChecklistSchema = new mongoose.Schema(
|
|
4
|
+
{
|
|
5
|
+
dutyRosterId: {
|
|
6
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
7
|
+
ref: "DutyRoster",
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
facilityId: {
|
|
11
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
12
|
+
ref: "Facility",
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
staffId: {
|
|
16
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
17
|
+
ref: "User",
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
masterWorkplanId: {
|
|
21
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
22
|
+
ref: "MasterWorkplan",
|
|
23
|
+
required: true,
|
|
24
|
+
},
|
|
25
|
+
// Array of tasks with their individual dates and status
|
|
26
|
+
tasks: [
|
|
27
|
+
{
|
|
28
|
+
childWorkplanId: {
|
|
29
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
30
|
+
ref: "ChildWorkplan",
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
// Each task can have multiple scheduled dates
|
|
34
|
+
scheduledDates: [
|
|
35
|
+
{
|
|
36
|
+
date: {
|
|
37
|
+
type: Date,
|
|
38
|
+
required: true,
|
|
39
|
+
},
|
|
40
|
+
status: {
|
|
41
|
+
type: String,
|
|
42
|
+
enum: [
|
|
43
|
+
"pending",
|
|
44
|
+
"completed",
|
|
45
|
+
"missed",
|
|
46
|
+
"cancelled",
|
|
47
|
+
"in-progress",
|
|
48
|
+
],
|
|
49
|
+
default: "pending",
|
|
50
|
+
required: true,
|
|
51
|
+
},
|
|
52
|
+
completedAt: {
|
|
53
|
+
type: Date,
|
|
54
|
+
},
|
|
55
|
+
completedBy: {
|
|
56
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
57
|
+
ref: "User",
|
|
58
|
+
},
|
|
59
|
+
notes: {
|
|
60
|
+
type: String,
|
|
61
|
+
maxlength: 500,
|
|
62
|
+
},
|
|
63
|
+
// Time tracking
|
|
64
|
+
startTime: String,
|
|
65
|
+
endTime: String,
|
|
66
|
+
actualDuration: Number, // in minutes
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
// Task metadata
|
|
70
|
+
priority: {
|
|
71
|
+
type: String,
|
|
72
|
+
enum: ["low", "medium", "high", "critical"],
|
|
73
|
+
default: "medium",
|
|
74
|
+
},
|
|
75
|
+
estimatedDuration: Number, // in minutes
|
|
76
|
+
isRecurring: {
|
|
77
|
+
type: Boolean,
|
|
78
|
+
default: false,
|
|
79
|
+
},
|
|
80
|
+
recurringPattern: {
|
|
81
|
+
type: String,
|
|
82
|
+
enum: ["daily", "weekly", "monthly", "custom"],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
// Overall checklist metadata
|
|
87
|
+
period: {
|
|
88
|
+
startDate: {
|
|
89
|
+
type: Date,
|
|
90
|
+
required: true,
|
|
91
|
+
},
|
|
92
|
+
endDate: {
|
|
93
|
+
type: Date,
|
|
94
|
+
required: true,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
// Summary statistics
|
|
98
|
+
summary: {
|
|
99
|
+
totalTasks: {
|
|
100
|
+
type: Number,
|
|
101
|
+
default: 0,
|
|
102
|
+
},
|
|
103
|
+
completedTasks: {
|
|
104
|
+
type: Number,
|
|
105
|
+
default: 0,
|
|
106
|
+
},
|
|
107
|
+
pendingTasks: {
|
|
108
|
+
type: Number,
|
|
109
|
+
default: 0,
|
|
110
|
+
},
|
|
111
|
+
missedTasks: {
|
|
112
|
+
type: Number,
|
|
113
|
+
default: 0,
|
|
114
|
+
},
|
|
115
|
+
completionPercentage: {
|
|
116
|
+
type: Number,
|
|
117
|
+
default: 0,
|
|
118
|
+
min: 0,
|
|
119
|
+
max: 100,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
status: {
|
|
123
|
+
type: String,
|
|
124
|
+
enum: ["active", "completed", "expired", "cancelled"],
|
|
125
|
+
default: "active",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
timestamps: true,
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Pre-save middleware to update summary statistics
|
|
134
|
+
DutyRosterChecklistSchema.pre("save", function (next) {
|
|
135
|
+
// Validate period dates
|
|
136
|
+
if (this.period.startDate >= this.period.endDate) {
|
|
137
|
+
return next(new Error("End date must be after start date"));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Calculate summary statistics
|
|
141
|
+
let totalScheduledDates = 0;
|
|
142
|
+
let completedScheduledDates = 0;
|
|
143
|
+
let pendingScheduledDates = 0;
|
|
144
|
+
let missedScheduledDates = 0;
|
|
145
|
+
|
|
146
|
+
this.tasks.forEach((task) => {
|
|
147
|
+
task.scheduledDates.forEach((scheduledDate) => {
|
|
148
|
+
totalScheduledDates++;
|
|
149
|
+
switch (scheduledDate.status) {
|
|
150
|
+
case "completed":
|
|
151
|
+
completedScheduledDates++;
|
|
152
|
+
break;
|
|
153
|
+
case "pending":
|
|
154
|
+
case "in-progress":
|
|
155
|
+
pendingScheduledDates++;
|
|
156
|
+
break;
|
|
157
|
+
case "missed":
|
|
158
|
+
missedScheduledDates++;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
this.summary.totalTasks = totalScheduledDates;
|
|
165
|
+
this.summary.completedTasks = completedScheduledDates;
|
|
166
|
+
this.summary.pendingTasks = pendingScheduledDates;
|
|
167
|
+
this.summary.missedTasks = missedScheduledDates;
|
|
168
|
+
this.summary.completionPercentage =
|
|
169
|
+
totalScheduledDates > 0
|
|
170
|
+
? Math.round((completedScheduledDates / totalScheduledDates) * 100)
|
|
171
|
+
: 0;
|
|
172
|
+
|
|
173
|
+
next();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Static method to generate dates between start and end date
|
|
177
|
+
DutyRosterChecklistSchema.statics.generateDateRange = function (
|
|
178
|
+
startDate,
|
|
179
|
+
endDate,
|
|
180
|
+
frequency = "daily",
|
|
181
|
+
) {
|
|
182
|
+
const dates = [];
|
|
183
|
+
const current = new Date(startDate);
|
|
184
|
+
const end = new Date(endDate);
|
|
185
|
+
|
|
186
|
+
while (current <= end) {
|
|
187
|
+
dates.push(new Date(current));
|
|
188
|
+
|
|
189
|
+
switch (frequency) {
|
|
190
|
+
case "daily":
|
|
191
|
+
current.setDate(current.getDate() + 1);
|
|
192
|
+
break;
|
|
193
|
+
case "weekly":
|
|
194
|
+
current.setDate(current.getDate() + 7);
|
|
195
|
+
break;
|
|
196
|
+
case "monthly":
|
|
197
|
+
current.setMonth(current.getMonth() + 1);
|
|
198
|
+
break;
|
|
199
|
+
default:
|
|
200
|
+
current.setDate(current.getDate() + 1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return dates;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// Instance method to update task status
|
|
208
|
+
DutyRosterChecklistSchema.methods.updateTaskStatus = function (
|
|
209
|
+
taskId,
|
|
210
|
+
dateId,
|
|
211
|
+
status,
|
|
212
|
+
completedBy,
|
|
213
|
+
notes,
|
|
214
|
+
) {
|
|
215
|
+
const task = this.tasks.id(taskId);
|
|
216
|
+
if (!task) {
|
|
217
|
+
throw new Error("Task not found");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const scheduledDate = task.scheduledDates.id(dateId);
|
|
221
|
+
if (!scheduledDate) {
|
|
222
|
+
throw new Error("Scheduled date not found");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
scheduledDate.status = status;
|
|
226
|
+
if (status === "completed") {
|
|
227
|
+
scheduledDate.completedAt = new Date();
|
|
228
|
+
scheduledDate.completedBy = completedBy;
|
|
229
|
+
}
|
|
230
|
+
if (notes) {
|
|
231
|
+
scheduledDate.notes = notes;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return this.save();
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Add indexes for efficient queries
|
|
238
|
+
DutyRosterChecklistSchema.index({ dutyRosterId: 1 });
|
|
239
|
+
DutyRosterChecklistSchema.index({ facilityId: 1, staffId: 1 });
|
|
240
|
+
DutyRosterChecklistSchema.index({ masterWorkplanId: 1 });
|
|
241
|
+
DutyRosterChecklistSchema.index({ "period.startDate": 1, "period.endDate": 1 });
|
|
242
|
+
DutyRosterChecklistSchema.index({ "tasks.scheduledDates.date": 1 });
|
|
243
|
+
DutyRosterChecklistSchema.index({ status: 1 });
|
|
244
|
+
|
|
245
|
+
const DutyRosterChecklist = mongoose.model(
|
|
246
|
+
"DutyRosterChecklist",
|
|
247
|
+
DutyRosterChecklistSchema,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
module.exports = DutyRosterChecklist;
|
package/src/models/dutyroster.js
CHANGED
|
@@ -36,8 +36,14 @@ const dutyRosterSchema = new mongoose.Schema(
|
|
|
36
36
|
ref: "MasterWorkplanAssignment",
|
|
37
37
|
required: true,
|
|
38
38
|
},
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
startDate: {
|
|
40
|
+
type: Date,
|
|
41
|
+
required: true,
|
|
42
|
+
},
|
|
43
|
+
endDate: {
|
|
44
|
+
type: Date,
|
|
45
|
+
required: true,
|
|
46
|
+
},
|
|
41
47
|
weeklySchedule: {
|
|
42
48
|
monday: [timeSlotSchema],
|
|
43
49
|
tuesday: [timeSlotSchema],
|
|
@@ -47,7 +53,6 @@ const dutyRosterSchema = new mongoose.Schema(
|
|
|
47
53
|
saturday: [timeSlotSchema],
|
|
48
54
|
sunday: [timeSlotSchema],
|
|
49
55
|
},
|
|
50
|
-
|
|
51
56
|
// For exceptions to the regular schedule
|
|
52
57
|
exceptions: [
|
|
53
58
|
{
|
|
@@ -65,7 +70,6 @@ const dutyRosterSchema = new mongoose.Schema(
|
|
|
65
70
|
reason: String,
|
|
66
71
|
},
|
|
67
72
|
],
|
|
68
|
-
|
|
69
73
|
// Metadata
|
|
70
74
|
metadata: {
|
|
71
75
|
period: {
|
|
@@ -79,21 +83,14 @@ const dutyRosterSchema = new mongoose.Schema(
|
|
|
79
83
|
},
|
|
80
84
|
);
|
|
81
85
|
|
|
82
|
-
//
|
|
83
|
-
dutyRosterSchema.statics.STATUS_CODES = {
|
|
84
|
-
ON: "On Duty",
|
|
85
|
-
OFF: "Off Duty",
|
|
86
|
-
AL: "Annual Leave",
|
|
87
|
-
CL: "Casual Leave",
|
|
88
|
-
"ML/PL": "Medical/Paternity Leave",
|
|
89
|
-
PH: "Public Holiday",
|
|
90
|
-
UI: "Unplanned Issues",
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
// Pre-save middleware to handle backward compatibility with old format
|
|
86
|
+
// Add validation to ensure endDate is after startDate
|
|
94
87
|
dutyRosterSchema.pre("save", function (next) {
|
|
95
|
-
|
|
88
|
+
// Validate date range
|
|
89
|
+
if (this.startDate && this.endDate && this.startDate >= this.endDate) {
|
|
90
|
+
return next(new Error("End date must be after start date"));
|
|
91
|
+
}
|
|
96
92
|
|
|
93
|
+
const schedule = this.weeklySchedule;
|
|
97
94
|
// Convert any day that's not an array to an array with a single element
|
|
98
95
|
for (const day of [
|
|
99
96
|
"monday",
|
|
@@ -107,7 +104,6 @@ dutyRosterSchema.pre("save", function (next) {
|
|
|
107
104
|
if (schedule[day] && !Array.isArray(schedule[day])) {
|
|
108
105
|
schedule[day] = [schedule[day]];
|
|
109
106
|
}
|
|
110
|
-
|
|
111
107
|
// Ensure each day has at least one time slot
|
|
112
108
|
if (!schedule[day] || schedule[day].length === 0) {
|
|
113
109
|
schedule[day] = [
|
|
@@ -119,8 +115,22 @@ dutyRosterSchema.pre("save", function (next) {
|
|
|
119
115
|
];
|
|
120
116
|
}
|
|
121
117
|
}
|
|
122
|
-
|
|
123
118
|
next();
|
|
124
119
|
});
|
|
125
120
|
|
|
121
|
+
// Status code definitions
|
|
122
|
+
dutyRosterSchema.statics.STATUS_CODES = {
|
|
123
|
+
ON: "On Duty",
|
|
124
|
+
OFF: "Off Duty",
|
|
125
|
+
AL: "Annual Leave",
|
|
126
|
+
CL: "Casual Leave",
|
|
127
|
+
"ML/PL": "Medical/Paternity Leave",
|
|
128
|
+
PH: "Public Holiday",
|
|
129
|
+
UI: "Unplanned Issues",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Add index for efficient date range queries
|
|
133
|
+
dutyRosterSchema.index({ startDate: 1, endDate: 1 });
|
|
134
|
+
dutyRosterSchema.index({ facilityId: 1, startDate: 1, endDate: 1 });
|
|
135
|
+
|
|
126
136
|
module.exports = mongoose.model("DutyRoster", dutyRosterSchema);
|
|
@@ -26,6 +26,13 @@ const leaseAgreementSchema = new mongoose.Schema({
|
|
|
26
26
|
ref: 'Customer',
|
|
27
27
|
required: true
|
|
28
28
|
},
|
|
29
|
+
|
|
30
|
+
// *** NEW FIELD: Add billing address support ***
|
|
31
|
+
billerAddressId: {
|
|
32
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
33
|
+
ref: 'BillerAddress'
|
|
34
|
+
},
|
|
35
|
+
|
|
29
36
|
// GL Account configurations
|
|
30
37
|
invoiceDoubleEntryAccount: {
|
|
31
38
|
type: mongoose.Schema.Types.ObjectId,
|
|
@@ -92,6 +99,28 @@ const leaseAgreementSchema = new mongoose.Schema({
|
|
|
92
99
|
type: mongoose.Schema.Types.ObjectId,
|
|
93
100
|
ref: 'Penalty'
|
|
94
101
|
},
|
|
102
|
+
|
|
103
|
+
// *** ENHANCED: Add payment configuration flags ***
|
|
104
|
+
bankPayment: {
|
|
105
|
+
type: Boolean,
|
|
106
|
+
default: false
|
|
107
|
+
},
|
|
108
|
+
mobilePayment: {
|
|
109
|
+
type: Boolean,
|
|
110
|
+
default: false
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// *** ENHANCED: Add structured payment method references ***
|
|
114
|
+
bankAccountId: {
|
|
115
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
116
|
+
ref: 'BankAccount'
|
|
117
|
+
},
|
|
118
|
+
paymentMethodId: {
|
|
119
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
120
|
+
ref: 'PaymentMethod'
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// Existing M-Pesa fields (keep for backward compatibility)
|
|
95
124
|
mpesaEnabled: {
|
|
96
125
|
type: Boolean,
|
|
97
126
|
default: false
|
|
@@ -101,6 +130,7 @@ const leaseAgreementSchema = new mongoose.Schema({
|
|
|
101
130
|
accountNumber: String,
|
|
102
131
|
phoneNumber: String
|
|
103
132
|
},
|
|
133
|
+
|
|
104
134
|
escalations: [{
|
|
105
135
|
_id: { type: mongoose.Schema.Types.ObjectId, auto: true },
|
|
106
136
|
effectiveDate: { type: Date, required: true },
|
|
@@ -219,4 +249,4 @@ leaseAgreementSchema.index({ facilityId: 1 });
|
|
|
219
249
|
|
|
220
250
|
const LeaseAgreement = mongoose.model('LeaseAgreement', leaseAgreementSchema);
|
|
221
251
|
|
|
222
|
-
module.exports = LeaseAgreement;
|
|
252
|
+
module.exports = LeaseAgreement;
|