rajeshbms commited on
Commit
f9a1cab
·
1 Parent(s): 901a0a1

feat(reference): add customer_type_id filtering to reference data endpoints

Browse files

- Add customer_type_id query parameter to get_all_reference_data endpoint
- Add customer_type_id query parameter to get_company_types endpoint
- Add customer_type_id query parameter to get_lead_sources endpoint
- Refactor company_types repository method to use direct SQL queries with parameter binding
- Refactor lead_sources repository method to use direct SQL queries with parameter binding
- Update active_only filter logic to include NULL values for enabled/inactive flags
- Improve cross-version database compatibility by using text() queries instead of ORM
- Add proper field name-based mapping for query result handling
- Update cache key generation to include customer_type_id parameter
- Enhance logging messages for better debugging of query failures

app/controllers/reference.py CHANGED
@@ -26,6 +26,7 @@ router = APIRouter(prefix="/api/v1/reference", tags=["reference-data"])
26
  @router.get("/all", response_model=ReferenceDataResponse)
27
  def get_all_reference_data(
28
  active_only: bool = Query(True, description="Return only active records"),
 
29
  db: Session = Depends(get_db),
30
  current_user: Optional[CurrentUser] = Depends(get_current_user_optional)
31
  ):
@@ -48,7 +49,7 @@ def get_all_reference_data(
48
  **Note**: This endpoint is public but may have enhanced data for authenticated users.
49
  """
50
  service = ReferenceDataService(db)
51
- return service.get_all_reference_data(active_only=active_only)
52
 
53
  @router.get("/states", response_model=List[StateOut])
54
  def get_states(
 
26
  @router.get("/all", response_model=ReferenceDataResponse)
27
  def get_all_reference_data(
28
  active_only: bool = Query(True, description="Return only active records"),
29
+ customer_type_id: Optional[int] = Query(None, description="Filter by customer type ID"),
30
  db: Session = Depends(get_db),
31
  current_user: Optional[CurrentUser] = Depends(get_current_user_optional)
32
  ):
 
49
  **Note**: This endpoint is public but may have enhanced data for authenticated users.
50
  """
51
  service = ReferenceDataService(db)
52
+ return service.get_all_reference_data(active_only=active_only, customer_type_id=customer_type_id)
53
 
54
  @router.get("/states", response_model=List[StateOut])
55
  def get_states(
app/db/repositories/reference_repo.py CHANGED
@@ -43,7 +43,7 @@ class ReferenceDataRepository:
43
  return self._cache[cache_key]
44
 
45
  try:
46
- # Use direct SQL query instead of stored procedure
47
  query_str = """
48
  SELECT TOP (1000) [StateID], [CustomerTypeID], [Description], [Enabled]
49
  FROM [hs-prod3].[dbo].[States]
@@ -52,7 +52,7 @@ class ReferenceDataRepository:
52
  params = {}
53
 
54
  if active_only:
55
- query_str += " AND [Enabled] = 1"
56
 
57
  if customer_type_id is not None:
58
  query_str += " AND [CustomerTypeID] = :customer_type_id"
@@ -61,10 +61,10 @@ class ReferenceDataRepository:
61
  sql_query = text(query_str)
62
  result = self.db.execute(sql_query, params)
63
 
 
64
  if result.returns_rows:
65
- # Map the query results to our expected schema
66
- states_data = []
67
  for row in result.fetchall():
 
68
  row_dict = dict(row._mapping)
69
  # Map query output to our API format
70
  state_data = {
@@ -80,7 +80,7 @@ class ReferenceDataRepository:
80
  return states_data
81
 
82
  except Exception as e:
83
- logger.warning(f"Direct query failed: {e}", exc_info=True)
84
 
85
  return []
86
 
@@ -118,30 +118,46 @@ class ReferenceDataRepository:
118
  if cache_key in self._cache:
119
  return self._cache[cache_key]
120
 
121
- query = self.db.query(CompanyType)
122
- if active_only:
123
- # The CompanyTypes table stores an "Inactive" flag mapped to CompanyType.enabled.
124
- # When active_only is True, return only active records (Inactive == False).
125
- query = query.filter(CompanyType.enabled == False)
126
-
127
- if customer_type_id is not None:
128
- query = query.filter(CompanyType.customer_type_id == customer_type_id)
129
-
130
- company_types = query.all()
131
- # Align keys to CompanyTypeOut schema: company_type_id, customer_type_id, description, inactive
132
- company_types_data = [
133
- {
134
- 'company_type_id': ct.company_type_id,
135
- 'customer_type_id': ct.customer_type_id,
136
- 'description': ct.description,
137
- 'inactive': ct.enabled,
138
- }
139
- for ct in company_types
140
- ]
141
-
142
- self._cache[cache_key] = company_types_data
143
- logger.info(f"Retrieved {len(company_types_data)} company types")
144
- return company_types_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
  def get_lead_sources(self, active_only: bool = True, customer_type_id: Optional[int] = None) -> List[Dict[str, Any]]:
147
  """Get all lead generation sources, optionally filtered by customer_type_id"""
@@ -149,27 +165,46 @@ class ReferenceDataRepository:
149
  if cache_key in self._cache:
150
  return self._cache[cache_key]
151
 
152
- query = self.db.query(LeadGeneratedFrom)
153
- if active_only:
154
- query = query.filter((LeadGeneratedFrom.enabled == True) | (LeadGeneratedFrom.enabled.is_(None)))
155
-
156
- if customer_type_id is not None:
157
- query = query.filter(LeadGeneratedFrom.customer_type_id == customer_type_id)
158
-
159
- lead_sources = query.order_by(LeadGeneratedFrom.description.asc()).all()
160
- lead_sources_data = [
161
- {
162
- 'lead_generated_from_id': ls.lead_generated_from_id,
163
- 'customer_type_id': ls.customer_type_id,
164
- 'description': ls.description,
165
- 'enabled': ls.enabled
166
- }
167
- for ls in lead_sources
168
- ]
169
-
170
- self._cache[cache_key] = lead_sources_data
171
- logger.info(f"Retrieved {len(lead_sources_data)} lead sources")
172
- return lead_sources_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  def get_payment_terms(self, active_only: bool = True) -> List[Dict[str, Any]]:
175
  """Get all payment terms"""
@@ -393,13 +428,16 @@ class ReferenceDataRepository:
393
  """Get all project statuses - Temporarily disabled due to model issues"""
394
  return [] # Return empty list for now
395
 
396
- def get_all_reference_data(self, active_only: bool = True) -> Dict[str, List[Dict[str, Any]]]:
397
  """Get all reference data in a single call for efficiency"""
 
 
 
398
  return {
399
- 'states': self.get_states(active_only),
400
  'countries': self.get_countries(active_only),
401
- 'company_types': self.get_company_types(active_only),
402
- 'lead_sources': self.get_lead_sources(active_only),
403
  'payment_terms': self.get_payment_terms(active_only),
404
  'purchase_prices': self.get_purchase_prices(active_only),
405
  'rental_prices': self.get_rental_prices(active_only),
@@ -409,7 +447,7 @@ class ReferenceDataRepository:
409
  'est_ship_dates': self.get_est_ship_dates(active_only),
410
  'est_freights': self.get_est_freights(active_only),
411
  'status_info': self.get_status_info(active_only),
412
- 'priorities': self.get_priorities(True, 1), # Get priorities with CustomerTypeID = 1
413
  'phone_types': self.get_phone_types(active_only),
414
  }
415
 
@@ -447,31 +485,46 @@ class ReferenceDataRepository:
447
  return []
448
 
449
  def get_priorities(self, active_only: bool = True, customer_type_id: Optional[int] = None) -> List[Dict[str, Any]]:
450
- """Get all priorities, filtered by CustomerTypeID = 1"""
451
  cache_key = f"priorities_{active_only}_{customer_type_id}"
452
  if cache_key in self._cache:
453
  return self._cache[cache_key]
454
 
455
- # Use direct SQL query as specified: SELECT PriorityID, CustomerTypeID, Description FROM Priorities WHERE CustomerTypeID = 1
456
- query = text("""
 
457
  SELECT [PriorityID], [CustomerTypeID], [Description]
458
- FROM [Priorities]
459
- WHERE [CustomerTypeID] = 1
460
- """)
461
-
462
- result = self.db.execute(query)
463
- priorities_data = []
464
-
465
- for row in result.fetchall():
466
- priorities_data.append({
467
- 'priority_id': row[0], # PriorityID
468
- 'customer_type_id': row[1], # CustomerTypeID
469
- 'description': row[2] # Description
470
- })
471
-
472
- self._cache[cache_key] = priorities_data
473
- logger.info(f"Retrieved {len(priorities_data)} priorities with CustomerTypeID = 1")
474
- return priorities_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
  def get_phone_types(self, active_only: bool = False) -> List[Dict[str, Any]]:
477
  """Get all phone types"""
 
43
  return self._cache[cache_key]
44
 
45
  try:
46
+ # Use direct SQL query with proper parameter binding for cross-version compatibility
47
  query_str = """
48
  SELECT TOP (1000) [StateID], [CustomerTypeID], [Description], [Enabled]
49
  FROM [hs-prod3].[dbo].[States]
 
52
  params = {}
53
 
54
  if active_only:
55
+ query_str += " AND ([Enabled] = 1 OR [Enabled] IS NULL)"
56
 
57
  if customer_type_id is not None:
58
  query_str += " AND [CustomerTypeID] = :customer_type_id"
 
61
  sql_query = text(query_str)
62
  result = self.db.execute(sql_query, params)
63
 
64
+ states_data = []
65
  if result.returns_rows:
 
 
66
  for row in result.fetchall():
67
+ # Use field name-based mapping for proper structure
68
  row_dict = dict(row._mapping)
69
  # Map query output to our API format
70
  state_data = {
 
80
  return states_data
81
 
82
  except Exception as e:
83
+ logger.warning(f"Direct states query failed: {e}", exc_info=True)
84
 
85
  return []
86
 
 
118
  if cache_key in self._cache:
119
  return self._cache[cache_key]
120
 
121
+ try:
122
+ # Use direct SQL query for better compatibility across Python versions
123
+ query_str = """
124
+ SELECT [CompanyTypeID], [CustomerTypeID], [Description], [Inactive]
125
+ FROM [CompanyTypes]
126
+ WHERE 1=1
127
+ """
128
+ params = {}
129
+
130
+ if active_only:
131
+ # When active_only is True, return only active records (Inactive == 0 or NULL)
132
+ query_str += " AND ([Inactive] = 0 OR [Inactive] IS NULL)"
133
+
134
+ if customer_type_id is not None:
135
+ query_str += " AND [CustomerTypeID] = :customer_type_id"
136
+ params["customer_type_id"] = customer_type_id
137
+
138
+ sql_query = text(query_str)
139
+ result = self.db.execute(sql_query, params)
140
+
141
+ company_types_data = []
142
+ if result.returns_rows:
143
+ for row in result.fetchall():
144
+ # Use field name-based mapping for proper structure
145
+ row_dict = dict(row._mapping)
146
+ company_type_data = {
147
+ 'company_type_id': row_dict.get('CompanyTypeID', None),
148
+ 'customer_type_id': row_dict.get('CustomerTypeID', None),
149
+ 'description': row_dict.get('Description', None),
150
+ 'inactive': row_dict.get('Inactive', False),
151
+ }
152
+ company_types_data.append(company_type_data)
153
+
154
+ self._cache[cache_key] = company_types_data
155
+ logger.info(f"Retrieved {len(company_types_data)} company types via direct query")
156
+ return company_types_data
157
+
158
+ except Exception as e:
159
+ logger.warning(f"Direct company types query failed: {e}", exc_info=True)
160
+ return []
161
 
162
  def get_lead_sources(self, active_only: bool = True, customer_type_id: Optional[int] = None) -> List[Dict[str, Any]]:
163
  """Get all lead generation sources, optionally filtered by customer_type_id"""
 
165
  if cache_key in self._cache:
166
  return self._cache[cache_key]
167
 
168
+ try:
169
+ # Use direct SQL query for better compatibility across Python versions
170
+ query_str = """
171
+ SELECT [LeadGeneratedFromID], [CustomerTypeID], [Description], [Enabled]
172
+ FROM [LeadGeneratedFroms]
173
+ WHERE 1=1
174
+ """
175
+ params = {}
176
+
177
+ if active_only:
178
+ query_str += " AND ([Enabled] = 1 OR [Enabled] IS NULL)"
179
+
180
+ if customer_type_id is not None:
181
+ query_str += " AND [CustomerTypeID] = :customer_type_id"
182
+ params["customer_type_id"] = customer_type_id
183
+
184
+
185
+ sql_query = text(query_str)
186
+ result = self.db.execute(sql_query, params)
187
+
188
+ lead_sources_data = []
189
+ if result.returns_rows:
190
+ for row in result.fetchall():
191
+ # Use field name-based mapping for proper structure
192
+ row_dict = dict(row._mapping)
193
+ lead_source_data = {
194
+ 'lead_generated_from_id': row_dict.get('LeadGeneratedFromID', None),
195
+ 'customer_type_id': row_dict.get('CustomerTypeID', None),
196
+ 'description': row_dict.get('Description', None),
197
+ 'enabled': row_dict.get('Enabled', True)
198
+ }
199
+ lead_sources_data.append(lead_source_data)
200
+
201
+ self._cache[cache_key] = lead_sources_data
202
+ logger.info(f"Retrieved {len(lead_sources_data)} lead sources via direct query")
203
+ return lead_sources_data
204
+
205
+ except Exception as e:
206
+ logger.warning(f"Direct lead sources query failed: {e}", exc_info=True)
207
+ return []
208
 
209
  def get_payment_terms(self, active_only: bool = True) -> List[Dict[str, Any]]:
210
  """Get all payment terms"""
 
428
  """Get all project statuses - Temporarily disabled due to model issues"""
429
  return [] # Return empty list for now
430
 
431
+ def get_all_reference_data(self, active_only: bool = True, customer_type_id: Optional[int] = None) -> Dict[str, List[Dict[str, Any]]]:
432
  """Get all reference data in a single call for efficiency"""
433
+ # Use customer_type_id = 1 as default if not provided (for backward compatibility)
434
+ default_customer_type_id = customer_type_id if customer_type_id is not None else 1
435
+
436
  return {
437
+ 'states': self.get_states(active_only, default_customer_type_id),
438
  'countries': self.get_countries(active_only),
439
+ 'company_types': self.get_company_types(active_only, default_customer_type_id),
440
+ 'lead_sources': self.get_lead_sources(active_only, default_customer_type_id),
441
  'payment_terms': self.get_payment_terms(active_only),
442
  'purchase_prices': self.get_purchase_prices(active_only),
443
  'rental_prices': self.get_rental_prices(active_only),
 
447
  'est_ship_dates': self.get_est_ship_dates(active_only),
448
  'est_freights': self.get_est_freights(active_only),
449
  'status_info': self.get_status_info(active_only),
450
+ 'priorities': self.get_priorities(active_only, default_customer_type_id),
451
  'phone_types': self.get_phone_types(active_only),
452
  }
453
 
 
485
  return []
486
 
487
  def get_priorities(self, active_only: bool = True, customer_type_id: Optional[int] = None) -> List[Dict[str, Any]]:
488
+ """Get all priorities, optionally filtered by customer_type_id"""
489
  cache_key = f"priorities_{active_only}_{customer_type_id}"
490
  if cache_key in self._cache:
491
  return self._cache[cache_key]
492
 
493
+ try:
494
+ # Use direct SQL query with proper parameter binding
495
+ query_str = """
496
  SELECT [PriorityID], [CustomerTypeID], [Description]
497
+ FROM [Priorities]
498
+ WHERE 1=1
499
+ """
500
+ params = {}
501
+
502
+ if customer_type_id is not None:
503
+ query_str += " AND [CustomerTypeID] = :customer_type_id"
504
+ params["customer_type_id"] = customer_type_id
505
+
506
+ sql_query = text(query_str)
507
+ result = self.db.execute(sql_query, params)
508
+
509
+ priorities_data = []
510
+ if result.returns_rows:
511
+ for row in result.fetchall():
512
+ # Use field name-based mapping for proper structure
513
+ row_dict = dict(row._mapping)
514
+ priority_data = {
515
+ 'priority_id': row_dict.get('PriorityID', None),
516
+ 'customer_type_id': row_dict.get('CustomerTypeID', None),
517
+ 'description': row_dict.get('Description', None)
518
+ }
519
+ priorities_data.append(priority_data)
520
+
521
+ self._cache[cache_key] = priorities_data
522
+ logger.info(f"Retrieved {len(priorities_data)} priorities via direct query")
523
+ return priorities_data
524
+
525
+ except Exception as e:
526
+ logger.warning(f"Direct priorities query failed: {e}", exc_info=True)
527
+ return []
528
 
529
  def get_phone_types(self, active_only: bool = False) -> List[Dict[str, Any]]:
530
  """Get all phone types"""
app/services/reference_service.py CHANGED
@@ -425,7 +425,7 @@ class ReferenceDataService:
425
  )
426
 
427
 
428
- def get_all_reference_data(self, active_only: bool = True) -> ReferenceDataResponse:
429
  """
430
  Get all reference data in a single response for efficiency.
431
  This is useful for frontend applications that need to populate
@@ -433,7 +433,7 @@ class ReferenceDataService:
433
  """
434
  try:
435
  # Get all data from repository
436
- all_data = self.repo.get_all_reference_data(active_only)
437
 
438
  # Transform to Pydantic models using keys provided by the repository.
439
  # The repository returns a dict with a subset of keys; map only those.
 
425
  )
426
 
427
 
428
+ def get_all_reference_data(self, active_only: bool = True, customer_type_id: Optional[int] = None) -> ReferenceDataResponse:
429
  """
430
  Get all reference data in a single response for efficiency.
431
  This is useful for frontend applications that need to populate
 
433
  """
434
  try:
435
  # Get all data from repository
436
+ all_data = self.repo.get_all_reference_data(active_only, customer_type_id)
437
 
438
  # Transform to Pydantic models using keys provided by the repository.
439
  # The repository returns a dict with a subset of keys; map only those.