payservedb 8.3.9 → 8.4.0

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