payservedb 8.1.7 → 8.1.9

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
@@ -221,6 +221,15 @@ const models = {
221
221
  FacilityBillingPrice: require("./src/models/facilityBillingPrices"),
222
222
  FacilityInvoicePayment: require("./src/models/facilityInvoicePayment"),
223
223
  CommunicationUserOpt: require("./src/models/communication_user_opt"),
224
+ Agent: require("./src/models/agents"),
225
+ AgentPerformance: require("./src/models/agent_performance"),
226
+ CustomerTicket: require("./src/models/customer_tickets"),
227
+ CustomerSurvey: require("./src/models/customer_surveys"),
228
+ KnowledgeBase: require("./src/models/knowledge_base"),
229
+ KnowledgeBaseRating: require("./src/models/knowledge_base_rating"),
230
+ AgentNotification: require("./src/models/notifications"),
231
+ TicketCategory: require("./src/models/ticket_category"),
232
+ InspectionCategory: require("./src/models/inspection_category"),
224
233
  };
225
234
 
226
235
  // 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": "8.1.7",
3
+ "version": "8.1.9",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -0,0 +1,53 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const notificationSchema = new mongoose.Schema({
4
+ user_id: {
5
+ type: mongoose.Schema.Types.ObjectId,
6
+ ref: 'User',
7
+ required: true,
8
+ index: true
9
+ },
10
+ type: {
11
+ type: String,
12
+ enum: ['ticket_escalated', 'agent_tagged', 'ticket_overdue', 'ticket_assigned', 'ticket_resolved'],
13
+ required: true
14
+ },
15
+ title: {
16
+ type: String,
17
+ required: true
18
+ },
19
+ message: {
20
+ type: String,
21
+ required: true
22
+ },
23
+ ticket_id: {
24
+ type: mongoose.Schema.Types.ObjectId,
25
+ ref: 'CustomerTicket'
26
+ },
27
+ ticket_number: {
28
+ type: String
29
+ },
30
+ link: {
31
+ type: String,
32
+ required: true
33
+ },
34
+ is_read: {
35
+ type: Boolean,
36
+ default: false
37
+ },
38
+ created_at: {
39
+ type: Date,
40
+ default: Date.now,
41
+ index: true
42
+ },
43
+ read_at: {
44
+ type: Date
45
+ },
46
+ metadata: {
47
+ type: mongoose.Schema.Types.Mixed
48
+ }
49
+ });
50
+
51
+ notificationSchema.index({ user_id: 1, is_read: 1, created_at: -1 });
52
+
53
+ module.exports = mongoose.models.AgentNotification || mongoose.model('AgentNotification', notificationSchema);
@@ -0,0 +1,127 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const agentPerformanceSchema = new mongoose.Schema({
4
+ agent_id: {
5
+ type: mongoose.Schema.Types.ObjectId,
6
+ ref: 'Agent',
7
+ required: true
8
+ },
9
+ period_start: {
10
+ type: Date,
11
+ required: true
12
+ },
13
+ period_end: {
14
+ type: Date,
15
+ required: true
16
+ },
17
+ period_type: {
18
+ type: String,
19
+ enum: ['daily', 'weekly', 'monthly', 'quarterly', 'yearly'],
20
+ default: 'daily'
21
+ },
22
+ tickets_handled: {
23
+ type: Number,
24
+ default: 0,
25
+ min: 0
26
+ },
27
+ tickets_resolved: {
28
+ type: Number,
29
+ default: 0,
30
+ min: 0
31
+ },
32
+ tickets_closed: {
33
+ type: Number,
34
+ default: 0,
35
+ min: 0
36
+ },
37
+ avg_response_time: {
38
+ type: Number,
39
+ default: 0,
40
+ min: 0,
41
+ comment: 'Average response time in minutes'
42
+ },
43
+ avg_resolution_time: {
44
+ type: Number,
45
+ default: 0,
46
+ min: 0,
47
+ comment: 'Average resolution time in minutes'
48
+ },
49
+ avg_first_response_time: {
50
+ type: Number,
51
+ default: 0,
52
+ min: 0,
53
+ comment: 'Average first response time in minutes'
54
+ },
55
+ sla_compliance_rate: {
56
+ type: Number,
57
+ default: 0,
58
+ min: 0,
59
+ max: 100,
60
+ comment: 'SLA compliance percentage'
61
+ },
62
+ sla_breaches: {
63
+ type: Number,
64
+ default: 0,
65
+ min: 0
66
+ },
67
+ average_rating: {
68
+ type: Number,
69
+ default: 0,
70
+ min: 0,
71
+ max: 5
72
+ },
73
+ total_ratings: {
74
+ type: Number,
75
+ default: 0,
76
+ min: 0
77
+ },
78
+ positive_ratings: {
79
+ type: Number,
80
+ default: 0,
81
+ min: 0
82
+ },
83
+ negative_ratings: {
84
+ type: Number,
85
+ default: 0,
86
+ min: 0
87
+ },
88
+ customer_satisfaction_score: {
89
+ type: Number,
90
+ default: 0,
91
+ min: 0,
92
+ max: 100
93
+ },
94
+ total_handle_time: {
95
+ type: Number,
96
+ default: 0,
97
+ min: 0,
98
+ comment: 'Total handle time in minutes'
99
+ },
100
+ active_time: {
101
+ type: Number,
102
+ default: 0,
103
+ min: 0,
104
+ comment: 'Total active time in minutes'
105
+ },
106
+ idle_time: {
107
+ type: Number,
108
+ default: 0,
109
+ min: 0,
110
+ comment: 'Total idle time in minutes'
111
+ },
112
+ created_at: {
113
+ type: Date,
114
+ default: Date.now
115
+ },
116
+ updated_at: {
117
+ type: Date,
118
+ default: Date.now
119
+ }
120
+ }, {
121
+ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
122
+ });
123
+
124
+ agentPerformanceSchema.index({ agent_id: 1, period_start: 1, period_end: 1 });
125
+ agentPerformanceSchema.index({ period_type: 1, period_start: -1 });
126
+
127
+ module.exports = mongoose.model('AgentPerformance', agentPerformanceSchema);
@@ -0,0 +1,109 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ // For multitenancy, agents should be on the main database
4
+ // Get or create main database connection
5
+ const getMainConnection = () => {
6
+ // If there's already a main connection, use it
7
+ if (mongoose.connections.length > 0 && mongoose.connections[0].name) {
8
+ return mongoose.connections[0];
9
+ }
10
+ // Otherwise use the default connection
11
+ return mongoose.connection;
12
+ };
13
+
14
+ const agentSchema = new mongoose.Schema({
15
+ user_id: {
16
+ type: mongoose.Schema.Types.ObjectId,
17
+ ref: 'User',
18
+ required: true
19
+ },
20
+ agent_id: {
21
+ type: String,
22
+ required: true,
23
+ unique: true,
24
+ trim: true
25
+ },
26
+ name: {
27
+ type: String,
28
+ trim: true
29
+ },
30
+ email: {
31
+ type: String,
32
+ trim: true
33
+ },
34
+ phone: {
35
+ type: String,
36
+ trim: true
37
+ },
38
+ id_number: {
39
+ type: String,
40
+ trim: true
41
+ },
42
+ role: {
43
+ type: String,
44
+ enum: ['call_center_agent', 'agent', 'team_leader', 'team_lead', 'technician', 'supervisor', 'manager'],
45
+ default: 'call_center_agent'
46
+ },
47
+ department: {
48
+ type: String,
49
+ trim: true
50
+ },
51
+ team_id: {
52
+ type: String,
53
+ trim: true
54
+ },
55
+ facility_id: {
56
+ type: mongoose.Schema.Types.ObjectId,
57
+ ref: 'Facility'
58
+ },
59
+ status: {
60
+ type: String,
61
+ enum: ['active', 'inactive', 'on_break', 'offline'],
62
+ default: 'active'
63
+ },
64
+ is_available: {
65
+ type: Boolean,
66
+ default: false
67
+ },
68
+ skills: [{
69
+ type: String,
70
+ trim: true
71
+ }],
72
+ languages: [{
73
+ type: String,
74
+ trim: true
75
+ }],
76
+ max_concurrent_tickets: {
77
+ type: Number,
78
+ default: 5,
79
+ min: 1
80
+ },
81
+ current_ticket_count: {
82
+ type: Number,
83
+ default: 0,
84
+ min: 0
85
+ },
86
+ created_at: {
87
+ type: Date,
88
+ default: Date.now
89
+ },
90
+ updated_at: {
91
+ type: Date,
92
+ default: Date.now
93
+ }
94
+ }, {
95
+ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
96
+ });
97
+
98
+ // Static method to generate agent ID
99
+ agentSchema.statics.generateAgentId = function() {
100
+ const randomNum = Math.floor(100000 + Math.random() * 900000); // 6 digit number
101
+ return `PS25${randomNum}`;
102
+ };
103
+
104
+ agentSchema.index({ user_id: 1 });
105
+ agentSchema.index({ agent_id: 1 });
106
+ agentSchema.index({ status: 1, is_available: 1 });
107
+ agentSchema.index({ team_id: 1 });
108
+
109
+ module.exports = mongoose.model('Agent', agentSchema);
@@ -0,0 +1,139 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const customerSurveySchema = new mongoose.Schema({
4
+ survey_id: {
5
+ type: String,
6
+ required: true,
7
+ unique: true,
8
+ trim: true
9
+ },
10
+ facility_id: {
11
+ type: mongoose.Schema.Types.ObjectId,
12
+ ref: 'Facility',
13
+ required: true
14
+ },
15
+ customer_id: {
16
+ type: mongoose.Schema.Types.ObjectId,
17
+ ref: 'Customer',
18
+ required: true
19
+ },
20
+ ticket_id: {
21
+ type: mongoose.Schema.Types.ObjectId,
22
+ ref: 'CustomerTicket'
23
+ },
24
+ agent_id: {
25
+ type: mongoose.Schema.Types.ObjectId,
26
+ ref: 'Agent'
27
+ },
28
+ survey_type: {
29
+ type: String,
30
+ enum: ['post_ticket', 'periodic', 'nps', 'csat', 'ces'],
31
+ default: 'post_ticket'
32
+ },
33
+ status: {
34
+ type: String,
35
+ enum: ['pending', 'completed', 'expired'],
36
+ default: 'pending'
37
+ },
38
+ overall_satisfaction: {
39
+ type: Number,
40
+ min: 1,
41
+ max: 5
42
+ },
43
+ agent_performance_rating: {
44
+ type: Number,
45
+ min: 1,
46
+ max: 5
47
+ },
48
+ response_time_rating: {
49
+ type: Number,
50
+ min: 1,
51
+ max: 5
52
+ },
53
+ resolution_quality_rating: {
54
+ type: Number,
55
+ min: 1,
56
+ max: 5
57
+ },
58
+ communication_rating: {
59
+ type: Number,
60
+ min: 1,
61
+ max: 5
62
+ },
63
+ nps_score: {
64
+ type: Number,
65
+ min: 0,
66
+ max: 10,
67
+ comment: 'Net Promoter Score - How likely to recommend (0-10)'
68
+ },
69
+ csat_score: {
70
+ type: Number,
71
+ min: 1,
72
+ max: 5,
73
+ comment: 'Customer Satisfaction Score'
74
+ },
75
+ ces_score: {
76
+ type: Number,
77
+ min: 1,
78
+ max: 7,
79
+ comment: 'Customer Effort Score - How easy was it (1-7)'
80
+ },
81
+ comments: {
82
+ type: String,
83
+ trim: true
84
+ },
85
+ feedback_text: {
86
+ type: String,
87
+ trim: true
88
+ },
89
+ improvement_suggestions: {
90
+ type: String,
91
+ trim: true
92
+ },
93
+ questions: [{
94
+ question_id: String,
95
+ question_text: String,
96
+ answer_type: {
97
+ type: String,
98
+ enum: ['rating', 'text', 'multiple_choice', 'yes_no']
99
+ },
100
+ answer: mongoose.Schema.Types.Mixed
101
+ }],
102
+ sent_at: {
103
+ type: Date,
104
+ default: Date.now
105
+ },
106
+ completed_at: {
107
+ type: Date
108
+ },
109
+ expires_at: {
110
+ type: Date
111
+ },
112
+ reminder_sent: {
113
+ type: Boolean,
114
+ default: false
115
+ },
116
+ reminder_sent_at: {
117
+ type: Date
118
+ },
119
+ created_at: {
120
+ type: Date,
121
+ default: Date.now
122
+ },
123
+ updated_at: {
124
+ type: Date,
125
+ default: Date.now
126
+ }
127
+ }, {
128
+ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
129
+ });
130
+
131
+ customerSurveySchema.index({ survey_id: 1 });
132
+ customerSurveySchema.index({ facility_id: 1, status: 1 });
133
+ customerSurveySchema.index({ customer_id: 1, created_at: -1 });
134
+ customerSurveySchema.index({ ticket_id: 1 });
135
+ customerSurveySchema.index({ agent_id: 1, completed_at: -1 });
136
+ customerSurveySchema.index({ survey_type: 1, status: 1 });
137
+ customerSurveySchema.index({ expires_at: 1 });
138
+
139
+ module.exports = mongoose.model('CustomerSurvey', customerSurveySchema);
@@ -0,0 +1,154 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const customerTicketSchema = new mongoose.Schema({
4
+ ticket_number: {
5
+ type: String,
6
+ required: true,
7
+ unique: true,
8
+ trim: true
9
+ },
10
+ facility_id: {
11
+ type: mongoose.Schema.Types.ObjectId,
12
+ ref: 'Facility',
13
+ required: true
14
+ },
15
+ customer_id: {
16
+ type: mongoose.Schema.Types.ObjectId,
17
+ ref: 'Customer',
18
+ required: true
19
+ },
20
+ unit_id: {
21
+ type: mongoose.Schema.Types.ObjectId,
22
+ ref: 'Unit'
23
+ },
24
+ assigned_agent_id: {
25
+ type: mongoose.Schema.Types.ObjectId,
26
+ ref: 'User'
27
+ },
28
+ created_by_agent_id: {
29
+ type: mongoose.Schema.Types.ObjectId,
30
+ ref: 'User',
31
+ required: true
32
+ },
33
+ title: {
34
+ type: String,
35
+ required: true,
36
+ trim: true,
37
+ maxlength: 255
38
+ },
39
+ description: {
40
+ type: String,
41
+ required: true
42
+ },
43
+ status: {
44
+ type: String,
45
+ enum: ['open', 'in_progress', 'pending_customer', 'escalated', 'resolved', 'closed', 'reopened'],
46
+ default: 'open'
47
+ },
48
+ category_id: {
49
+ type: mongoose.Schema.Types.ObjectId,
50
+ ref: 'TicketCategory',
51
+ required: true
52
+ },
53
+ source: {
54
+ type: String,
55
+ enum: ['portal', 'email', 'phone', 'chat', 'in_person', 'mobile_app'],
56
+ default: 'portal'
57
+ },
58
+ sla_status: {
59
+ type: String,
60
+ enum: ['within_sla', 'approaching_breach', 'breached'],
61
+ default: 'within_sla'
62
+ },
63
+ sla_due_date: {
64
+ type: Date
65
+ },
66
+ first_response_time: {
67
+ type: Number,
68
+ comment: 'Time to first response in minutes'
69
+ },
70
+ resolution_time: {
71
+ type: Number,
72
+ comment: 'Time to resolution in minutes'
73
+ },
74
+ customer_rating: {
75
+ type: Number,
76
+ min: 1,
77
+ max: 5
78
+ },
79
+ customer_feedback: {
80
+ type: String,
81
+ trim: true
82
+ },
83
+ tags: [{
84
+ type: String,
85
+ trim: true
86
+ }],
87
+ attachments: [{
88
+ filename: String,
89
+ file_path: String,
90
+ file_size: Number,
91
+ mime_type: String,
92
+ uploaded_at: {
93
+ type: Date,
94
+ default: Date.now
95
+ }
96
+ }],
97
+ interactions: [{
98
+ agent_id: {
99
+ type: mongoose.Schema.Types.ObjectId,
100
+ ref: 'User'
101
+ },
102
+ message: String,
103
+ is_internal_note: {
104
+ type: Boolean,
105
+ default: false
106
+ },
107
+ tagged_agents: [{
108
+ type: mongoose.Schema.Types.ObjectId,
109
+ ref: 'User'
110
+ }],
111
+ created_at: {
112
+ type: Date,
113
+ default: Date.now
114
+ }
115
+ }],
116
+ reopened_count: {
117
+ type: Number,
118
+ default: 0,
119
+ min: 0
120
+ },
121
+ last_reopened_at: {
122
+ type: Date
123
+ },
124
+ resolved_at: {
125
+ type: Date
126
+ },
127
+ resolved_by: {
128
+ type: mongoose.Schema.Types.ObjectId,
129
+ ref: 'User'
130
+ },
131
+ closed_at: {
132
+ type: Date
133
+ },
134
+ created_at: {
135
+ type: Date,
136
+ default: Date.now
137
+ },
138
+ updated_at: {
139
+ type: Date,
140
+ default: Date.now
141
+ }
142
+ }, {
143
+ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
144
+ });
145
+
146
+ customerTicketSchema.index({ ticket_number: 1 });
147
+ customerTicketSchema.index({ facility_id: 1, status: 1 });
148
+ customerTicketSchema.index({ customer_id: 1, created_at: -1 });
149
+ customerTicketSchema.index({ assigned_agent_id: 1, status: 1 });
150
+ customerTicketSchema.index({ status: 1, sla_status: 1 });
151
+ customerTicketSchema.index({ category_id: 1, created_at: -1 });
152
+ customerTicketSchema.index({ sla_status: 1, sla_due_date: 1 });
153
+
154
+ module.exports = mongoose.model('CustomerTicket', customerTicketSchema);
@@ -165,7 +165,8 @@ const handoverSchema = new mongoose.Schema({
165
165
 
166
166
  // Any attached documents
167
167
  attachments: [{
168
- name: String,
168
+ name: String, // Custom descriptive name (e.g., "Chair Image 23")
169
+ fileName: String, // Original file name
169
170
  fileUrl: String,
170
171
  uploadDate: {
171
172
  type: Date,
@@ -0,0 +1,38 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ // Define the schema for inspection categories
4
+ const inspectionCategorySchema = new mongoose.Schema({
5
+ name: {
6
+ type: String,
7
+ required: [true, 'Category name is required'],
8
+ trim: true,
9
+ unique: false // Unique per facility, not globally
10
+ },
11
+ description: {
12
+ type: String,
13
+ trim: true,
14
+ default: ''
15
+ },
16
+ active: {
17
+ type: Boolean,
18
+ default: true
19
+ },
20
+ facilityId: {
21
+ type: mongoose.Schema.Types.ObjectId,
22
+ ref: 'Facility',
23
+ required: true
24
+ }
25
+ }, {
26
+ timestamps: true
27
+ });
28
+
29
+ // Add compound index to ensure unique category names per facility
30
+ inspectionCategorySchema.index({ facilityId: 1, name: 1 }, { unique: true });
31
+
32
+ // Add index for common queries
33
+ inspectionCategorySchema.index({ facilityId: 1 });
34
+
35
+ // Create InspectionCategory model
36
+ const InspectionCategory = mongoose.model('InspectionCategory', inspectionCategorySchema);
37
+
38
+ module.exports = InspectionCategory;
@@ -0,0 +1,109 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const knowledgeBaseSchema = new mongoose.Schema({
4
+ title: {
5
+ type: String,
6
+ required: true,
7
+ trim: true,
8
+ maxlength: 255
9
+ },
10
+ content: {
11
+ type: String,
12
+ required: true
13
+ },
14
+ summary: {
15
+ type: String,
16
+ trim: true,
17
+ maxlength: 500
18
+ },
19
+ category_id: {
20
+ type: mongoose.Schema.Types.ObjectId,
21
+ ref: 'TicketCategory',
22
+ required: true
23
+ },
24
+ tags: [{
25
+ type: String,
26
+ trim: true
27
+ }],
28
+ status: {
29
+ type: String,
30
+ enum: ['draft', 'published', 'archived'],
31
+ default: 'draft'
32
+ },
33
+ visibility: {
34
+ type: String,
35
+ enum: ['internal', 'public'],
36
+ default: 'internal'
37
+ },
38
+ is_featured: {
39
+ type: Boolean,
40
+ default: false
41
+ },
42
+ author_id: {
43
+ type: mongoose.Schema.Types.ObjectId,
44
+ ref: 'User',
45
+ required: true
46
+ },
47
+ created_by: {
48
+ type: mongoose.Schema.Types.ObjectId,
49
+ ref: 'User',
50
+ required: true
51
+ },
52
+ updated_by: {
53
+ type: mongoose.Schema.Types.ObjectId,
54
+ ref: 'User'
55
+ },
56
+ views_count: {
57
+ type: Number,
58
+ default: 0,
59
+ min: 0
60
+ },
61
+ helpful_count: {
62
+ type: Number,
63
+ default: 0,
64
+ min: 0
65
+ },
66
+ not_helpful_count: {
67
+ type: Number,
68
+ default: 0,
69
+ min: 0
70
+ },
71
+ attachments: [{
72
+ filename: String,
73
+ file_path: String,
74
+ file_size: Number,
75
+ mime_type: String,
76
+ uploaded_at: {
77
+ type: Date,
78
+ default: Date.now
79
+ }
80
+ }],
81
+ related_articles: [{
82
+ type: mongoose.Schema.Types.ObjectId,
83
+ ref: 'KnowledgeBase'
84
+ }],
85
+ created_at: {
86
+ type: Date,
87
+ default: Date.now
88
+ },
89
+ updated_at: {
90
+ type: Date,
91
+ default: Date.now
92
+ },
93
+ published_at: {
94
+ type: Date
95
+ },
96
+ archived_at: {
97
+ type: Date
98
+ }
99
+ }, {
100
+ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
101
+ });
102
+
103
+ knowledgeBaseSchema.index({ title: 'text', content: 'text', summary: 'text', tags: 'text' });
104
+ knowledgeBaseSchema.index({ category_id: 1, status: 1 });
105
+ knowledgeBaseSchema.index({ status: 1, is_featured: 1 });
106
+ knowledgeBaseSchema.index({ author_id: 1, created_at: -1 });
107
+ knowledgeBaseSchema.index({ views_count: -1 });
108
+
109
+ module.exports = mongoose.model('KnowledgeBase', knowledgeBaseSchema);
@@ -0,0 +1,44 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const knowledgeBaseRatingSchema = new mongoose.Schema({
4
+ article_id: {
5
+ type: mongoose.Schema.Types.ObjectId,
6
+ ref: 'KnowledgeBase',
7
+ required: true
8
+ },
9
+ agent_id: {
10
+ type: mongoose.Schema.Types.ObjectId,
11
+ ref: 'User',
12
+ required: true
13
+ },
14
+ helpful: {
15
+ type: Boolean,
16
+ required: true
17
+ },
18
+ feedback: {
19
+ type: String,
20
+ trim: true,
21
+ maxlength: 1000
22
+ },
23
+ ticket_id: {
24
+ type: mongoose.Schema.Types.ObjectId,
25
+ ref: 'CustomerTicket',
26
+ comment: 'Optional reference to ticket where this article was used'
27
+ },
28
+ created_at: {
29
+ type: Date,
30
+ default: Date.now
31
+ },
32
+ updated_at: {
33
+ type: Date,
34
+ default: Date.now
35
+ }
36
+ }, {
37
+ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
38
+ });
39
+
40
+ knowledgeBaseRatingSchema.index({ article_id: 1, agent_id: 1 }, { unique: true });
41
+ knowledgeBaseRatingSchema.index({ article_id: 1, helpful: 1 });
42
+ knowledgeBaseRatingSchema.index({ agent_id: 1, created_at: -1 });
43
+
44
+ module.exports = mongoose.model('KnowledgeBaseRating', knowledgeBaseRatingSchema);
@@ -28,6 +28,23 @@ const levySchema = new mongoose.Schema(
28
28
  required: true,
29
29
  trim: true,
30
30
  },
31
+ taxEnabled: {
32
+ type: Boolean,
33
+ default: false,
34
+ required: false
35
+ },
36
+ taxRate: {
37
+ type: Number,
38
+ default: 0,
39
+ required: false,
40
+ min: [0, 'Tax rate must be a positive number'],
41
+ max: [100, 'Tax rate cannot exceed 100']
42
+ },
43
+ taxType: {
44
+ type: String,
45
+ enum: ['VAT', 'Withholding', 'ServiceTax', 'Custom'],
46
+ default: 'VAT'
47
+ },
31
48
  collectionFrequency: {
32
49
  type: String,
33
50
  required: true,
@@ -0,0 +1,61 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const ticketCategorySchema = new mongoose.Schema({
4
+ name: {
5
+ type: String,
6
+ required: true,
7
+ trim: true,
8
+ unique: true,
9
+ maxlength: 100
10
+ },
11
+ description: {
12
+ type: String,
13
+ trim: true,
14
+ maxlength: 500
15
+ },
16
+ priority: {
17
+ type: String,
18
+ required: true,
19
+ enum: ['low', 'medium', 'high', 'urgent', 'critical'],
20
+ default: 'medium'
21
+ },
22
+ sla_hours: {
23
+ type: Number,
24
+ required: true,
25
+ min: 1
26
+ },
27
+ color: {
28
+ type: String,
29
+ default: '#3b82f6',
30
+ trim: true
31
+ },
32
+ is_active: {
33
+ type: Boolean,
34
+ default: true
35
+ },
36
+ created_by: {
37
+ type: mongoose.Schema.Types.ObjectId,
38
+ ref: 'User',
39
+ required: true
40
+ },
41
+ updated_by: {
42
+ type: mongoose.Schema.Types.ObjectId,
43
+ ref: 'User'
44
+ },
45
+ created_at: {
46
+ type: Date,
47
+ default: Date.now
48
+ },
49
+ updated_at: {
50
+ type: Date,
51
+ default: Date.now
52
+ }
53
+ }, {
54
+ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
55
+ });
56
+
57
+ ticketCategorySchema.index({ name: 1 });
58
+ ticketCategorySchema.index({ priority: 1 });
59
+ ticketCategorySchema.index({ is_active: 1 });
60
+
61
+ module.exports = mongoose.model('TicketCategory', ticketCategorySchema);
@@ -25,7 +25,7 @@ const userSchema = new mongoose.Schema({
25
25
  type: {
26
26
  type: String,
27
27
  required: [true, 'Type is required'],
28
- enum: ['Company', 'Project Manager', 'Universal', 'Core', 'Resident', 'Landlord', 'Supplier'],
28
+ enum: ['Company', 'Project Manager', 'Universal', 'Core', 'Resident', 'Landlord', 'Supplier', 'Customer_Support'],
29
29
  },
30
30
  department: {
31
31
  type: mongoose.Schema.Types.ObjectId,