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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payservedb",
3
- "version": "6.4.4",
3
+ "version": "6.4.6",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -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;
@@ -36,8 +36,14 @@ const dutyRosterSchema = new mongoose.Schema(
36
36
  ref: "MasterWorkplanAssignment",
37
37
  required: true,
38
38
  },
39
-
40
- // Regular weekly schedule - now array of time slots for each day
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
- // Status code definitions
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
- const schedule = this.weeklySchedule;
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;
@@ -9,6 +9,7 @@ const masterWorkplanSchema = new mongoose.Schema({
9
9
  title: {
10
10
  type: String,
11
11
  required: true,
12
+ unique: true,
12
13
  },
13
14
  description: {
14
15
  type: String,
@@ -44,7 +44,7 @@ const analogBillingSchema = new mongoose.Schema({
44
44
  },
45
45
  status: {
46
46
  type: String,
47
- enum: ['pending', 'reviewed', 'billed'],
47
+ enum: ['pending', 'reviewed', 'billed', 'failed'],
48
48
  default: 'pending',
49
49
  },
50
50
  }, {