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

Implement project creation via stored procedure; update ProjectCreate schema, ProjectService, and add test scripts for validation

Browse files
app/controllers/projects.py CHANGED
@@ -1,12 +1,3 @@
1
- from fastapi import APIRouter, Depends, status
2
- from sqlalchemy.orm import Session
3
- from app.db.session import get_db
4
- from app.services.project_service import ProjectService
5
- from app.schemas.project import ProjectCreate, ProjectOut
6
- from typing import List
7
-
8
- router = APIRouter(prefix="/api/v1/projects", tags=["projects"])
9
-
10
  from fastapi import APIRouter, Depends, status, Query
11
  from sqlalchemy.orm import Session
12
  from app.db.session import get_db
@@ -52,9 +43,17 @@ def get_project(project_no: int, db: Session = Depends(get_db)):
52
 
53
  @router.post("/", response_model=ProjectOut, status_code=status.HTTP_201_CREATED)
54
  def create_project(project_in: ProjectCreate, db: Session = Depends(get_db)):
55
- """Create a new project"""
 
 
 
 
 
 
 
 
56
  service = ProjectService(db)
57
- return service.create(project_in.dict())
58
 
59
  @router.put("/{project_no}", response_model=ProjectOut)
60
  def update_project(project_no: int, project_in: ProjectCreate, db: Session = Depends(get_db)):
 
 
 
 
 
 
 
 
 
 
1
  from fastapi import APIRouter, Depends, status, Query
2
  from sqlalchemy.orm import Session
3
  from app.db.session import get_db
 
43
 
44
  @router.post("/", response_model=ProjectOut, status_code=status.HTTP_201_CREATED)
45
  def create_project(project_in: ProjectCreate, db: Session = Depends(get_db)):
46
+ """
47
+ Create a new project using stored procedure
48
+
49
+ Creates a new project record using the spProjectsInsert stored procedure.
50
+ All project fields including billing, shipping, payment, and project details
51
+ can be provided in the request body.
52
+
53
+ Returns the created project with the generated ProjectNo.
54
+ """
55
  service = ProjectService(db)
56
+ return service.create(project_in.model_dump())
57
 
58
  @router.put("/{project_no}", response_model=ProjectOut)
59
  def update_project(project_no: int, project_in: ProjectCreate, db: Session = Depends(get_db)):
app/db/repositories/project_repo.py CHANGED
@@ -3,6 +3,7 @@ from sqlalchemy import text, Row
3
  from app.db.models.project import Project
4
  from typing import List, Tuple, Dict, Any
5
  from app.core.exceptions import RepositoryException
 
6
  import logging
7
 
8
  logger = logging.getLogger(__name__)
@@ -15,6 +16,168 @@ class ProjectRepository:
15
  """Get a single project by ProjectNo"""
16
  return self.db.query(Project).filter(Project.project_no == project_no).first()
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  def list_via_sp(self, customer_type: int = 0, order_by: str = "ProjectNo",
19
  order_direction: str = "ASC", page: int = 1, page_size: int = 10) -> Tuple[List[Dict[str, Any]], int]:
