MukeshKapoor25 commited on
Commit
ea1b45f
Β·
1 Parent(s): 4bd3006

Enhance project management with stored procedures; update project retrieval and modification methods, and add comprehensive test scripts for validation

Browse files
app/controllers/projects.py CHANGED
@@ -57,9 +57,17 @@ def create_project(project_in: ProjectCreate, db: Session = Depends(get_db)):
57
 
58
  @router.put("/{project_no}", response_model=ProjectOut)
59
  def update_project(project_no: int, project_in: ProjectCreate, db: Session = Depends(get_db)):
60
- """Update an existing project"""
 
 
 
 
 
 
 
 
61
  service = ProjectService(db)
62
- return service.update(project_no, project_in.dict())
63
 
64
  @router.delete("/{project_no}", status_code=status.HTTP_204_NO_CONTENT)
65
  def delete_project(project_no: int, db: Session = Depends(get_db)):
 
57
 
58
  @router.put("/{project_no}", response_model=ProjectOut)
59
  def update_project(project_no: int, project_in: ProjectCreate, db: Session = Depends(get_db)):
60
+ """
61
+ Update an existing project using stored procedure
62
+
63
+ Updates an existing project record using the spProjectsUpdate stored procedure.
64
+ All project fields including billing, shipping, payment, and project details
65
+ can be updated in the request body.
66
+
67
+ Returns the updated project data.
68
+ """
69
  service = ProjectService(db)
70
+ return service.update(project_no, project_in.model_dump())
71
 
72
  @router.delete("/{project_no}", status_code=status.HTTP_204_NO_CONTENT)
73
  def delete_project(project_no: int, db: Session = Depends(get_db)):
app/db/repositories/project_repo.py CHANGED
@@ -16,6 +16,44 @@ class ProjectRepository:
16
  """Get a single project by ProjectNo"""
17
  return self.db.query(Project).filter(Project.project_no == project_no).first()
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  def create_via_sp(self, project_data: ProjectCreate) -> int:
20
  """
21
  Create a new project using the spProjectsInsert stored procedure
@@ -178,6 +216,161 @@ class ProjectRepository:
178
  logger.error(f"Error calling spProjectsInsert stored procedure: {e}")
179
  raise RepositoryException(f"Failed to create project: {e}")
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  def list_via_sp(self, customer_type: int = 0, order_by: str = "ProjectNo",
182
  order_direction: str = "ASC", page: int = 1, page_size: int = 10) -> Tuple[List[Dict[str, Any]], int]:
183
  """
 
16
  """Get a single project by ProjectNo"""
17
  return self.db.query(Project).filter(Project.project_no == project_no).first()
18
 
19
+ def get_via_sp(self, project_no: int) -> Dict[str, Any]:
20
+ """
21
+ Get a single project using the spProjectsGet stored procedure
22
+
23
+ Args:
24
+ project_no: The ProjectNo to retrieve
25
+
26
+ Returns:
27
+ Dict containing project data or None if not found
28
+ """
29
+ try:
30
+ # Call the stored procedure
31
+ sp_query = text("""
32
+ EXEC spProjectsGet @ProjectNo = :project_no
33
+ """)
34
+
35
+ result = self.db.execute(sp_query, {'project_no': project_no})
36
+
37
+ if result.returns_rows:
38
+ project_row = result.fetchone()
39
+ if project_row:
40
+ project_data = dict(project_row._mapping)
41
+ logger.info(f"Retrieved project {project_no} via stored procedure")
42
+ return project_data
43
+
44
+ logger.info(f"Project {project_no} not found via stored procedure")
45
+ return None
46
+
47
+ except Exception as e:
48
+ logger.error(f"Error calling spProjectsGet stored procedure: {e}")
49
+ # Fallback to ORM method
50
+ logger.info(f"Falling back to ORM method for project {project_no}")
51
+ project = self.get(project_no)
52
+ if project:
53
+ # Convert SQLAlchemy model to dict for consistency
54
+ return {column.name: getattr(project, column.name) for column in project.__table__.columns}
55
+ return None
56
+
57
  def create_via_sp(self, project_data: ProjectCreate) -> int:
