payservedb 8.3.4 → 8.3.5

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/index.js +306 -306
  3. package/package.json +17 -17
  4. package/src/models/account.js +52 -52
  5. package/src/models/agent_departments.js +59 -59
  6. package/src/models/agent_notifications.js +53 -53
  7. package/src/models/agent_performance.js +127 -127
  8. package/src/models/agent_roles.js +77 -77
  9. package/src/models/agents.js +154 -154
  10. package/src/models/apilog.js +18 -18
  11. package/src/models/approvalsWorkflows.js +49 -49
  12. package/src/models/archivedapilog.js +18 -18
  13. package/src/models/asset.js +92 -92
  14. package/src/models/assetsAssignment.js +64 -64
  15. package/src/models/auditTrail.js +346 -346
  16. package/src/models/bankdetails.js +43 -47
  17. package/src/models/billerAddress.js +124 -124
  18. package/src/models/booking_invoice.js +151 -151
  19. package/src/models/bookinganalytics.js +63 -63
  20. package/src/models/bookingconfig.js +45 -45
  21. package/src/models/bookingproperty.js +122 -122
  22. package/src/models/bookingreservation.js +192 -192
  23. package/src/models/bookingrevenuerecord.js +84 -84
  24. package/src/models/budget.js +95 -95
  25. package/src/models/budgetCategory.js +19 -19
  26. package/src/models/campaigns.js +108 -108
  27. package/src/models/cashpayment.js +264 -264
  28. package/src/models/combinedUnits.js +62 -62
  29. package/src/models/common_area_electricity.js +38 -38
  30. package/src/models/common_area_generator.js +41 -41
  31. package/src/models/common_area_utility_alert.js +37 -37
  32. package/src/models/common_area_water.js +39 -39
  33. package/src/models/communication_status.js +33 -33
  34. package/src/models/communication_user_opt.js +32 -32
  35. package/src/models/company.js +53 -53
  36. package/src/models/coreBaseSettings.js +16 -16
  37. package/src/models/coreInvoiceSettings.js +100 -100
  38. package/src/models/counter_schema.js +21 -21
  39. package/src/models/country_tax.js +42 -42
  40. package/src/models/currency_settings.js +39 -39
  41. package/src/models/customer.js +210 -210
  42. package/src/models/customer_satisfaction_survey.js +278 -278
  43. package/src/models/customer_surveys.js +139 -139
  44. package/src/models/customer_tickets.js +239 -239
  45. package/src/models/dailyChecklist.js +312 -312
  46. package/src/models/default_payment_details.js +17 -17
  47. package/src/models/deliveryTimeMarks.js +18 -18
  48. package/src/models/document_type.js +19 -19
  49. package/src/models/dutyRosterChecklist.js +250 -250
  50. package/src/models/dutyroster.js +136 -136
  51. package/src/models/email.js +37 -37
  52. package/src/models/email_sms_queue.js +61 -61
  53. package/src/models/entry_exit.js +53 -53
  54. package/src/models/expense.js +99 -99
  55. package/src/models/expense_category.js +45 -45
  56. package/src/models/facility.js +62 -62
  57. package/src/models/facilityBillingPrices.js +29 -29
  58. package/src/models/facilityInvoice.js +240 -223
  59. package/src/models/facilityInvoicePayment.js +52 -47
  60. package/src/models/facilityInvoiceRecipient.js +32 -32
  61. package/src/models/facilityWalletTransactionsMetadata.js +236 -236
  62. package/src/models/facility_departements.js +20 -20
  63. package/src/models/facility_payment_details.js +20 -20
  64. package/src/models/facilityasset.js +25 -25
  65. package/src/models/faq.js +18 -18
  66. package/src/models/gl_account_double_entries.js +25 -25
  67. package/src/models/gl_accounts.js +56 -56
  68. package/src/models/gl_entries.js +49 -49
  69. package/src/models/goodsReceivedNotes.js +115 -115
  70. package/src/models/guard.js +47 -47
  71. package/src/models/handover.js +247 -247
  72. package/src/models/inspection_category.js +38 -38
  73. package/src/models/invoice.js +387 -387
  74. package/src/models/invoicing_schedule.js +36 -36
  75. package/src/models/item_inspection.js +96 -96
  76. package/src/models/knowledge_base.js +109 -109
  77. package/src/models/knowledge_base_rating.js +44 -44
  78. package/src/models/leaseagreement.js +243 -236
  79. package/src/models/leasetemplate.js +17 -17
  80. package/src/models/levy.js +223 -223
  81. package/src/models/levy_invoice_settings.js +26 -26
  82. package/src/models/levycontract.js +173 -173
  83. package/src/models/levytype.js +23 -23
  84. package/src/models/maintenance_service_vendor.js +38 -38
  85. package/src/models/maintenance_services.js +17 -17
  86. package/src/models/maintenancerequisition.js +31 -31
  87. package/src/models/master_workplan.js +32 -32
  88. package/src/models/master_workplan_child.js +34 -34
  89. package/src/models/message.js +38 -38
  90. package/src/models/module.js +21 -21
  91. package/src/models/notification.js +44 -44
  92. package/src/models/paymentTermsMarks.js +19 -19
  93. package/src/models/penalty.js +76 -76
  94. package/src/models/pendingCredentials.js +32 -32
  95. package/src/models/powerMeterCommunicationProtocol.js +17 -17
  96. package/src/models/powerMeterCustomerAccount.js +78 -78
  97. package/src/models/powerMeterCustomerBand.js +14 -14
  98. package/src/models/powerMeterDailyReading.js +30 -30
  99. package/src/models/powerMeterGateways.js +40 -40
  100. package/src/models/powerMeterMonthlyReading.js +34 -34
  101. package/src/models/powerMeterPowerCharges.js +85 -85
  102. package/src/models/powerMeterSettings.js +159 -159
  103. package/src/models/powerMeterSingleDayReading.js +32 -32
  104. package/src/models/powerMeters.js +116 -116
  105. package/src/models/powerMetersManufacturer.js +14 -14
  106. package/src/models/power_meter_account.js +81 -81
  107. package/src/models/power_meter_command_logs.js +30 -30
  108. package/src/models/power_meter_command_queue.js +33 -33
  109. package/src/models/power_meter_negative_balance.js +44 -44
  110. package/src/models/power_prepaid_credits.js +47 -47
  111. package/src/models/power_prepaid_debits.js +53 -53
  112. package/src/models/power_prepaid_orders.js +78 -78
  113. package/src/models/power_sms_notification.js +26 -26
  114. package/src/models/propertyManagerContract.js +556 -556
  115. package/src/models/propertyManagerRevenue.js +195 -195
  116. package/src/models/purchaseOrderInvoice.js +74 -74
  117. package/src/models/purchase_order.js +213 -213
  118. package/src/models/purchase_request.js +110 -110
  119. package/src/models/refresh_token.js +23 -23
  120. package/src/models/reminder.js +197 -197
  121. package/src/models/report.js +13 -13
  122. package/src/models/resident.js +121 -121
  123. package/src/models/rfq_details.js +131 -131
  124. package/src/models/rfq_response.js +153 -153
  125. package/src/models/service_charge_invoice_upload.js +42 -42
  126. package/src/models/service_charge_payments.js +27 -27
  127. package/src/models/servicerequest.js +55 -55
  128. package/src/models/settings.js +62 -62
  129. package/src/models/short_urls.js +21 -21
  130. package/src/models/smart_meter_daily_consumption.js +44 -44
  131. package/src/models/sms_africastalking.js +20 -20
  132. package/src/models/sms_balance_notification.js +26 -26
  133. package/src/models/sms_meliora.js +20 -20
  134. package/src/models/staff.js +36 -36
  135. package/src/models/stocksandspare.js +161 -161
  136. package/src/models/suppliers.js +74 -74
  137. package/src/models/tickets.js +173 -173
  138. package/src/models/tickets_category.js +72 -72
  139. package/src/models/unitManagementTemplate.js +44 -44
  140. package/src/models/unitasset.js +25 -25
  141. package/src/models/units.js +118 -118
  142. package/src/models/user.js +186 -186
  143. package/src/models/valueaddedservices.js +79 -79
  144. package/src/models/vas_invoices_upload.js +50 -50
  145. package/src/models/vas_payments.js +24 -24
  146. package/src/models/vasinvoice.js +192 -192
  147. package/src/models/vasvendor.js +57 -57
  148. package/src/models/visitLog.js +95 -95
  149. package/src/models/visitor.js +67 -67
  150. package/src/models/waitlist.js +45 -45
  151. package/src/models/wallet.js +44 -44
  152. package/src/models/wallet_transactions.js +50 -50
  153. package/src/models/water_invoice.js +351 -351
  154. package/src/models/water_meter_Command_Queue.js +33 -33
  155. package/src/models/water_meter_account.js +82 -82
  156. package/src/models/water_meter_billing.js +58 -58
  157. package/src/models/water_meter_communication.js +17 -17
  158. package/src/models/water_meter_communication_logs.js +39 -39
  159. package/src/models/water_meter_concentrator.js +70 -70
  160. package/src/models/water_meter_daily_history.js +32 -32
  161. package/src/models/water_meter_high_risk.js +36 -36
  162. package/src/models/water_meter_iot_cards.js +34 -34
  163. package/src/models/water_meter_manufacturer.js +35 -35
  164. package/src/models/water_meter_monthly_history.js +36 -36
  165. package/src/models/water_meter_negative_amounts.js +44 -44
  166. package/src/models/water_meter_settings.js +276 -283
  167. package/src/models/water_meter_single_day_history.js +34 -34
  168. package/src/models/water_meter_size.js +15 -15
  169. package/src/models/water_meters.js +133 -133
  170. package/src/models/water_meters_delivery.js +76 -76
  171. package/src/models/water_prepaid_credit.js +47 -47
  172. package/src/models/water_prepaid_debit.js +50 -50
  173. package/src/models/workorder.js +49 -49
  174. package/.idea/material_theme_project_new.xml +0 -12
  175. package/.idea/modules.xml +0 -8
  176. package/.idea/psdb.iml +0 -12
  177. package/.idea/vcs.xml +0 -6
@@ -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);