20
  """
 
3
  from app.db.models.project import Project
4
  from typing import List, Tuple, Dict, Any
5
  from app.core.exceptions import RepositoryException
6
+ from app.schemas.project import ProjectCreate
7
  import logging
8
 
9
  logger = logging.getLogger(__name__)
 
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
22
+
23
+ Args:
24
+ project_data: ProjectCreate schema containing all project fields
25
+
26
+ Returns:
27
+ int: The new ProjectNo (project ID) generated by the stored procedure
28
+ """
29
+ try:
30
+ # Prepare the stored procedure call with all parameters
31
+ sp_query = text("""
32
+ DECLARE @ProjectNo INT;
33
+ EXEC spProjectsInsert
34
+ @ProjectName = :project_name,
35
+ @ProjectLocation = :project_location,
36
+ @ProjectType = :project_type,
37
+ @BidDate = :bid_date,
38
+ @StartDate = :start_date,
39
+ @IsAwarded = :is_awarded,
40
+ @Notes = :notes,
41
+ @BarrierSize = :barrier_size,
42
+ @LeaseTerm = :lease_term,
43
+ @PurchaseOption = :purchase_option,
44
+ @LeadSource = :lead_source,
45
+ @rep = :rep,
46
+ @EngineerCompanyId = :engineer_company_id,
47
+ @EngineerNotes = :engineer_notes,
48
+ @Status = :status,
49
+ @Bill_Name = :bill_name,
50
+ @Bill_Address1 = :bill_address1,
51
+ @Bill_Address2 = :bill_address2,
52
+ @Bill_City = :bill_city,
53
+ @Bill_State = :bill_state,
54
+ @Bill_Zip = :bill_zip,
55
+ @Ship_Name = :ship_name,
56
+ @Ship_Address1 = :ship_address1,
57
+ @Ship_Address2 = :ship_address2,
58
+ @Ship_City = :ship_city,
59
+ @Ship_State = :ship_state,
60
+ @Ship_Zip = :ship_zip,
61
+ @Acct_Payable = :acct_payable,
62
+ @Bill_Email = :bill_email,
63
+ @Bill_Phone = :bill_phone,
64
+ @Ship_Email = :ship_email,
65
+ @Ship_Phone = :ship_phone,
66
+ @Ship_OfficePhone = :ship_office_phone,
67
+ @_FasDam = :fas_dam,
68
+ @EngineerCompany = :engineer_company,
69
+ @CustomerTypeID = :customer_type_id,
70
+ @PaymentTermId = :payment_term_id,
71
+ @PaymentNote = :payment_note,
72
+ @RentalPriceId = :rental_price_id,
73
+ @PurchasePriceId = :purchase_price_id,
74
+ @EstShipDateId = :est_ship_date_id,
75
+ @FOBId = :fob_id,
76
+ @ExpediteFee = :expedite_fee,
77
+ @EstFreightId = :est_freight_id,
78
+ @EstFreightFee = :est_freight_fee,
79
+ @TaxRate = :tax_rate,
80
+ @WeeklyCharge = :weekly_charge,
81
+ @CrewMembers = :crew_members,
82
+ @TackHoes = :tack_hoes,
83
+ @WaterPump = :water_pump,
84
+ @WaterPump2 = :water_pump2,
85
+ @Pipes = :pipes,
86
+ @Timpers = :timpers,
87
+ @EstInstalationTime = :est_installation_time,
88
+ @RepairKits = :repair_kits,
89
+ @InstallationAdvisor = :installation_advisor,
90
+ @EmployeeId = :employee_id,
91
+ @InstallDate = :install_date,
92
+ @Commission = :commission,
93
+ @AdvisorId = :advisor_id,
94
+ @ShipVia = :ship_via,
95
+ @ValidFor = :valid_for,
96
+ @ProjectNo = @ProjectNo OUTPUT;
97
+ SELECT @ProjectNo AS ProjectNo;
98
+ """)
99
+
100
+ # Execute the stored procedure with parameters
101
+ result = self.db.execute(sp_query, {
102
+ 'project_name': project_data.project_name,
103
+ 'project_location': project_data.project_location,
104
+ 'project_type': project_data.project_type,
105
+ 'bid_date': project_data.bid_date,
106
+ 'start_date': project_data.start_date,
107
+ 'is_awarded': project_data.is_awarded,
108
+ 'notes': project_data.notes,
109
+ 'barrier_size': project_data.barrier_size,
110
+ 'lease_term': project_data.lease_term,
111
+ 'purchase_option': project_data.purchase_option,
112
+ 'lead_source': project_data.lead_source,
113
+ 'rep': project_data.rep,
114
+ 'engineer_company_id': project_data.engineer_company_id,
115
+ 'engineer_notes': project_data.engineer_notes,
116
+ 'status': project_data.status,
117
+ 'bill_name': project_data.bill_name,
118
+ 'bill_address1': project_data.bill_address1,
119
+ 'bill_address2': project_data.bill_address2,
120
+ 'bill_city': project_data.bill_city,
121
+ 'bill_state': project_data.bill_state,
122
+ 'bill_zip': project_data.bill_zip,
123
+ 'ship_name': project_data.ship_name,
124
+ 'ship_address1': project_data.ship_address1,
125
+ 'ship_address2': project_data.ship_address2,
126
+ 'ship_city': project_data.ship_city,
127
+ 'ship_state': project_data.ship_state,
128
+ 'ship_zip': project_data.ship_zip,
129
+ 'acct_payable': project_data.acct_payable,
130
+ 'bill_email': project_data.bill_email,
131
+ 'bill_phone': project_data.bill_phone,
132
+ 'ship_email': project_data.ship_email,
133
+ 'ship_phone': project_data.ship_phone,
134
+ 'ship_office_phone': project_data.ship_office_phone,
135
+ 'fas_dam': project_data.fas_dam,
136
+ 'engineer_company': project_data.engineer_company,
137
+ 'customer_type_id': project_data.customer_type_id,
138
+ 'payment_term_id': project_data.payment_term_id,
139
+ 'payment_note': project_data.payment_note,
140
+ 'rental_price_id': project_data.rental_price_id,
141
+ 'purchase_price_id': project_data.purchase_price_id,
142
+ 'est_ship_date_id': project_data.est_ship_date_id,
143
+ 'fob_id': project_data.fob_id,
144
+ 'expedite_fee': project_data.expedite_fee,
145
+ 'est_freight_id': project_data.est_freight_id,
146
+ 'est_freight_fee': project_data.est_freight_fee,
147
+ 'tax_rate': project_data.tax_rate,
148
+ 'weekly_charge': project_data.weekly_charge,
149
+ 'crew_members': project_data.crew_members,
150
+ 'tack_hoes': project_data.tack_hoes,
151
+ 'water_pump': project_data.water_pump,
152
+ 'water_pump2': project_data.water_pump2,
153
+ 'pipes': project_data.pipes,
154
+ 'timpers': project_data.timpers,
155
+ 'est_installation_time': project_data.est_installation_time,
156
+ 'repair_kits': project_data.repair_kits,
157
+ 'installation_advisor': project_data.installation_advisor,
158
+ 'employee_id': project_data.employee_id,
159
+ 'install_date': project_data.install_date,
160
+ 'commission': project_data.commission,
161
+ 'advisor_id': project_data.advisor_id,
162
+ 'ship_via': project_data.ship_via,
163
+ 'valid_for': project_data.valid_for
164
+ })
165
+
166
+ # Get the generated ProjectNo from the output
167
+ project_row = result.fetchone()
168
+ if project_row and hasattr(project_row, 'ProjectNo'):
169
+ project_no = project_row.ProjectNo
170
+ self.db.commit()
171
+ logger.info(f"Successfully created project with ProjectNo: {project_no}")
172
+ return project_no
173
+ else:
174
+ raise RepositoryException("Failed to get ProjectNo from stored procedure")
175
+
176
+ except Exception as e:
177
+ self.db.rollback()
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
  """
app/schemas/project.py CHANGED
@@ -42,6 +42,40 @@ class ProjectCreate(BaseModel):
42
  ship_email: Optional[str] = None
43
  ship_phone: Optional[str] = None
44
  ship_office_phone: Optional[str] = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  class ProjectOut(BaseModel):
47
  project_no: int
@@ -84,25 +118,41 @@ class ProjectOut(BaseModel):
84
  ship_phone: Optional[str] = None
85
  ship_office_phone: Optional[str] = None
86
 
87
- # Payment and pricing info
88
  acct_payable: Optional[str] = None
89
  payment_term_id: Optional[int] = None
90
  payment_note: Optional[str] = None
91
  rental_price_id: Optional[int] = None
92
  purchase_price_id: Optional[int] = None
93
-
94
- # Financial info
 
 
 
95
  tax_rate: Optional[Decimal] = None
96
  weekly_charge: Optional[Decimal] = None
97
- commission: Optional[Decimal] = None
98
 
99
  # Project details
100
  crew_members: Optional[int] = None
101
- install_date: Optional[datetime] = None
 
 
 
 
 
 
 
102
  employee_id: Optional[str] = None
 
 
103
  advisor_id: Optional[str] = None
 
 
 
 
 
104
 
105
- # Flags
106
  is_international: Optional[bool] = None
107
  order_number: Optional[str] = None
108
 
 
42
  ship_email: Optional[str] = None
43
  ship_phone: Optional[str] = None
44
  ship_office_phone: Optional[str] = None
45
+
46
+ # Accounts and payment information
47
+ acct_payable: Optional[str] = None
48
+ payment_term_id: Optional[int] = None
49
+ payment_note: Optional[str] = None
50
+ rental_price_id: Optional[int] = None
51
+ purchase_price_id: Optional[int] = None
52
+ est_ship_date_id: Optional[int] = None
53
+ fob_id: Optional[int] = None
54
+ expedite_fee: Optional[Decimal] = None
55
+ est_freight_id: Optional[int] = None
56
+ est_freight_fee: Optional[Decimal] = None
57
+ tax_rate: Optional[Decimal] = None
58
+ weekly_charge: Optional[Decimal] = None
59
+
60
+ # Project details
61
+ crew_members: Optional[int] = None
62
+ tack_hoes: Optional[int] = None
63
+ water_pump: Optional[int] = None
64
+ water_pump2: Optional[int] = None
65
+ pipes: Optional[int] = None
66
+ timpers: Optional[int] = None
67
+ est_installation_time: Optional[int] = None
68
+ repair_kits: Optional[str] = None
69
+ installation_advisor: Optional[str] = None
70
+ employee_id: Optional[str] = None
71
+ install_date: Optional[datetime] = None
72
+ commission: Optional[Decimal] = None
73
+ advisor_id: Optional[str] = None
74
+ ship_via: Optional[str] = None
75
+ valid_for: Optional[str] = None
76
+
77
+ # Special fields
78
+ fas_dam: Optional[bool] = False
79
 
80
  class ProjectOut(BaseModel):
81
  project_no: int
 
118
  ship_phone: Optional[str] = None
119
  ship_office_phone: Optional[str] = None
120
 
121
+ # Accounts and payment information
122
  acct_payable: Optional[str] = None
123
  payment_term_id: Optional[int] = None
124
  payment_note: Optional[str] = None
125
  rental_price_id: Optional[int] = None
126
  purchase_price_id: Optional[int] = None
127
+ est_ship_date_id: Optional[int] = None
128
+ fob_id: Optional[int] = None
129
+ expedite_fee: Optional[Decimal] = None
130
+ est_freight_id: Optional[int] = None
131
+ est_freight_fee: Optional[Decimal] = None
132
  tax_rate: Optional[Decimal] = None
133
  weekly_charge: Optional[Decimal] = None
 
134
 
135
  # Project details
136
  crew_members: Optional[int] = None
137
+ tack_hoes: Optional[int] = None
138
+ water_pump: Optional[int] = None
139
+ water_pump2: Optional[int] = None
140
+ pipes: Optional[int] = None
141
+ timpers: Optional[int] = None
142
+ est_installation_time: Optional[int] = None
143
+ repair_kits: Optional[str] = None
144
+ installation_advisor: Optional[str] = None
145
  employee_id: Optional[str] = None
146
+ install_date: Optional[datetime] = None
147
+ commission: Optional[Decimal] = None
148
  advisor_id: Optional[str] = None
149
+ ship_via: Optional[str] = None
150
+ valid_for: Optional[str] = None
151
+
152
+ # Special fields
153
+ fas_dam: Optional[bool] = False
154
 
155
+ # Legacy compatibility fields
156
  is_international: Optional[bool] = None
157
  order_number: Optional[str] = None
158
 
app/services/project_service.py CHANGED
@@ -163,9 +163,30 @@ class ProjectService:
163
  """Legacy list method for backward compatibility"""
164
  return self.repo.list(customer_id, status, skip, limit)
165
 
166
- def create(self, data):
167
- project = Project(**data)
168
- return self.repo.create(project)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
  def update(self, project_no: int, data):
171
  project = self.repo.get(project_no)
 
163
  """Legacy list method for backward compatibility"""
164
  return self.repo.list(customer_id, status, skip, limit)
165
 
166
+ def create(self, project_data: dict):
167
+ """
168
+ Create a new project using stored procedure
169
+
170
+ Args:
171
+ project_data: Dictionary containing project creation data
172
+
173
+ Returns:
174
+ ProjectOut: The created project data
175
+ """
176
+ from app.schemas.project import ProjectCreate
177
+
178
+ # Validate input data using Pydantic schema
179
+ project_create = ProjectCreate(**project_data)
180
+
181
+ # Create project via stored procedure
182
+ project_no = self.repo.create_via_sp(project_create)
183
+
184
+ # Retrieve the created project to return as ProjectOut
185
+ created_project = self.repo.get(project_no)
186
+ if not created_project:
187
+ raise NotFoundException("Failed to retrieve created project")
188
+
189
+ return created_project
190
 
191
  def update(self, project_no: int, data):
192
  project = self.repo.get(project_no)
test_create_curl.sh ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Test script for the create_project endpoint using curl
4
+
5
+ echo "Testing project creation via stored procedure..."
6
+
7
+ # Create test payload
8
+ cat > test_project.json << 'EOF'
9
+ {
10
+ "project_name": "Test Aqua Barrier Project",
11
+ "project_location": "Los Angeles, CA",
12
+ "project_type": "Commercial",
13
+ "bid_date": "2024-01-15T10:00:00",
14
+ "start_date": "2024-02-01T08:00:00",
15
+ "is_awarded": true,
16
+ "notes": "Test project for SP validation",
17
+ "barrier_size": "50ft x 100ft",
18
+ "lease_term": "12 months",
19
+ "purchase_option": false,
20
+ "lead_source": "Website",
21
+ "rep": "John Doe",
22
+ "engineer_company_id": 1,
23
+ "engineer_notes": "Standard installation required",
24
+ "engineer_company": "ABC Engineering",
25
+ "status": 1,
26
+ "customer_type_id": 1,
27
+ "bill_name": "Test Company Inc",
28
+ "bill_address1": "123 Main Street",
29
+ "bill_address2": "Suite 100",
30
+ "bill_city": "Los Angeles",
31
+ "bill_state": "CA",
32
+ "bill_zip": "90210",
33
+ "bill_email": "billing@testcompany.com",
34
+ "bill_phone": "555-123-4567",
35
+ "ship_name": "Test Company Inc",
36
+ "ship_address1": "456 Oak Avenue",
37
+ "ship_city": "Los Angeles",
38
+ "ship_state": "CA",
39
+ "ship_zip": "90211",
40
+ "ship_email": "shipping@testcompany.com",
41
+ "ship_phone": "555-234-5678",
42
+ "ship_office_phone": "555-345-6789",
43
+ "acct_payable": "AP Department",
44
+ "payment_term_id": 1,
45
+ "payment_note": "Net 30",
46
+ "rental_price_id": 1,
47
+ "purchase_price_id": 1,
48
+ "est_ship_date_id": 1,
49
+ "fob_id": 1,
50
+ "expedite_fee": "500.00",
51
+ "est_freight_id": 1,
52
+ "est_freight_fee": "250.00",
53
+ "tax_rate": "8.25",
54
+ "weekly_charge": "1500.00",
55
+ "crew_members": 3,
56
+ "tack_hoes": 2,
57
+ "water_pump": 1,
58
+ "water_pump2": 1,
59
+ "pipes": 10,
60
+ "timpers": 5,
61
+ "est_installation_time": 8,
62
+ "repair_kits": "Standard Kit",
63
+ "installation_advisor": "Installation notes here...",
64
+ "employee_id": "EMP001",
65
+ "install_date": "2024-02-15T09:00:00",
66
+ "commission": "1000.00",
67
+ "advisor_id": "ADV001",
68
+ "ship_via": "UPS Ground",
69
+ "valid_for": "Quote valid for 30 days",
70
+ "fas_dam": false
71
+ }
72
+ EOF
73
+
74
+ echo "Making POST request to create project..."
75
+ echo ""
76
+
77
+ # Make the API call
78
+ response=$(curl -s -w "\nHTTP_STATUS:%{http_code}\n" \
79
+ -X POST \
80
+ -H "Content-Type: application/json" \
81
+ -d @test_project.json \
82
+ http://localhost:8000/api/v1/projects/)
83
+
84
+ # Parse response
85
+ http_status=$(echo "$response" | grep "HTTP_STATUS" | cut -d: -f2)
86
+ response_body=$(echo "$response" | sed '/HTTP_STATUS/d')
87
+
88
+ echo "HTTP Status: $http_status"
89
+ echo ""
90
+
91
+ if [ "$http_status" = "201" ]; then
92
+ echo "✅ Project created successfully!"
93
+ echo "Response:"
94
+ echo "$response_body" | python -m json.tool 2>/dev/null || echo "$response_body"
95
+ else
96
+ echo "❌ Error creating project"
97
+ echo "Response:"
98
+ echo "$response_body"
99
+ fi
100
+
101
+ # Clean up
102
+ rm -f test_project.json
103
+
104
+ echo ""
105
+ echo "Test completed."
test_create_project.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for the create_project endpoint using the spProjectsInsert stored procedure
4
+ """
5
+ import requests
6
+ import json
7
+ from datetime import datetime
8
+ from decimal import Decimal
9
+
10
+ # API endpoint
11
+ BASE_URL = "http://localhost:8000"
12
+ ENDPOINT = f"{BASE_URL}/api/v1/projects/"
13
+
14
+ def test_create_project():
15
+ """Test creating a project with the new stored procedure implementation"""
16
+
17
+ # Sample project data matching the stored procedure parameters
18
+ project_data = {
19
+ "project_name": "Test Aqua Barrier Project",
20
+ "project_location": "Los Angeles, CA",
21
+ "project_type": "Commercial",
22
+ "bid_date": "2024-01-15T10:00:00",
23
+ "start_date": "2024-02-01T08:00:00",
24
+ "is_awarded": True,
25
+ "notes": "Test project for SP validation",
26
+ "barrier_size": "50ft x 100ft",
27
+ "lease_term": "12 months",
28
+ "purchase_option": False,
29
+ "lead_source": "Website",
30
+ "rep": "John Doe",
31
+ "engineer_company_id": 1,
32
+ "engineer_notes": "Standard installation required",
33
+ "engineer_company": "ABC Engineering",
34
+ "status": 1,
35
+ "customer_type_id": 1,
36
+
37
+ # Billing address
38
+ "bill_name": "Test Company Inc",
39
+ "bill_address1": "123 Main Street",
40
+ "bill_address2": "Suite 100",
41
+ "bill_city": "Los Angeles",
42
+ "bill_state": "CA",
43
+ "bill_zip": "90210",
44
+ "bill_email": "billing@testcompany.com",
45
+ "bill_phone": "555-123-4567",
46
+
47
+ # Shipping address
48
+ "ship_name": "Test Company Inc",
49
+ "ship_address1": "456 Oak Avenue",
50
+ "ship_city": "Los Angeles",
51
+ "ship_state": "CA",
52
+ "ship_zip": "90211",
53
+ "ship_email": "shipping@testcompany.com",
54
+ "ship_phone": "555-234-5678",
55
+ "ship_office_phone": "555-345-6789",
56
+
57
+ # Payment and project details
58
+ "acct_payable": "AP Department",
59
+ "payment_term_id": 1,
60
+ "payment_note": "Net 30",
61
+ "rental_price_id": 1,
62
+ "purchase_price_id": 1,
63
+ "est_ship_date_id": 1,
64
+ "fob_id": 1,
65
+ "expedite_fee": "500.00",
66
+ "est_freight_id": 1,
67
+ "est_freight_fee": "250.00",
68
+ "tax_rate": "8.25",
69
+ "weekly_charge": "1500.00",
70
+ "crew_members": 3,
71
+ "tack_hoes": 2,
72
+ "water_pump": 1,
73
+ "water_pump2": 1,
74
+ "pipes": 10,
75
+ "timpers": 5,
76
+ "est_installation_time": 8,
77
+ "repair_kits": "Standard Kit",
78
+ "installation_advisor": "Installation notes here...",
79
+ "employee_id": "EMP001",
80
+ "install_date": "2024-02-15T09:00:00",
81
+ "commission": "1000.00",
82
+ "advisor_id": "ADV001",
83
+ "ship_via": "UPS Ground",
84
+ "valid_for": "Quote valid for 30 days",
85
+ "fas_dam": False
86
+ }
87
+
88
+ print("Testing project creation via stored procedure...")
89
+ print(f"POST {ENDPOINT}")
90
+ print(f"Payload: {json.dumps(project_data, indent=2)}")
91
+
92
+ try:
93
+ # Make the API call
94
+ response = requests.post(
95
+ ENDPOINT,
96
+ json=project_data,
97
+ headers={"Content-Type": "application/json"}
98
+ )
99
+
100
+ print(f"\nResponse Status: {response.status_code}")
101
+ print(f"Response Headers: {dict(response.headers)}")
102
+
103
+ if response.status_code == 201:
104
+ result = response.json()
105
+ print(f"\n✅ Project created successfully!")
106
+ print(f"ProjectNo: {result.get('project_no')}")
107
+ print(f"Project Name: {result.get('project_name')}")
108
+ print(f"Response: {json.dumps(result, indent=2, default=str)}")
109
+ return True
110
+ else:
111
+ print(f"\n❌ Error creating project")
112
+ print(f"Error: {response.text}")
113
+ return False
114
+
115
+ except requests.exceptions.RequestException as e:
116
+ print(f"\n❌ Network error: {e}")
117
+ return False
118
+ except Exception as e:
119
+ print(f"\n❌ Unexpected error: {e}")
120
+ return False
121
+
122
+ if __name__ == "__main__":
123
+ success = test_create_project()
124
+ if success:
125
+ print("\n🎉 Test completed successfully!")
126
+ else:
127
+ print("\n💥 Test failed!")