58
  """
59
  Create a new project using the spProjectsInsert stored procedure
 
216
  logger.error(f"Error calling spProjectsInsert stored procedure: {e}")
217
  raise RepositoryException(f"Failed to create project: {e}")
218
 
219
+ def update_via_sp(self, project_no: int, project_data: ProjectCreate) -> bool:
220
+ """
221
+ Update an existing project using the spProjectsUpdate stored procedure
222
+
223
+ Args:
224
+ project_no: The ProjectNo to update
225
+ project_data: ProjectCreate schema containing updated project fields
226
+
227
+ Returns:
228
+ bool: True if update was successful
229
+ """
230
+ try:
231
+ # Prepare the stored procedure call with all parameters
232
+ sp_query = text("""
233
+ EXEC spProjectsUpdate
234
+ @ProjectNo = :project_no,
235
+ @ProjectName = :project_name,
236
+ @ProjectLocation = :project_location,
237
+ @ProjectType = :project_type,
238
+ @BidDate = :bid_date,
239
+ @StartDate = :start_date,
240
+ @IsAwarded = :is_awarded,
241
+ @Notes = :notes,
242
+ @BarrierSize = :barrier_size,
243
+ @LeaseTerm = :lease_term,
244
+ @PurchaseOption = :purchase_option,
245
+ @LeadSource = :lead_source,
246
+ @rep = :rep,
247
+ @EngineerCompanyId = :engineer_company_id,
248
+ @EngineerNotes = :engineer_notes,
249
+ @Status = :status,
250
+ @Bill_Name = :bill_name,
251
+ @Bill_Address1 = :bill_address1,
252
+ @Bill_Address2 = :bill_address2,
253
+ @Bill_City = :bill_city,
254
+ @Bill_State = :bill_state,
255
+ @Bill_Zip = :bill_zip,
256
+ @Ship_Name = :ship_name,
257
+ @Ship_Address1 = :ship_address1,
258
+ @Ship_Address2 = :ship_address2,
259
+ @Ship_City = :ship_city,
260
+ @Ship_State = :ship_state,
261
+ @Ship_Zip = :ship_zip,
262
+ @Acct_Payable = :acct_payable,
263
+ @Bill_Email = :bill_email,
264
+ @Bill_Phone = :bill_phone,
265
+ @Ship_Email = :ship_email,
266
+ @Ship_Phone = :ship_phone,
267
+ @Ship_OfficePhone = :ship_office_phone,
268
+ @EngineerCompany = :engineer_company,
269
+ @_FasDam = :fas_dam,
270
+ @PaymentTermId = :payment_term_id,
271
+ @PaymentNote = :payment_note,
272
+ @RentalPriceId = :rental_price_id,
273
+ @PurchasePriceId = :purchase_price_id,
274
+ @EstShipDateId = :est_ship_date_id,
275
+ @FOBId = :fob_id,
276
+ @ExpediteFee = :expedite_fee,
277
+ @EstFreightId = :est_freight_id,
278
+ @EstFreightFee = :est_freight_fee,
279
+ @TaxRate = :tax_rate,
280
+ @WeeklyCharge = :weekly_charge,
281
+ @CrewMembers = :crew_members,
282
+ @TackHoes = :tack_hoes,
283
+ @WaterPump = :water_pump,
284
+ @WaterPump2 = :water_pump2,
285
+ @Pipes = :pipes,
286
+ @Timpers = :timpers,
287
+ @EstInstalationTime = :est_installation_time,
288
+ @RepairKits = :repair_kits,
289
+ @InstallationAdvisor = :installation_advisor,
290
+ @EmployeeId = :employee_id,
291
+ @InstallDate = :install_date,
292
+ @Commission = :commission,
293
+ @AdvisorId = :advisor_id,
294
+ @ShipVia = :ship_via,
295
+ @ValidFor = :valid_for
296
+ """)
297
+
298
+ # Execute the stored procedure with parameters
299
+ self.db.execute(sp_query, {
300
+ 'project_no': project_no,
301
+ 'project_name': project_data.project_name,
302
+ 'project_location': project_data.project_location,
303
+ 'project_type': project_data.project_type,
304
+ 'bid_date': project_data.bid_date,
305
+ 'start_date': project_data.start_date,
306
+ 'is_awarded': project_data.is_awarded,
307
+ 'notes': project_data.notes,
308
+ 'barrier_size': project_data.barrier_size,
309
+ 'lease_term': project_data.lease_term,
310
+ 'purchase_option': project_data.purchase_option,
311
+ 'lead_source': project_data.lead_source,
312
+ 'rep': project_data.rep,
313
+ 'engineer_company_id': project_data.engineer_company_id,
314
+ 'engineer_notes': project_data.engineer_notes,
315
+ 'status': project_data.status,
316
+ 'bill_name': project_data.bill_name,
317
+ 'bill_address1': project_data.bill_address1,
318
+ 'bill_address2': project_data.bill_address2,
319
+ 'bill_city': project_data.bill_city,
320
+ 'bill_state': project_data.bill_state,
321
+ 'bill_zip': project_data.bill_zip,
322
+ 'ship_name': project_data.ship_name,
323
+ 'ship_address1': project_data.ship_address1,
324
+ 'ship_address2': project_data.ship_address2,
325
+ 'ship_city': project_data.ship_city,
326
+ 'ship_state': project_data.ship_state,
327
+ 'ship_zip': project_data.ship_zip,
328
+ 'acct_payable': project_data.acct_payable,
329
+ 'bill_email': project_data.bill_email,
330
+ 'bill_phone': project_data.bill_phone,
331
+ 'ship_email': project_data.ship_email,
332
+ 'ship_phone': project_data.ship_phone,
333
+ 'ship_office_phone': project_data.ship_office_phone,
334
+ 'engineer_company': project_data.engineer_company,
335
+ 'fas_dam': project_data.fas_dam,
336
+ 'payment_term_id': project_data.payment_term_id,
337
+ 'payment_note': project_data.payment_note,
338
+ 'rental_price_id': project_data.rental_price_id,
339
+ 'purchase_price_id': project_data.purchase_price_id,
340
+ 'est_ship_date_id': project_data.est_ship_date_id,
341
+ 'fob_id': project_data.fob_id,
342
+ 'expedite_fee': project_data.expedite_fee,
343
+ 'est_freight_id': project_data.est_freight_id,
344
+ 'est_freight_fee': project_data.est_freight_fee,
345
+ 'tax_rate': project_data.tax_rate,
346
+ 'weekly_charge': project_data.weekly_charge,
347
+ 'crew_members': project_data.crew_members,
348
+ 'tack_hoes': project_data.tack_hoes,
349
+ 'water_pump': project_data.water_pump,
350
+ 'water_pump2': project_data.water_pump2,
351
+ 'pipes': project_data.pipes,
352
+ 'timpers': project_data.timpers,
353
+ 'est_installation_time': project_data.est_installation_time,
354
+ 'repair_kits': project_data.repair_kits,
355
+ 'installation_advisor': project_data.installation_advisor,
356
+ 'employee_id': project_data.employee_id,
357
+ 'install_date': project_data.install_date,
358
+ 'commission': project_data.commission,
359
+ 'advisor_id': project_data.advisor_id,
360
+ 'ship_via': project_data.ship_via,
361
+ 'valid_for': project_data.valid_for
362
+ })
363
+
364
+ # Commit the transaction
365
+ self.db.commit()
366
+ logger.info(f"Successfully updated project {project_no} via stored procedure")
367
+ return True
368
+
369
+ except Exception as e:
370
+ self.db.rollback()
371
+ logger.error(f"Error calling spProjectsUpdate stored procedure: {e}")
372
+ raise RepositoryException(f"Failed to update project: {e}")
373
+
374
  def list_via_sp(self, customer_type: int = 0, order_by: str = "ProjectNo",
375
  order_direction: str = "ASC", page: int = 1, page_size: int = 10) -> Tuple[List[Dict[str, Any]], int]:
376
  """
