Skip to content

Commit 5aaab32

Browse files
Merge pull request #2034 from OneCommunityGlobal/chaitanya-purchase-req-form-enhancement-be
Chaitanya - Purchase request form enhancement
2 parents fcee570 + 2c0093c commit 5aaab32

3 files changed

Lines changed: 131 additions & 30 deletions

File tree

src/controllers/bmdashboard/__tests__/bmMaterialsController.test.js

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ describe('bmMaterialsController', () => {
9393
});
9494

9595
describe('bmPurchaseMaterials', () => {
96+
const validProjectId = '507f1f77bcf86cd799439011';
97+
const validMatTypeId = '507f1f77bcf86cd799439012';
98+
const validRequestorId = '507f1f77bcf86cd799439013';
99+
96100
it('should create a new material if not found', async () => {
97101
mockFindOne.mockResolvedValue(null);
98102
mockCreate.mockImplementation(() => ({
@@ -104,35 +108,43 @@ describe('bmMaterialsController', () => {
104108

105109
const req = {
106110
body: {
107-
primaryId: 'project123',
108-
secondaryId: 'matType123',
111+
primaryId: validProjectId,
112+
secondaryId: validMatTypeId,
109113
quantity: 50,
110-
priority: 'high',
114+
priority: 'Low',
111115
brand: 'BrandX',
112-
requestor: { requestorId: 'user123' },
116+
requestor: { requestorId: validRequestorId },
113117
},
114118
};
115119
const res = {
116120
status: jest.fn().mockReturnThis(),
117121
send: jest.fn(),
122+
json: jest.fn().mockReturnThis(),
118123
};
119124

120125
await controller.bmPurchaseMaterials(req, res);
121126

122127
expect(mockFindOne).toHaveBeenCalledWith({
123-
project: 'project123',
124-
itemType: 'matType123',
128+
project: validProjectId,
129+
itemType: validMatTypeId,
125130
});
126131
expect(mockCreate).toHaveBeenCalled();
127132
expect(res.status).toHaveBeenCalledWith(201);
128133
expect(res.send).toHaveBeenCalled();
129134
});
130135

131136
it('should update an existing material if found', async () => {
132-
const mockMaterial = { _id: 'material123' };
137+
const mockMaterial = {
138+
_id: '507f1f77bcf86cd799439014',
139+
stockBought: 100,
140+
};
133141
mockFindOne.mockResolvedValue(mockMaterial);
134142

135-
mongoose.Types.ObjectId = jest.fn().mockReturnValue('material123');
143+
// Mock ObjectId.isValid to return true, and ObjectId constructor
144+
mongoose.Types.ObjectId.isValid = jest.fn().mockReturnValue(true);
145+
const originalObjectId = mongoose.Types.ObjectId;
146+
mongoose.Types.ObjectId = jest.fn().mockReturnValue('507f1f77bcf86cd799439014');
147+
mongoose.Types.ObjectId.isValid = originalObjectId.isValid;
136148

137149
mockFindOneAndUpdate.mockReturnValue({
138150
exec: jest.fn().mockReturnValue({
@@ -145,24 +157,25 @@ describe('bmMaterialsController', () => {
145157

146158
const req = {
147159
body: {
148-
primaryId: 'project123',
149-
secondaryId: 'matType123',
160+
primaryId: validProjectId,
161+
secondaryId: validMatTypeId,
150162
quantity: 50,
151-
priority: 'high',
163+
priority: 'Low',
152164
brand: 'BrandX',
153-
requestor: { requestorId: 'user123' },
165+
requestor: { requestorId: validRequestorId },
154166
},
155167
};
156168
const res = {
157169
status: jest.fn().mockReturnThis(),
158170
send: jest.fn(),
171+
json: jest.fn().mockReturnThis(),
159172
};
160173

161174
await controller.bmPurchaseMaterials(req, res);
162175

163176
expect(mockFindOne).toHaveBeenCalledWith({
164-
project: 'project123',
165-
itemType: 'matType123',
177+
project: validProjectId,
178+
itemType: validMatTypeId,
166179
});
167180
expect(mockFindOneAndUpdate).toHaveBeenCalled();
168181
expect(res.status).toHaveBeenCalledWith(201);
@@ -174,17 +187,18 @@ describe('bmMaterialsController', () => {
174187

175188
const req = {
176189
body: {
177-
primaryId: 'project123',
178-
secondaryId: 'matType123',
190+
primaryId: validProjectId,
191+
secondaryId: validMatTypeId,
179192
quantity: 50,
180-
priority: 'high',
193+
priority: 'Low',
181194
brand: 'BrandX',
182-
requestor: { requestorId: 'user123' },
195+
requestor: { requestorId: validRequestorId },
183196
},
184197
};
185198
const res = {
186199
status: jest.fn().mockReturnThis(),
187200
send: jest.fn(),
201+
json: jest.fn().mockReturnThis(),
188202
};
189203

190204
await controller.bmPurchaseMaterials(req, res);

src/controllers/bmdashboard/bmMaterialsController.js

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,93 @@ const bmMaterialsController = function (BuildingMaterial) {
4343
quantity,
4444
priority,
4545
brand: brandPref,
46-
requestor: { requestorId },
46+
requestor: { requestorId } = {},
4747
} = req.body;
4848

4949
try {
50+
// Validation: Check required fields
51+
if (!projectId) {
52+
return res.status(400).json({
53+
message: 'Project is required',
54+
field: 'projectId',
55+
});
56+
}
57+
58+
if (!matTypeId) {
59+
return res.status(400).json({
60+
message: 'Material is required',
61+
field: 'matTypeId',
62+
});
63+
}
64+
65+
if (!quantity && quantity !== 0) {
66+
return res.status(400).json({
67+
message: 'Quantity is required',
68+
field: 'quantity',
69+
});
70+
}
71+
72+
if (!priority) {
73+
return res.status(400).json({
74+
message: 'Priority is required',
75+
field: 'priority',
76+
});
77+
}
78+
79+
if (!requestorId) {
80+
return res.status(400).json({
81+
message: 'Requestor information is required',
82+
field: 'requestorId',
83+
});
84+
}
85+
86+
// Validation: Validate ObjectIds
87+
if (!mongoose.Types.ObjectId.isValid(projectId)) {
88+
return res.status(400).json({
89+
message: 'Invalid project ID format',
90+
field: 'projectId',
91+
});
92+
}
93+
94+
if (!mongoose.Types.ObjectId.isValid(matTypeId)) {
95+
return res.status(400).json({
96+
message: 'Invalid material ID format',
97+
field: 'matTypeId',
98+
});
99+
}
100+
101+
if (!mongoose.Types.ObjectId.isValid(requestorId)) {
102+
return res.status(400).json({
103+
message: 'Invalid requestor ID format',
104+
field: 'requestorId',
105+
});
106+
}
107+
108+
// Validation: Validate quantity
109+
const quantityNum = Number(quantity);
110+
if (Number.isNaN(quantityNum)) {
111+
return res.status(400).json({
112+
message: 'Quantity must be a valid number',
113+
field: 'quantity',
114+
});
115+
}
116+
117+
if (quantityNum <= 0) {
118+
return res.status(400).json({
119+
message: 'Quantity must be greater than 0',
120+
field: 'quantity',
121+
});
122+
}
123+
124+
// Validation: Validate priority
125+
const validPriorities = ['Low', 'Medium', 'High'];
126+
if (!validPriorities.includes(priority)) {
127+
return res.status(400).json({
128+
message: 'Priority must be one of: Low, Medium, High',
129+
field: 'priority',
130+
});
131+
}
132+
50133
// check if requestor has permission to make purchase request
51134
//! Note: this code is disabled until permissions are added
52135
// TODO: uncomment this code to execute auth check
@@ -60,7 +143,7 @@ const bmMaterialsController = function (BuildingMaterial) {
60143
// if no, add a new document to the collection
61144
// if yes, update the existing document
62145
const newPurchaseRecord = {
63-
quantity,
146+
quantity: quantityNum,
64147
priority,
65148
brandPref,
66149
requestedBy: requestorId,
@@ -74,14 +157,14 @@ const bmMaterialsController = function (BuildingMaterial) {
74157
itemType: matTypeId,
75158
project: projectId,
76159
purchaseRecord: [newPurchaseRecord],
77-
stockBought: quantity,
160+
stockBought: quantityNum,
78161
};
79162
BuildingMaterial.create(newDoc)
80163
.then(() => res.status(201).send())
81164
.catch((error) => res.status(500).send(error));
82165
return;
83166
}
84-
doc.stockBought += quantity;
167+
doc.stockBought += quantityNum;
85168
BuildingMaterial.findOneAndUpdate(
86169
{ _id: mongoose.Types.ObjectId(doc._id) },
87170
{ $push: { purchaseRecord: newPurchaseRecord } },

src/controllers/bmdashboard/bmMaterialsController.test.js

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,13 +113,17 @@ describe('bmMaterialsController', () => {
113113

114114
describe('bmPurchaseMaterials', () => {
115115
it('should create new material purchase record when material does not exist', async () => {
116+
const validProjectId = '507f1f77bcf86cd799439011';
117+
const validMaterialTypeId = '507f1f77bcf86cd799439012';
118+
const validRequestorId = '507f1f77bcf86cd799439013';
119+
116120
const purchaseData = {
117-
primaryId: 'project123',
118-
secondaryId: 'materialType456',
121+
primaryId: validProjectId,
122+
secondaryId: validMaterialTypeId,
119123
quantity: 100,
120124
priority: 'High',
121125
brand: 'Premium Brand',
122-
requestor: { requestorId: 'user789' },
126+
requestor: { requestorId: validRequestorId },
123127
};
124128

125129
req.body = purchaseData;
@@ -134,18 +138,18 @@ describe('bmMaterialsController', () => {
134138
await controller.bmPurchaseMaterials(req, res);
135139

136140
expect(BuildingMaterialMock.findOne).toHaveBeenCalledWith({
137-
project: 'project123',
138-
itemType: 'materialType456',
141+
project: validProjectId,
142+
itemType: validMaterialTypeId,
139143
});
140144
expect(BuildingMaterialMock.create).toHaveBeenCalledWith({
141-
itemType: 'materialType456',
142-
project: 'project123',
145+
itemType: validMaterialTypeId,
146+
project: validProjectId,
143147
purchaseRecord: [
144148
{
145149
quantity: 100,
146150
priority: 'High',
147151
brandPref: 'Premium Brand',
148-
requestedBy: 'user789',
152+
requestedBy: validRequestorId,
149153
},
150154
],
151155
stockBought: 100,

0 commit comments

Comments
 (0)