payservedb 8.3.1 → 8.3.2

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.
Files changed (177) hide show
  1. package/.env +2 -2
  2. package/.idea/material_theme_project_new.xml +12 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/psdb.iml +12 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/index.js +306 -306
  7. package/package.json +17 -17
  8. package/src/models/account.js +52 -52
  9. package/src/models/agent_departments.js +59 -59
  10. package/src/models/agent_notifications.js +53 -53
  11. package/src/models/agent_performance.js +127 -127
  12. package/src/models/agent_roles.js +77 -77
  13. package/src/models/agents.js +154 -154
  14. package/src/models/apilog.js +18 -18
  15. package/src/models/approvalsWorkflows.js +49 -49
  16. package/src/models/archivedapilog.js +18 -18
  17. package/src/models/asset.js +92 -92
  18. package/src/models/assetsAssignment.js +64 -64
  19. package/src/models/auditTrail.js +346 -346
  20. package/src/models/bankdetails.js +47 -43
  21. package/src/models/billerAddress.js +124 -124
  22. package/src/models/booking_invoice.js +151 -151
  23. package/src/models/bookinganalytics.js +63 -63
  24. package/src/models/bookingconfig.js +45 -45
  25. package/src/models/bookingproperty.js +122 -122
  26. package/src/models/bookingreservation.js +192 -192
  27. package/src/models/bookingrevenuerecord.js +84 -84
  28. package/src/models/budget.js +95 -95
  29. package/src/models/budgetCategory.js +19 -19
  30. package/src/models/campaigns.js +108 -108
  31. package/src/models/cashpayment.js +264 -264
  32. package/src/models/combinedUnits.js +62 -62
  33. package/src/models/common_area_electricity.js +38 -38
  34. package/src/models/common_area_generator.js +41 -41
  35. package/src/models/common_area_utility_alert.js +37 -37
  36. package/src/models/common_area_water.js +39 -39
  37. package/src/models/communication_status.js +33 -33
  38. package/src/models/communication_user_opt.js +32 -32
  39. package/src/models/company.js +53 -53
  40. package/src/models/coreBaseSettings.js +16 -16
  41. package/src/models/coreInvoiceSettings.js +100 -100
  42. package/src/models/counter_schema.js +21 -21
  43. package/src/models/country_tax.js +42 -42
  44. package/src/models/currency_settings.js +39 -39
  45. package/src/models/customer.js +210 -210
  46. package/src/models/customer_satisfaction_survey.js +278 -278
  47. package/src/models/customer_surveys.js +139 -139
  48. package/src/models/customer_tickets.js +239 -239
  49. package/src/models/dailyChecklist.js +312 -312
  50. package/src/models/default_payment_details.js +17 -17
  51. package/src/models/deliveryTimeMarks.js +18 -18
  52. package/src/models/document_type.js +19 -19
  53. package/src/models/dutyRosterChecklist.js +250 -250
  54. package/src/models/dutyroster.js +136 -136
  55. package/src/models/email.js +37 -37
  56. package/src/models/email_sms_queue.js +61 -61
  57. package/src/models/entry_exit.js +53 -53
  58. package/src/models/expense.js +99 -99
  59. package/src/models/expense_category.js +45 -45
  60. package/src/models/facility.js +62 -62
  61. package/src/models/facilityBillingPrices.js +29 -29
  62. package/src/models/facilityInvoice.js +223 -224
  63. package/src/models/facilityInvoicePayment.js +47 -52
  64. package/src/models/facilityInvoiceRecipient.js +32 -32
  65. package/src/models/facilityWalletTransactionsMetadata.js +236 -236
  66. package/src/models/facility_departements.js +20 -20
  67. package/src/models/facility_payment_details.js +20 -20
  68. package/src/models/facilityasset.js +25 -25
  69. package/src/models/faq.js +18 -18
  70. package/src/models/gl_account_double_entries.js +25 -25
  71. package/src/models/gl_accounts.js +56 -56
  72. package/src/models/gl_entries.js +49 -49
  73. package/src/models/goodsReceivedNotes.js +115 -115
  74. package/src/models/guard.js +47 -47
  75. package/src/models/handover.js +247 -247
  76. package/src/models/inspection_category.js +38 -38
  77. package/src/models/invoice.js +387 -387
  78. package/src/models/invoicing_schedule.js +36 -36
  79. package/src/models/item_inspection.js +96 -96
  80. package/src/models/knowledge_base.js +109 -109
  81. package/src/models/knowledge_base_rating.js +44 -44
  82. package/src/models/leaseagreement.js +236 -243
  83. package/src/models/leasetemplate.js +17 -17
  84. package/src/models/levy.js +223 -223
  85. package/src/models/levy_invoice_settings.js +26 -26
  86. package/src/models/levycontract.js +173 -173
  87. package/src/models/levytype.js +23 -23
  88. package/src/models/maintenance_service_vendor.js +38 -38
  89. package/src/models/maintenance_services.js +17 -17
  90. package/src/models/maintenancerequisition.js +31 -31
  91. package/src/models/master_workplan.js +32 -32
  92. package/src/models/master_workplan_child.js +34 -34
  93. package/src/models/message.js +38 -38
  94. package/src/models/module.js +21 -21
  95. package/src/models/notification.js +44 -44
  96. package/src/models/paymentTermsMarks.js +19 -19
  97. package/src/models/penalty.js +76 -76
  98. package/src/models/pendingCredentials.js +32 -32
  99. package/src/models/powerMeterCommunicationProtocol.js +17 -17
  100. package/src/models/powerMeterCustomerAccount.js +78 -78
  101. package/src/models/powerMeterCustomerBand.js +14 -14
  102. package/src/models/powerMeterDailyReading.js +30 -30
  103. package/src/models/powerMeterGateways.js +40 -40
  104. package/src/models/powerMeterMonthlyReading.js +34 -34
  105. package/src/models/powerMeterPowerCharges.js +85 -85
  106. package/src/models/powerMeterSettings.js +159 -159
  107. package/src/models/powerMeterSingleDayReading.js +32 -32
  108. package/src/models/powerMeters.js +116 -116
  109. package/src/models/powerMetersManufacturer.js +14 -14
  110. package/src/models/power_meter_account.js +81 -81
  111. package/src/models/power_meter_command_logs.js +30 -30
  112. package/src/models/power_meter_command_queue.js +33 -33
  113. package/src/models/power_meter_negative_balance.js +44 -44
  114. package/src/models/power_prepaid_credits.js +47 -47
  115. package/src/models/power_prepaid_debits.js +53 -53
  116. package/src/models/power_prepaid_orders.js +78 -78
  117. package/src/models/power_sms_notification.js +26 -26
  118. package/src/models/propertyManagerContract.js +556 -556
  119. package/src/models/propertyManagerRevenue.js +195 -195
  120. package/src/models/purchaseOrderInvoice.js +74 -74
  121. package/src/models/purchase_order.js +213 -213
  122. package/src/models/purchase_request.js +110 -110
  123. package/src/models/refresh_token.js +23 -23
  124. package/src/models/reminder.js +197 -197
  125. package/src/models/report.js +13 -13
  126. package/src/models/resident.js +121 -121
  127. package/src/models/rfq_details.js +131 -131
  128. package/src/models/rfq_response.js +153 -153
  129. package/src/models/service_charge_invoice_upload.js +42 -42
  130. package/src/models/service_charge_payments.js +27 -27
  131. package/src/models/servicerequest.js +55 -55
  132. package/src/models/settings.js +62 -62
  133. package/src/models/short_urls.js +21 -21
  134. package/src/models/smart_meter_daily_consumption.js +44 -44
  135. package/src/models/sms_africastalking.js +20 -20
  136. package/src/models/sms_balance_notification.js +26 -26
  137. package/src/models/sms_meliora.js +20 -20
  138. package/src/models/staff.js +36 -36
  139. package/src/models/stocksandspare.js +161 -161
  140. package/src/models/suppliers.js +74 -74
  141. package/src/models/tickets.js +173 -173
  142. package/src/models/tickets_category.js +72 -72
  143. package/src/models/unitManagementTemplate.js +44 -44
  144. package/src/models/unitasset.js +25 -25
  145. package/src/models/units.js +118 -118
  146. package/src/models/user.js +186 -186
  147. package/src/models/valueaddedservices.js +79 -79
  148. package/src/models/vas_invoices_upload.js +50 -50
  149. package/src/models/vas_payments.js +24 -24
  150. package/src/models/vasinvoice.js +192 -192
  151. package/src/models/vasvendor.js +57 -57
  152. package/src/models/visitLog.js +95 -95
  153. package/src/models/visitor.js +67 -67
  154. package/src/models/waitlist.js +45 -45
  155. package/src/models/wallet.js +44 -44
  156. package/src/models/wallet_transactions.js +50 -50
  157. package/src/models/water_invoice.js +351 -351
  158. package/src/models/water_meter_Command_Queue.js +33 -33
  159. package/src/models/water_meter_account.js +82 -82
  160. package/src/models/water_meter_billing.js +58 -58
  161. package/src/models/water_meter_communication.js +17 -17
  162. package/src/models/water_meter_communication_logs.js +39 -39
  163. package/src/models/water_meter_concentrator.js +70 -70
  164. package/src/models/water_meter_daily_history.js +32 -32
  165. package/src/models/water_meter_high_risk.js +36 -36
  166. package/src/models/water_meter_iot_cards.js +34 -34
  167. package/src/models/water_meter_manufacturer.js +35 -35
  168. package/src/models/water_meter_monthly_history.js +36 -36
  169. package/src/models/water_meter_negative_amounts.js +44 -44
  170. package/src/models/water_meter_settings.js +276 -276
  171. package/src/models/water_meter_single_day_history.js +34 -34
  172. package/src/models/water_meter_size.js +15 -15
  173. package/src/models/water_meters.js +133 -133
  174. package/src/models/water_meters_delivery.js +76 -76
  175. package/src/models/water_prepaid_credit.js +47 -47
  176. package/src/models/water_prepaid_debit.js +50 -50
  177. package/src/models/workorder.js +49 -49