app/services/project_service.py CHANGED
@@ -27,11 +27,32 @@ class ProjectService:
27
  self.repo = ProjectRepository(db)
28
 
29
  def get(self, project_no: int):
30
- """Get a single project by ProjectNo"""
31
- project = self.repo.get(project_no)
32
- if not project:
 
 
 
 
 
 
 
 
 
 
33
  raise NotFoundException("Project not found")
34
- return project
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  def list_projects(self, customer_type: int = 0, order_by: str = "project_no",
37
  order_direction: str = "asc", page: int = 1, page_size: int = 10) -> PaginatedResponse[ProjectOut]:
@@ -140,13 +161,29 @@ class ProjectService:
140
  'PaymentNote': 'payment_note',
141
  'RentalPriceId': 'rental_price_id',
142
  'PurchasePriceId': 'purchase_price_id',
 
 
 
 
 
143
  'TaxRate': 'tax_rate',
144
  'WeeklyCharge': 'weekly_charge',
145
  'Commission': 'commission',
146
  'CrewMembers': 'crew_members',
147
- 'InstallDate': 'install_date',
 
 
 
 
 
 
 
148
  'EmployeeId': 'employee_id',
 
149
  'AdvisorId': 'advisor_id',
 
 
 
150
  'IsInternational': 'is_international',
