payservedb 4.6.0 → 4.6.1

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
@@ -134,7 +134,8 @@ const models = {
134
134
  ServiceChargeInvoiceUpload: require('./src/models/service_charge_invoice_upload'),
135
135
  Campaign: require('./src/models/campaigns'),
136
136
  InspectionItem: require('./src/models/item_inspection'),
137
- Supplier: require('./src/models/suppliers')
137
+ Supplier: require('./src/models/suppliers'),
138
+ PurchaseRequest: require('./src/models/purchase_request')
138
139
 
139
140
  };
140
141
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payservedb",
3
- "version": "4.6.0",
3
+ "version": "4.6.1",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -0,0 +1,192 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const purchaseOrderSchema = new mongoose.Schema({
4
+ // Main Purchase Order Information
5
+ facilityId: {
6
+ type: mongoose.Schema.Types.ObjectId,
7
+ ref: 'Facility',
8
+ required: true,
9
+ index: true
10
+ },
11
+ poNumber: {
12
+ type: String,
13
+ required: true,
14
+ unique: true,
15
+ trim: true,
16
+ index: true
17
+ },
18
+ prNumber: {
19
+ type: String,
20
+ required: true,
21
+ trim: true,
22
+ index: true,
23
+ ref: 'PurchaseRequest'
24
+ },
25
+ supplier: {
26
+ type: mongoose.Schema.Types.ObjectId,
27
+ ref: 'Supplier',
28
+ required: true
29
+ },
30
+ department: {
31
+ type: String,
32
+ required: true,
33
+ trim: true
34
+ },
35
+ currency: {
36
+ type: String,
37
+ required: true,
38
+ trim: true,
39
+ default: 'KES'
40
+ },
41
+ date: {
42
+ type: Date,
43
+ required: true,
44
+ default: Date.now
45
+ },
46
+ internalNotes: {
47
+ type: String,
48
+ trim: true
49
+ },
50
+ supplierNotes: {
51
+ type: String,
52
+ trim: true
53
+ },
54
+ budget: {
55
+ type: String,
56
+ trim: true
57
+ },
58
+ status: {
59
+ type: String,
60
+ enum: ['pending approval', 'approved', 'rejected', 'sent', 'received', 'completed', 'canceled'],
61
+ default: 'pending approval',
62
+ index: true
63
+ },
64
+
65
+ // Items Details
66
+ items: [{
67
+ itemDescription: {
68
+ type: String,
69
+ required: true,
70
+ trim: true
71
+ },
72
+ quantity: {
73
+ type: Number,
74
+ required: true
75
+ },
76
+ unitOfMeasure: {
77
+ type: String,
78
+ required: true,
79
+ trim: true
80
+ },
81
+ unitPrice: {
82
+ type: Number,
83
+ required: true
84
+ },
85
+ taxRate: {
86
+ type: Number,
87
+ default: 0
88
+ },
89
+ totalPrice: {
90
+ type: Number,
91
+ required: true
92
+ }
93
+ }],
94
+
95
+ // Calculated Totals
96
+ subtotal: {
97
+ type: Number,
98
+ required: true
99
+ },
100
+ taxTotal: {
101
+ type: Number,
102
+ required: true,
103
+ default: 0
104
+ },
105
+ grandTotal: {
106
+ type: Number,
107
+ required: true
108
+ },
109
+
110
+ // Approvers Section
111
+ approvers: [{
112
+ approverId: {
113
+ type: mongoose.Schema.Types.ObjectId,
114
+ ref: 'User',
115
+ required: true
116
+ },
117
+ approverName: {
118
+ type: String,
119
+ required: true
120
+ },
121
+ approverRole: {
122
+ type: String,
123
+ required: true
124
+ },
125
+ approvalStatus: {
126
+ type: String,
127
+ enum: ['pending', 'approved', 'rejected'],
128
+ default: 'pending'
129
+ },
130
+ approvalDate: {
131
+ type: Date
132
+ },
133
+ comments: {
134
+ type: String,
135
+ trim: true
136
+ }
137
+ }],
138
+
139
+ // Document attachments
140
+ attachments: [{
141
+ name: {
142
+ type: String,
143
+ required: true
144
+ },
145
+ fileType: {
146
+ type: String,
147
+ required: true
148
+ },
149
+ filePath: {
150
+ type: String,
151
+ required: true
152
+ },
153
+ uploadDate: {
154
+ type: Date,
155
+ default: Date.now
156
+ }
157
+ }]
158
+ }, {
159
+ timestamps: true
160
+ });
161
+
162
+ // Virtual for tracking approval progress
163
+ purchaseOrderSchema.virtual('approvalProgress').get(function () {
164
+ if (!this.approvers || this.approvers.length === 0) return 0;
165
+
166
+ const approvedCount = this.approvers.filter(
167
+ approver => approver.approvalStatus === 'approved'
168
+ ).length;
169
+
170
+ return (approvedCount / this.approvers.length) * 100;
171
+ });
172
+
173
+ // Pre-save middleware to calculate totals
174
+ purchaseOrderSchema.pre('save', function (next) {
175
+ // Calculate subtotal
176
+ this.subtotal = this.items.reduce((sum, item) => sum + (item.unitPrice * item.quantity), 0);
177
+
178
+ // Calculate tax total
179
+ this.taxTotal = this.items.reduce((sum, item) => {
180
+ const itemTax = (item.unitPrice * item.quantity) * (item.taxRate / 100);
181
+ return sum + itemTax;
182
+ }, 0);
183
+
184
+ // Calculate grand total
185
+ this.grandTotal = this.subtotal + this.taxTotal;
186
+
187
+ next();
188
+ });
189
+
190
+ const PurchaseOrder = mongoose.model('PurchaseOrder', purchaseOrderSchema);
191
+
192
+ module.exports = PurchaseOrder;
@@ -0,0 +1,65 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const purchaseRequestSchema = new mongoose.Schema({
4
+ facilityId: {
5
+ type: mongoose.Schema.Types.ObjectId,
6
+ ref: 'Facility',
7
+ required: true,
8
+ index: true
9
+ },
10
+ itemDescription: {
11
+ type: String,
12
+ required: true,
13
+ trim: true
14
+ },
15
+ from: {
16
+ type: String,
17
+ ref: 'User',
18
+ trim: true
19
+ },
20
+ quantity: {
21
+ type: Number,
22
+ required: true
23
+ },
24
+ unitOfMeasure: {
25
+ type: String,
26
+ required: true,
27
+ trim: true
28
+ },
29
+ remarksSpecification: {
30
+ type: String,
31
+ trim: true
32
+ },
33
+ date: {
34
+ type: Date,
35
+ required: true,
36
+ default: Date.now
37
+ },
38
+ irfNumber: {
39
+ type: String,
40
+ required: true,
41
+ trim: true,
42
+ index: true
43
+ },
44
+ department: {
45
+ type: String,
46
+ trim: true
47
+ },
48
+ status: {
49
+ type: String,
50
+ default: 'pending',
51
+ index: true
52
+ },
53
+ poStatus: {
54
+ type: String,
55
+ enum: ['PO Raised', 'pending'],
56
+ default: 'pending',
57
+ index: true
58
+ }
59
+ }, {
60
+ timestamps: true
61
+ });
62
+
63
+ const PurchaseRequest = mongoose.model('PurchaseRequest', purchaseRequestSchema);
64
+
65
+ module.exports = PurchaseRequest;
@@ -0,0 +1,268 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const rfqSchema = new mongoose.Schema({
4
+ facilityId: {
5
+ type: mongoose.Schema.Types.ObjectId,
6
+ ref: 'Facility',
7
+ required: true,
8
+ index: true
9
+ },
10
+ name: {
11
+ type: String,
12
+ required: true,
13
+ trim: true,
14
+ index: true
15
+ },
16
+ rfqNumber: {
17
+ type: String,
18
+ required: true,
19
+ unique: true,
20
+ trim: true,
21
+ index: true
22
+ },
23
+ startDate: {
24
+ type: Date,
25
+ required: true
26
+ },
27
+ closingDate: {
28
+ type: Date,
29
+ required: true
30
+ },
31
+ currency: {
32
+ type: String,
33
+ required: true,
34
+ default: 'KES',
35
+ trim: true
36
+ },
37
+ rfqType: {
38
+ type: String,
39
+ required: true,
40
+ enum: ['open', 'closed'],
41
+ default: 'closed'
42
+ },
43
+ rfqFee: {
44
+ type: Number,
45
+ default: 0
46
+ },
47
+ rfqEvaluationType: {
48
+ type: String,
49
+ required: true,
50
+ enum: ['automatic', 'approvers'],
51
+ default: 'approvers'
52
+ },
53
+ notes: {
54
+ type: String,
55
+ trim: true
56
+ },
57
+ status: {
58
+ type: String,
59
+ enum: ['open','closed', 'awarded', 'canceled'],
60
+ default: 'open',
61
+ index: true
62
+ },
63
+ // RFQ Categories (Products or Services)
64
+ categories: [{
65
+ name: {
66
+ type: String,
67
+ required: true,
68
+ trim: true
69
+ },
70
+ // For Product type
71
+ products: [{
72
+ productName: {
73
+ type: String,
74
+ trim: true,
75
+ required: function() {
76
+ return this.parent().categoryType === 'product';
77
+ }
78
+ },
79
+ sku: {
80
+ type: String,
81
+ trim: true
82
+ },
83
+ quantity: {
84
+ type: Number,
85
+ required: function() {
86
+ return this.parent().categoryType === 'product';
87
+ }
88
+ },
89
+ unitOfMeasure: {
90
+ type: String,
91
+ trim: true
92
+ },
93
+ estimatedUnitPrice: {
94
+ type: Number
95
+ }
96
+ }],
97
+ // For Service type
98
+ services: [{
99
+ serviceDescription: {
100
+ type: String,
101
+ trim: true,
102
+ required: function() {
103
+ return this.parent().categoryType === 'service';
104
+ }
105
+ },
106
+ quantity: {
107
+ type: Number,
108
+ required: function() {
109
+ return this.parent().categoryType === 'service';
110
+ }
111
+ },
112
+ unitOfMeasure: {
113
+ type: String,
114
+ trim: true
115
+ },
116
+ estimatedUnitPrice: {
117
+ type: Number
118
+ }
119
+ }]
120
+ }],
121
+
122
+ // Suppliers invited to respond to the RFQ
123
+ suppliers: [{
124
+ supplierId: {
125
+ type: mongoose.Schema.Types.ObjectId,
126
+ ref: 'Supplier',
127
+ required: true
128
+ },
129
+ supplierName: {
130
+ type: String,
131
+ required: true,
132
+ trim: true
133
+ },
134
+ invitationStatus: {
135
+ type: String,
136
+ enum: ['pending', 'accepted', 'declined', 'no-response'],
137
+ default: 'pending'
138
+ },
139
+ invitationDate: {
140
+ type: Date,
141
+ default: Date.now
142
+ },
143
+ responseDate: {
144
+ type: Date
145
+ },
146
+ quotationSubmitted: {
147
+ type: Boolean,
148
+ default: false
149
+ },
150
+ quotationDetails: {
151
+ submissionDate: {
152
+ type: Date
153
+ },
154
+ totalAmount: {
155
+ type: Number
156
+ },
157
+ items: [{
158
+ categoryId: {
159
+ type: mongoose.Schema.Types.ObjectId,
160
+ required: true
161
+ },
162
+ itemId: {
163
+ type: mongoose.Schema.Types.ObjectId,
164
+ required: true
165
+ },
166
+ unitPrice: {
167
+ type: Number,
168
+ required: true
169
+ },
170
+ quantity: {
171
+ type: Number,
172
+ required: true
173
+ },
174
+ totalPrice: {
175
+ type: Number,
176
+ required: true
177
+ },
178
+ notes: {
179
+ type: String,
180
+ trim: true
181
+ }
182
+ }],
183
+ attachments: [{
184
+ name: {
185
+ type: String,
186
+ required: true
187
+ },
188
+ fileType: {
189
+ type: String,
190
+ required: true
191
+ },
192
+ filePath: {
193
+ type: String,
194
+ required: true
195
+ },
196
+ uploadDate: {
197
+ type: Date,
198
+ default: Date.now
199
+ }
200
+ }],
201
+ notes: {
202
+ type: String,
203
+ trim: true
204
+ }
205
+ },
206
+ evaluationScore: {
207
+ type: Number,
208
+ min: 0,
209
+ max: 100
210
+ },
211
+ evaluationNotes: {
212
+ type: String,
213
+ trim: true
214
+ }
215
+ }],
216
+
217
+
218
+ // Award details after RFQ is closed
219
+ awardDetails: {
220
+ awarded: {
221
+ type: Boolean,
222
+ default: false
223
+ },
224
+ awardedSupplierId: {
225
+ type: mongoose.Schema.Types.ObjectId,
226
+ ref: 'Supplier'
227
+ },
228
+ awardedSupplierName: {
229
+ type: String,
230
+ trim: true
231
+ },
232
+ awardDate: {
233
+ type: Date
234
+ },
235
+ awardNotes: {
236
+ type: String,
237
+ trim: true
238
+ },
239
+ purchaseOrderCreated: {
240
+ type: Boolean,
241
+ default: false
242
+ },
243
+ purchaseOrderId: {
244
+ type: mongoose.Schema.Types.ObjectId,
245
+ ref: 'PurchaseOrder'
246
+ }
247
+ }
248
+ }, {
249
+ timestamps: true
250
+ });
251
+
252
+ // Virtual for tracking participation rate
253
+ rfqSchema.virtual('participationRate').get(function() {
254
+ if (!this.suppliers || this.suppliers.length === 0) return 0;
255
+
256
+ const respondedCount = this.suppliers.filter(
257
+ supplier => supplier.quotationSubmitted === true
258
+ ).length;
259
+
260
+ return (respondedCount / this.suppliers.length) * 100;
261
+ });
262
+
263
+ // Index for efficient querying by date ranges
264
+ rfqSchema.index({ startDate: 1, closingDate: 1 });
265
+
266
+ const RFQ = mongoose.model('RFQ', rfqSchema);
267
+
268
+ module.exports = RFQ;
File without changes
File without changes
File without changes