@@ -1,278 +1,278 @@
1
- const mongoose = require('mongoose');
2
-
3
- const customerSatisfactionSurveySchema = new mongoose.Schema({
4
- ticket_id: {
5
- type: mongoose.Schema.Types.ObjectId,
6
- ref: 'CustomerTicket',
7
- required: true,
8
- unique: true
9
- },
10
- ticket_number: {
11
- type: String,
12
- required: true
13
- },
14
- customer_id: {
15
- type: mongoose.Schema.Types.ObjectId,
16
- ref: 'Customer',
17
- required: true
18
- },
19
- facility_id: {
20
- type: mongoose.Schema.Types.ObjectId,
21
- ref: 'Facility',
22
- required: true
23
- },
24
- assigned_agent_id: {
25
- type: mongoose.Schema.Types.ObjectId,
26
- ref: 'User'
27
- },
28
- custom_caller: {
29
- name: {
30
- type: String,
31
- trim: true,
32
- comment: 'Name of the person who reported the ticket (if different from customer)'
33
- },
34
- phone: {
35
- type: String,
36
- trim: true,
37
- comment: 'Phone number of the custom caller'
38
- },
39
- email: {
40
- type: String,
41
- trim: true,
42
- comment: 'Email of the custom caller'
43
- },
44
- relationship: {
45
- type: String,
46
- trim: true,
47
- comment: 'Relationship to customer (e.g., spouse, agent, caretaker)'
48
- }
49
- },
50
- survey_token: {
51
- type: String,
52
- required: true,
53
- unique: true,
54
- index: true
55
- },
56
- // Survey Responses
57
- overall_satisfaction: {
58
- type: Number,
59
- min: 1,
60
- max: 5,
61
- comment: 'Overall satisfaction rating (1-5 stars)'
62
- },
63
- resolution_quality: {
64
- type: Number,
65
- min: 1,
66
- max: 5,
67
- comment: 'Quality of resolution (1-5 stars)'
68
- },
69
- response_time_satisfaction: {
70
- type: Number,
71
- min: 1,
72
- max: 5,
73
- comment: 'Satisfaction with response time (1-5 stars)'
74
- },
75
- agent_professionalism: {
76
- type: Number,
77
- min: 1,
78
- max: 5,
79
- comment: 'Agent professionalism rating (1-5 stars)'
80
- },
81
- agent_knowledge: {
82
- type: Number,
83
- min: 1,
84
- max: 5,
85
- comment: 'Agent knowledge rating (1-5 stars)'
86
- },
87
- communication_clarity: {
88
- type: Number,
89
- min: 1,
90
- max: 5,
91
- comment: 'Communication clarity rating (1-5 stars)'
92
- },
93
- // NPS (Net Promoter Score)
94
- would_recommend: {
95
- type: Number,
96
- min: 0,
97
- max: 10,
98
- comment: 'Likelihood to recommend (0-10 scale)'
99
- },
100
- // Open-ended feedback
101
- positive_feedback: {
102
- type: String,
103
- maxlength: 1000,
104
- comment: 'What did we do well?'
105
- },
106
- improvement_feedback: {
107
- type: String,
108
- maxlength: 1000,
109
- comment: 'What could we improve?'
110
- },
111
- additional_comments: {
112
- type: String,
113
- maxlength: 2000
114
- },
115
- // Issue resolution
116
- issue_fully_resolved: {
117
- type: Boolean,
118
- comment: 'Was the issue fully resolved?'
119
- },
120
- issue_resolution_details: {
121
- type: String,
122
- maxlength: 500,
123
- comment: 'If not fully resolved, please explain'
124
- },
125
- // Metadata
126
- status: {
127
- type: String,
128
- enum: ['pending', 'completed', 'expired'],
129
- default: 'pending'
130
- },
131
- sent_at: {
132
- type: Date,
133
- default: Date.now
134
- },
135
- completed_at: {
136
- type: Date
137
- },
138
- expired_at: {
139
- type: Date,
140
- comment: 'Survey expires 7 days after sending'
141
- },
142
- reminder_sent: {
143
- type: Boolean,
144
- default: false
145
- },
146
- reminder_sent_at: {
147
- type: Date
148
- },
149
- ip_address: String,
150
- user_agent: String,
151
- response_time_seconds: {
152
- type: Number,
153
- comment: 'Time taken to complete survey in seconds'
154
- },
155
- created_at: {
156
- type: Date,
157
- default: Date.now
158
- },
159
- updated_at: {
160
- type: Date,
161
- default: Date.now
162
- }
163
- }, {
164
- timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
165
- });
166
-
167
- // Indexes for performance
168
- customerSatisfactionSurveySchema.index({ survey_token: 1 });
169
- customerSatisfactionSurveySchema.index({ customer_id: 1, created_at: -1 });
170
- customerSatisfactionSurveySchema.index({ facility_id: 1, status: 1 });
171
- customerSatisfactionSurveySchema.index({ assigned_agent_id: 1, status: 1 });
172
- customerSatisfactionSurveySchema.index({ status: 1, expired_at: 1 });
173
- customerSatisfactionSurveySchema.index({ ticket_id: 1 });
174
-
175
- // Virtual for calculating NPS category
176
- customerSatisfactionSurveySchema.virtual('nps_category').get(function() {
177
- if (this.would_recommend === null || this.would_recommend === undefined) {
178
- return null;
179
- }
180
- if (this.would_recommend >= 9) return 'promoter';
181
- if (this.would_recommend >= 7) return 'passive';
182
- return 'detractor';
183
- });
184
-
185
- // Virtual for calculating average rating
186
- customerSatisfactionSurveySchema.virtual('average_rating').get(function() {
187
- const ratings = [
188
- this.overall_satisfaction,
189
- this.resolution_quality,
190
- this.response_time_satisfaction,
191
- this.agent_professionalism,
192
- this.agent_knowledge,
193
- this.communication_clarity
194
- ].filter(r => r !== null && r !== undefined);
195
-
196
- if (ratings.length === 0) return null;
197
- return (ratings.reduce((sum, r) => sum + r, 0) / ratings.length).toFixed(2);
198
- });
199
-
200
- // Method to check if survey is expired
201
- customerSatisfactionSurveySchema.methods.isExpired = function() {
202
- return this.expired_at && new Date() > this.expired_at;
203
- };
204
-
205
- // Method to mark survey as completed
206
- customerSatisfactionSurveySchema.methods.markCompleted = function() {
207
- this.status = 'completed';
208
- this.completed_at = new Date();
209
- return this.save();
210
- };
211
-
212
- // Static method to get analytics
213
- customerSatisfactionSurveySchema.statics.getAnalytics = async function(filters = {}) {
214
- const match = { status: 'completed' };
215
-
216
- if (filters.facility_id) match.facility_id = mongoose.Types.ObjectId(filters.facility_id);
217
- if (filters.assigned_agent_id) match.assigned_agent_id = mongoose.Types.ObjectId(filters.assigned_agent_id);
218
- if (filters.start_date) match.completed_at = { $gte: new Date(filters.start_date) };
219
- if (filters.end_date) {
220
- match.completed_at = match.completed_at || {};
221
- match.completed_at.$lte = new Date(filters.end_date);
222
- }
223
-
224
- const analytics = await this.aggregate([
225
- { $match: match },
226
- {
227
- $group: {
228
- _id: null,
229
- total_responses: { $sum: 1 },
230
- avg_overall_satisfaction: { $avg: '$overall_satisfaction' },
231
- avg_resolution_quality: { $avg: '$resolution_quality' },
232
- avg_response_time_satisfaction: { $avg: '$response_time_satisfaction' },
233
- avg_agent_professionalism: { $avg: '$agent_professionalism' },
234
- avg_agent_knowledge: { $avg: '$agent_knowledge' },
235
- avg_communication_clarity: { $avg: '$communication_clarity' },
236
- avg_nps: { $avg: '$would_recommend' },
237
- fully_resolved_count: {
238
- $sum: { $cond: ['$issue_fully_resolved', 1, 0] }
239
- },
240
- promoters: {
241
- $sum: { $cond: [{ $gte: ['$would_recommend', 9] }, 1, 0] }
242
- },
243
- passives: {
244
- $sum: { $cond: [{ $and: [{ $gte: ['$would_recommend', 7] }, { $lt: ['$would_recommend', 9] }] }, 1, 0] }
245
- },
246
- detractors: {
247
- $sum: { $cond: [{ $lt: ['$would_recommend', 7] }, 1, 0] }
248
- }
249
- }
250
- }
251
- ]);
252
-
253
- if (analytics.length === 0) {
254
- return {
255
- total_responses: 0,
256
- avg_overall_satisfaction: 0,
257
- nps_score: 0
258
- };
259
- }
260
-
261
- const result = analytics[0];
262
- const total = result.total_responses;
263
-
264
- // Calculate NPS score: (% Promoters - % Detractors)
265
- const nps_score = total > 0
266
- ? Math.round(((result.promoters - result.detractors) / total) * 100)
267
- : 0;
268
-
269
- return {
270
- ...result,
271
- nps_score,
272
- resolution_rate: total > 0
273
- ? Math.round((result.fully_resolved_count / total) * 100)
274
- : 0
275
- };
276
- };
277
-
278
- module.exports = mongoose.model('CustomerSatisfactionSurvey', customerSatisfactionSurveySchema);
1
+ const mongoose = require('mongoose');
2
+
3
+ const customerSatisfactionSurveySchema = new mongoose.Schema({
4
+ ticket_id: {
5
+ type: mongoose.Schema.Types.ObjectId,
6
+ ref: 'CustomerTicket',
7
+ required: true,
8
+ unique: true
9
+ },
10
+ ticket_number: {
11
+ type: String,
12
+ required: true
13
+ },
14
+ customer_id: {
15
+ type: mongoose.Schema.Types.ObjectId,
16
+ ref: 'Customer',
17
+ required: true
18
+ },
19
+ facility_id: {
20
+ type: mongoose.Schema.Types.ObjectId,
21
+ ref: 'Facility',
22
+ required: true
23
+ },
24
+ assigned_agent_id: {
25
+ type: mongoose.Schema.Types.ObjectId,
26
+ ref: 'User'
27
+ },
28
+ custom_caller: {
29
+ name: {
30
+ type: String,
31
+ trim: true,
32
+ comment: 'Name of the person who reported the ticket (if different from customer)'
33
+ },
34
+ phone: {
35
+ type: String,
36
+ trim: true,
37
+ comment: 'Phone number of the custom caller'
38
+ },
39
+ email: {
40
+ type: String,
41
+ trim: true,
42
+ comment: 'Email of the custom caller'
43
+ },
44
+ relationship: {
45
+ type: String,
46
+ trim: true,
47
+ comment: 'Relationship to customer (e.g., spouse, agent, caretaker)'
48
+ }
49
+ },
50
+ survey_token: {
51
+ type: String,
52
+ required: true,
53
+ unique: true,
54
+ index: true
55
+ },
56
+ // Survey Responses
57
+ overall_satisfaction: {
58
+ type: Number,
59
+ min: 1,
60
+ max: 5,
61
+ comment: 'Overall satisfaction rating (1-5 stars)'
62
+ },
63
+ resolution_quality: {
64
+ type: Number,
65
+ min: 1,
66
+ max: 5,
67
+ comment: 'Quality of resolution (1-5 stars)'
68
+ },
69
+ response_time_satisfaction: {
70
+ type: Number,
71
+ min: 1,
72
+ max: 5,
73
+ comment: 'Satisfaction with response time (1-5 stars)'
74
+ },
75
+ agent_professionalism: {
76
+ type: Number,
77
+ min: 1,
78
+ max: 5,
79
+ comment: 'Agent professionalism rating (1-5 stars)'
80
+ },
81
+ agent_knowledge: {
82
+ type: Number,
83
+ min: 1,
84
+ max: 5,
85
+ comment: 'Agent knowledge rating (1-5 stars)'
86
+ },
87
+ communication_clarity: {
88
+ type: Number,
89
+ min: 1,
90
+ max: 5,
91
+ comment: 'Communication clarity rating (1-5 stars)'
92
+ },
93
+ // NPS (Net Promoter Score)
94
+ would_recommend: {
95
+ type: Number,
96
+ min: 0,
97
+ max: 10,
98
+ comment: 'Likelihood to recommend (0-10 scale)'
99
+ },
100
+ // Open-ended feedback
101
+ positive_feedback: {
102
+ type: String,
103
+ maxlength: 1000,
104
+ comment: 'What did we do well?'
105
+ },
106
+ improvement_feedback: {
107
+ type: String,
108
+ maxlength: 1000,
109
+ comment: 'What could we improve?'
110
+ },
111
+ additional_comments: {
112
+ type: String,
113
+ maxlength: 2000
114
+ },
115
+ // Issue resolution
116
+ issue_fully_resolved: {
117
+ type: Boolean,
118
+ comment: 'Was the issue fully resolved?'
119
+ },
120
+ issue_resolution_details: {
121
+ type: String,
122
+ maxlength: 500,
123
+ comment: 'If not fully resolved, please explain'
124
+ },
125
+ // Metadata
126
+ status: {
127
+ type: String,
128
+ enum: ['pending', 'completed', 'expired'],
129
+ default: 'pending'
130
+ },
131
+ sent_at: {
132
+ type: Date,
133
+ default: Date.now
134
+ },
135
+ completed_at: {
136
+ type: Date
137
+ },
138
+ expired_at: {
139
+ type: Date,
140
+ comment: 'Survey expires 7 days after sending'
141
+ },
142
+ reminder_sent: {
143
+ type: Boolean,
144
+ default: false
145
+ },
146
+ reminder_sent_at: {
147
+ type: Date
148
+ },
149
+ ip_address: String,
150
+ user_agent: String,
151
+ response_time_seconds: {
152
+ type: Number,
153
+ comment: 'Time taken to complete survey in seconds'
154
+ },
155
+ created_at: {
156
+ type: Date,
157
+ default: Date.now
158
+ },
159
+ updated_at: {
160
+ type: Date,
161
+ default: Date.now
162
+ }
163
+ }, {
164
+ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
165
+ });
166
+
167
+ // Indexes for performance
168
+ customerSatisfactionSurveySchema.index({ survey_token: 1 });
169
+ customerSatisfactionSurveySchema.index({ customer_id: 1, created_at: -1 });
170
+ customerSatisfactionSurveySchema.index({ facility_id: 1, status: 1 });
171
+ customerSatisfactionSurveySchema.index({ assigned_agent_id: 1, status: 1 });
172
+ customerSatisfactionSurveySchema.index({ status: 1, expired_at: 1 });
173
+ customerSatisfactionSurveySchema.index({ ticket_id: 1 });
174
+
175
+ // Virtual for calculating NPS category
176
+ customerSatisfactionSurveySchema.virtual('nps_category').get(function() {
177
+ if (this.would_recommend === null || this.would_recommend === undefined) {
178
+ return null;
179
+ }
180
+ if (this.would_recommend >= 9) return 'promoter';
181
+ if (this.would_recommend >= 7) return 'passive';
182
+ return 'detractor';
183
+ });
184
+
185
+ // Virtual for calculating average rating
186
+ customerSatisfactionSurveySchema.virtual('average_rating').get(function() {
187
+ const ratings = [
188
+ this.overall_satisfaction,
189
+ this.resolution_quality,
190
+ this.response_time_satisfaction,
191
+ this.agent_professionalism,
192
+ this.agent_knowledge,
193
+ this.communication_clarity
194
+ ].filter(r => r !== null && r !== undefined);
195
+
196
+ if (ratings.length === 0) return null;
197
+ return (ratings.reduce((sum, r) => sum + r, 0) / ratings.length).toFixed(2);
198
+ });
199
+
200
+ // Method to check if survey is expired
201
+ customerSatisfactionSurveySchema.methods.isExpired = function() {
202
+ return this.expired_at && new Date() > this.expired_at;
203
+ };
204
+
205
+ // Method to mark survey as completed
206
+ customerSatisfactionSurveySchema.methods.markCompleted = function() {
207
+ this.status = 'completed';
208
+ this.completed_at = new Date();
209
+ return this.save();
210
+ };
211
+
212
+ // Static method to get analytics
213
+ customerSatisfactionSurveySchema.statics.getAnalytics = async function(filters = {}) {
214
+ const match = { status: 'completed' };
215
+
216
+ if (filters.facility_id) match.facility_id = mongoose.Types.ObjectId(filters.facility_id);
217
+ if (filters.assigned_agent_id) match.assigned_agent_id = mongoose.Types.ObjectId(filters.assigned_agent_id);
218
+ if (filters.start_date) match.completed_at = { $gte: new Date(filters.start_date) };
219
+ if (filters.end_date) {
220
+ match.completed_at = match.completed_at || {};
221
+ match.completed_at.$lte = new Date(filters.end_date);
222
+ }
223
+
224
+ const analytics = await this.aggregate([
225
+ { $match: match },
226
+ {
227
+ $group: {
228
+ _id: null,
229
+ total_responses: { $sum: 1 },
230
+ avg_overall_satisfaction: { $avg: '$overall_satisfaction' },
231
+ avg_resolution_quality: { $avg: '$resolution_quality' },
232
+ avg_response_time_satisfaction: { $avg: '$response_time_satisfaction' },
233
+ avg_agent_professionalism: { $avg: '$agent_professionalism' },
234
+ avg_agent_knowledge: { $avg: '$agent_knowledge' },
235
+ avg_communication_clarity: { $avg: '$communication_clarity' },
236
+ avg_nps: { $avg: '$would_recommend' },
237
+ fully_resolved_count: {
238
+ $sum: { $cond: ['$issue_fully_resolved', 1, 0] }
239
+ },
240
+ promoters: {
241
+ $sum: { $cond: [{ $gte: ['$would_recommend', 9] }, 1, 0] }
242
+ },
243
+ passives: {
244
+ $sum: { $cond: [{ $and: [{ $gte: ['$would_recommend', 7] }, { $lt: ['$would_recommend', 9] }] }, 1, 0] }
245
+ },
246
+ detractors: {
247
+ $sum: { $cond: [{ $lt: ['$would_recommend', 7] }, 1, 0] }
248
+ }
249
+ }
250
+ }
251
+ ]);
252
+
253
+ if (analytics.length === 0) {
254
+ return {
255
+ total_responses: 0,
256
+ avg_overall_satisfaction: 0,
257
+ nps_score: 0
258
+ };
259
+ }
260
+
261
+ const result = analytics[0];
262
+ const total = result.total_responses;
263
+
264
+ // Calculate NPS score: (% Promoters - % Detractors)
265
+ const nps_score = total > 0
266
+ ? Math.round(((result.promoters - result.detractors) / total) * 100)
267
+ : 0;
268
+
269
+ return {
270
+ ...result,
271
+ nps_score,
272
+ resolution_rate: total > 0
273
+ ? Math.round((result.fully_resolved_count / total) * 100)
274
+ : 0
275
+ };
276
+ };
277
+
278
+ module.exports = mongoose.model('CustomerSatisfactionSurvey', customerSatisfactionSurveySchema);