151
  'OrderNumber': 'order_number'
152
  }
@@ -188,13 +225,41 @@ class ProjectService:
188
 
189
  return created_project
190
 
191
- def update(self, project_no: int, data):
192
- project = self.repo.get(project_no)
193
- if not project:
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  raise NotFoundException("Project not found")
195
- for k, v in data.items():
196
- setattr(project, k, v)
197
- return self.repo.update(project)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
 
199
  def delete(self, project_no: int):
200
  project = self.repo.get(project_no)
 
27
  self.repo = ProjectRepository(db)
28
 
29
  def get(self, project_no: int):
30
+ """
31
+ Get a single project by ProjectNo using stored procedure
32
+
33
+ Args:
34
+ project_no: The ProjectNo to retrieve
35
+
36
+ Returns:
37
+ ProjectOut: The project data as a Pydantic model
38
+ """
39
+ # Get project data via stored procedure
40
+ project_data = self.repo.get_via_sp(project_no)
41
+
42
+ if not project_data:
43
  raise NotFoundException("Project not found")
44
+
45
+ # Transform database row data to match ProjectOut schema
46
+ transformed_data = self._transform_db_row_to_schema(project_data)
47
+
48
+ # Create and return ProjectOut instance
49
+ try:
50
+ project_out = ProjectOut(**transformed_data)
51
+ return project_out
52
+ except Exception as e:
53
+ logger.error(f"Error creating ProjectOut from data: {e}")
54
+ logger.debug(f"Transformed data: {transformed_data}")
55
+ raise NotFoundException("Error processing project data")
56
 
57
  def list_projects(self, customer_type: int = 0, order_by: str = "project_no",
58
  order_direction: str = "asc", page: int = 1, page_size: int = 10) -> PaginatedResponse[ProjectOut]:
 
161
  'PaymentNote': 'payment_note',
162
  'RentalPriceId': 'rental_price_id',
163
  'PurchasePriceId': 'purchase_price_id',
164
+ 'EstShipDateId': 'est_ship_date_id',
165
+ 'FOBId': 'fob_id',
166
+ 'ExpediteFee': 'expedite_fee',
167
+ 'EstFreightId': 'est_freight_id',
168
+ 'EstFreightFee': 'est_freight_fee',
169
  'TaxRate': 'tax_rate',
170
  'WeeklyCharge': 'weekly_charge',
171
  'Commission': 'commission',
172
  'CrewMembers': 'crew_members',
173
+ 'TackHoes': 'tack_hoes',
174
+ 'WaterPump': 'water_pump',
175
+ 'WaterPump2': 'water_pump2',
176
+ 'Pipes': 'pipes',
177
+ 'Timpers': 'timpers',
178
+ 'EstInstalationTime': 'est_installation_time', # Note: SP has typo "Instalation"
179
+ 'RepairKits': 'repair_kits',
180
+ 'InstallationAdvisor': 'installation_advisor',
181
  'EmployeeId': 'employee_id',
182
+ 'InstallDate': 'install_date',
183
  'AdvisorId': 'advisor_id',
184
+ 'ShipVia': 'ship_via',
185
+ 'ValidFor': 'valid_for',
186
+ '_FasDam': 'fas_dam',
187
  'IsInternational': 'is_international',
188
  'OrderNumber': 'order_number'
189
  }
 
225
 
226
  return created_project
227
 
228
+ def update(self, project_no: int, project_data: dict):
229
+ """
230
+ Update an existing project using stored procedure
231
+
232
+ Args:
233
+ project_no: The ProjectNo to update
234
+ project_data: Dictionary containing updated project data
235
+
236
+ Returns:
237
+ ProjectOut: The updated project data
238
+ """
239
+ from app.schemas.project import ProjectCreate
240
+
241
+ # First check if project exists
242
+ existing_project_data = self.repo.get_via_sp(project_no)
243
+ if not existing_project_data:
244
  raise NotFoundException("Project not found")
245
+
246
+ # Validate input data using Pydantic schema
247
+ project_update = ProjectCreate(**project_data)
248
+
249
+ # Update project via stored procedure
250
+ success = self.repo.update_via_sp(project_no, project_update)
251
+
252
+ if not success:
253
+ raise NotFoundException("Failed to update project")
254
+
255
+ # Retrieve the updated project to return as ProjectOut
256
+ updated_project_data = self.repo.get_via_sp(project_no)
257
+ if not updated_project_data:
258
+ raise NotFoundException("Failed to retrieve updated project")
259
+
260
+ # Transform and return the updated project
261
+ transformed_data = self._transform_db_row_to_schema(updated_project_data)
262
+ return ProjectOut(**transformed_data)
263
 
264
  def delete(self, project_no: int):
265
  project = self.repo.get(project_no)
test_get_project.sh ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Test script for the get_project endpoint using curl
4
+
5
+ echo "Testing get_project endpoint via stored procedure..."
6
+
7
+ echo ""
8
+ echo "=== Test 1: Get existing project (ID: 2) ==="
9
+ echo "GET /api/v1/projects/2"
10
+
11
+ response1=$(curl -s -w "\nHTTP_STATUS:%{http_code}\n" \
12
+ -X GET \
13
+ -H "Content-Type: application/json" \
14
+ http://localhost:8000/api/v1/projects/2)
15
+
16
+ # Parse response
17
+ http_status1=$(echo "$response1" | grep "HTTP_STATUS" | cut -d: -f2)
18
+ response_body1=$(echo "$response1" | sed '/HTTP_STATUS/d')
19
+
20
+ echo "HTTP Status: $http_status1"
21
+ if [ "$http_status1" = "200" ]; then
22
+ echo "βœ… Project retrieved successfully!"
23
+ echo "Project Name: $(echo "$response_body1" | grep -o '"project_name":"[^"]*"' | cut -d'"' -f4)"
24
+ echo "Project Location: $(echo "$response_body1" | grep -o '"project_location":"[^"]*"' | cut -d'"' -f4)"
25
+ else
26
+ echo "❌ Error retrieving project"
27
+ echo "$response_body1"
28
+ fi
29
+
30
+ echo ""
31
+ echo "=== Test 2: Get non-existent project (ID: 999999) ==="
32
+ echo "GET /api/v1/projects/999999"
33
+
34
+ response2=$(curl -s -w "\nHTTP_STATUS:%{http_code}\n" \
35
+ -X GET \
36
+ -H "Content-Type: application/json" \
37
+ http://localhost:8000/api/v1/projects/999999)
38
+
39
+ # Parse response
40
+ http_status2=$(echo "$response2" | grep "HTTP_STATUS" | cut -d: -f2)
41
+ response_body2=$(echo "$response2" | sed '/HTTP_STATUS/d')
42
+
43
+ echo "HTTP Status: $http_status2"
44
+ if [ "$http_status2" = "404" ]; then
45
+ echo "βœ… Correctly returned 404 for non-existent project"
46
+ echo "Error message: $response_body2"
47
+ else
48
+ echo "❌ Unexpected response for non-existent project"
49
+ echo "$response_body2"
50
+ fi
51
+
52
+ echo ""
53
+ echo "=== Test 3: Test our created project (ID: 15865) ==="
54
+ echo "GET /api/v1/projects/15865"
55
+
56
+ response3=$(curl -s -w "\nHTTP_STATUS:%{http_code}\n" \
57
+ -X GET \
58
+ -H "Content-Type: application/json" \
59
+ http://localhost:8000/api/v1/projects/15865)
60
+
61
+ # Parse response
62
+ http_status3=$(echo "$response3" | grep "HTTP_STATUS" | cut -d: -f2)
63
+ response_body3=$(echo "$response3" | sed '/HTTP_STATUS/d')
64
+
65
+ echo "HTTP Status: $http_status3"
66
+ if [ "$http_status3" = "200" ]; then
67
+ echo "βœ… Our created project retrieved successfully!"
68
+ echo "Project Name: $(echo "$response_body3" | grep -o '"project_name":"[^"]*"' | cut -d'"' -f4)"
69
+ echo "Project Location: $(echo "$response_body3" | grep -o '"project_location":"[^"]*"' | cut -d'"' -f4)"
70
+ echo "Response (formatted):"
71
+ echo "$response_body3" | python -m json.tool 2>/dev/null || echo "$response_body3"
72
+ else
73
+ echo "❌ Error retrieving our created project"
74
+ echo "$response_body3"
75
+ fi
76
+
77
+ echo ""
78
+ echo "πŸŽ‰ Get project tests completed!"
test_update_project.sh ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Test script for the update_project endpoint using curl
4
+
5
+ echo "Testing update_project endpoint via stored procedure..."
6
+
7
+ PROJECT_ID=15865
8
+
9
+ echo ""
10
+ echo "=== Test 1: Get current project data (ID: $PROJECT_ID) ==="
11
+ echo "GET /api/v1/projects/$PROJECT_ID"
12
+
13
+ current_project=$(curl -s -X GET -H "Content-Type: application/json" \
14
+ http://localhost:8000/api/v1/projects/$PROJECT_ID)
15
+
16
+ echo "Current project name: $(echo "$current_project" | grep -o '"project_name":"[^"]*"' | cut -d'"' -f4)"
17
+ echo "Current project location: $(echo "$current_project" | grep -o '"project_location":"[^"]*"' | cut -d'"' -f4)"
18
+
19
+ echo ""
20
+ echo "=== Test 2: Update project (ID: $PROJECT_ID) ==="
21
+ echo "PUT /api/v1/projects/$PROJECT_ID"
22
+
23
+ # Create update payload with some changes
24
+ cat > update_project.json << 'EOF'
25
+ {
26
+ "project_name": "UPDATED: Test Aqua Barrier Project",
27
+ "project_location": "UPDATED: San Francisco, CA",
28
+ "project_type": "Commercial - Updated",
29
+ "bid_date": "2024-01-20T10:00:00",
30
+ "start_date": "2024-02-05T08:00:00",
31
+ "is_awarded": true,
32
+ "notes": "UPDATED: Test project for SP validation",
33
+ "barrier_size": "UPDATED: 60ft x 120ft",
34
+ "lease_term": "18 months",
35
+ "purchase_option": false,
36
+ "lead_source": "Website - Updated",
37
+ "rep": "Jane Doe",
38
+ "engineer_company_id": 2,
39
+ "engineer_notes": "UPDATED: Advanced installation required",
40
+ "engineer_company": "XYZ Engineering",
41
+ "status": 2,
42
+ "customer_type_id": 1,
43
+ "bill_name": "UPDATED: Test Company Inc",
44
+ "bill_address1": "456 Updated Street",
45
+ "bill_address2": "Suite 200",
46
+ "bill_city": "San Francisco",
47
+ "bill_state": "CA",
48
+ "bill_zip": "94102",
49
+ "bill_email": "updated-billing@testcompany.com",
50
+ "bill_phone": "555-999-8888",
51
+ "ship_name": "UPDATED: Test Company Inc",
52
+ "ship_address1": "789 New Avenue",
53
+ "ship_city": "San Francisco",
54
+ "ship_state": "CA",
55
+ "ship_zip": "94103",
56
+ "ship_email": "updated-shipping@testcompany.com",
57
+ "ship_phone": "555-888-7777",
58
+ "ship_office_phone": "555-777-6666",
59
+ "acct_payable": "Updated AP Department",
60
+ "payment_term_id": 2,
61
+ "payment_note": "Net 45",
62
+ "rental_price_id": 2,
63
+ "purchase_price_id": 2,
64
+ "est_ship_date_id": 2,
65
+ "fob_id": 2,
66
+ "expedite_fee": "750.00",
67
+ "est_freight_id": 2,
68
+ "est_freight_fee": "350.00",
69
+ "tax_rate": "9.50",
70
+ "weekly_charge": "2000.00",
71
+ "crew_members": 4,
72
+ "tack_hoes": 3,
73
+ "water_pump": 2,
74
+ "water_pump2": 2,
75
+ "pipes": 15,
76
+ "timpers": 8,
77
+ "est_installation_time": 12,
78
+ "repair_kits": "Premium Kit",
79
+ "installation_advisor": "UPDATED: Special installation notes here...",
80
+ "employee_id": "EMP002",
81
+ "install_date": "2024-02-20T09:00:00",
82
+ "commission": "1500.00",
83
+ "advisor_id": "ADV002",
84
+ "ship_via": "FedEx Express",
85
+ "valid_for": "Quote valid for 60 days",
86
+ "fas_dam": true
87
+ }
88
+ EOF
89
+
90
+ echo "Making PUT request to update project..."
91
+
92
+ response=$(curl -s -w "\nHTTP_STATUS:%{http_code}\n" \
93
+ -X PUT \
94
+ -H "Content-Type: application/json" \
95
+ -d @update_project.json \
96
+ http://localhost:8000/api/v1/projects/$PROJECT_ID)
97
+
98
+ # Parse response
99
+ http_status=$(echo "$response" | grep "HTTP_STATUS" | cut -d: -f2)
100
+ response_body=$(echo "$response" | sed '/HTTP_STATUS/d')
101
+
102
+ echo "HTTP Status: $http_status"
103
+
104
+ if [ "$http_status" = "200" ]; then
105
+ echo "βœ… Project updated successfully!"
106
+ echo "Updated project name: $(echo "$response_body" | grep -o '"project_name":"[^"]*"' | cut -d'"' -f4)"
107
+ echo "Updated project location: $(echo "$response_body" | grep -o '"project_location":"[^"]*"' | cut -d'"' -f4)"
108
+ echo "Updated barrier size: $(echo "$response_body" | grep -o '"barrier_size":"[^"]*"' | cut -d'"' -f4)"
109
+ echo ""
110
+ echo "Full Response (formatted):"
111
+ echo "$response_body" | python -m json.tool 2>/dev/null || echo "$response_body"
112
+ else
113
+ echo "❌ Error updating project"
114
+ echo "Response:"
115
+ echo "$response_body"
116
+ fi
117
+
118
+ echo ""
119
+ echo "=== Test 3: Verify update by fetching project again ==="
120
+ echo "GET /api/v1/projects/$PROJECT_ID"
121
+
122
+ updated_project=$(curl -s -X GET -H "Content-Type: application/json" \
123
+ http://localhost:8000/api/v1/projects/$PROJECT_ID)
124
+
125
+ echo "Verified project name: $(echo "$updated_project" | grep -o '"project_name":"[^"]*"' | cut -d'"' -f4)"
126
+ echo "Verified project location: $(echo "$updated_project" | grep -o '"project_location":"[^"]*"' | cut -d'"' -f4)"
127
+
128
+ echo ""
129
+ echo "=== Test 4: Update non-existent project (ID: 999999) ==="
130
+ echo "PUT /api/v1/projects/999999"
131
+
132
+ response_not_found=$(curl -s -w "\nHTTP_STATUS:%{http_code}\n" \
133
+ -X PUT \
134
+ -H "Content-Type: application/json" \
135
+ -d @update_project.json \
136
+ http://localhost:8000/api/v1/projects/999999)
137
+
138
+ # Parse response
139
+ http_status_not_found=$(echo "$response_not_found" | grep "HTTP_STATUS" | cut -d: -f2)
140
+ response_body_not_found=$(echo "$response_not_found" | sed '/HTTP_STATUS/d')
141
+
142
+ echo "HTTP Status: $http_status_not_found"
143
+ if [ "$http_status_not_found" = "404" ]; then
144
+ echo "βœ… Correctly returned 404 for non-existent project"
145
+ echo "Error message: $response_body_not_found"
146
+ else
147
+ echo "❌ Unexpected response for non-existent project"
148
+ echo "$response_body_not_found"
149
+ fi
150
+
151
+ # Clean up
152
+ rm -f update_project.json
153
+
154
+ echo ""
155
+ echo "πŸŽ‰ Update project tests completed!"