MukeshKapoor25 commited on
Commit
43bf20b
·
1 Parent(s): 1aae6be

docs: update merchant type naming conventions to lowercase

Browse files

- Update NCNF merchant type references to use lowercase `ncnf` throughout documentation
- Standardize merchant code examples to reflect lowercase type prefixes
- Update API response examples to show correct lowercase merchant type values
- Correct enum references in error handling and testing documentation
- Update merchant type mapping and default values to use consistent lowercase naming
- Align all documentation with implemented lowercase merchant type constants
- Improve consistency across merchant type architecture, city code enhancement, and optional fields documentation

DEFAULT_MERCHANT_TYPES_ARCHITECTURE.md CHANGED
@@ -18,7 +18,7 @@ The `_get_default_merchant_types` functionality has been refactored from a singl
18
 
19
  **Default Merchant Types**:
20
  ```python
21
- NCNF = DefaultMerchantType(code="ncnf", label="NCNF", order=1)
22
  CNF = DefaultMerchantType(code="cnf", label="CNF", order=2)
23
  DISTRIBUTOR = DefaultMerchantType(code="distributor", label="Distributor", order=3)
24
  RETAIL = DefaultMerchantType(code="retail", label="Retail", order=4)
@@ -121,7 +121,7 @@ Authorization: Bearer <jwt_token>
121
  "success": true,
122
  "message": "Merchant types retrieved successfully",
123
  "data": [
124
- {"code": "ncnf", "label": "NCNF", "order": 1},
125
  {"code": "cnf", "label": "CNF", "order": 2},
126
  {"code": "distributor", "label": "Distributor", "order": 3},
127
  {"code": "retail", "label": "Retail", "order": 4}
@@ -152,7 +152,7 @@ Authorization: Bearer <jwt_token>
152
  "success": true,
153
  "message": "Merchant types list retrieved successfully",
154
  "data": [
155
- {"code": "ncnf", "label": "NCNF"},
156
  {"code": "cnf", "label": "CNF"},
157
  {"code": "distributor", "label": "Distributor"},
158
  {"code": "retail", "label": "Retail"}
 
18
 
19
  **Default Merchant Types**:
20
  ```python
21
+ ncnf = DefaultMerchantType(code="ncnf", label="ncnf", order=1)
22
  CNF = DefaultMerchantType(code="cnf", label="CNF", order=2)
23
  DISTRIBUTOR = DefaultMerchantType(code="distributor", label="Distributor", order=3)
24
  RETAIL = DefaultMerchantType(code="retail", label="Retail", order=4)
 
121
  "success": true,
122
  "message": "Merchant types retrieved successfully",
123
  "data": [
124
+ {"code": "ncnf", "label": "ncnf", "order": 1},
125
  {"code": "cnf", "label": "CNF", "order": 2},
126
  {"code": "distributor", "label": "Distributor", "order": 3},
127
  {"code": "retail", "label": "Retail", "order": 4}
 
152
  "success": true,
153
  "message": "Merchant types list retrieved successfully",
154
  "data": [
155
+ {"code": "ncnf", "label": "ncnf"},
156
  {"code": "cnf", "label": "CNF"},
157
  {"code": "distributor", "label": "Distributor"},
158
  {"code": "retail", "label": "Retail"}
MERCHANT_CITY_CODE_ENHANCEMENT_SUMMARY.md CHANGED
@@ -130,14 +130,14 @@ merchant_code: Optional[constr(pattern=MERCHANT_CODE_REGEX.pattern)] = Field(
130
  ## Merchant Code Examples
131
 
132
  ### By Merchant Type:
133
- - **National CNF**: `NCNF-MUM-0001`, `NCNF-DEL-0002`
134
  - **CNF**: `CNF-BLR-0001`, `CNF-CHN-0002`
135
  - **Distributor**: `DIS-HYD-0001`, `DIS-PUN-0002`
136
  - **retail/Retail**: `SAL-KOL-0001`, `SAL-AMD-0002`
137
 
138
  ### By City:
139
  - **Mumbai**: `SAL-MUM-0001`, `CNF-MUM-0001`, `DIS-MUM-0001`
140
- - **Delhi**: `NCNF-DEL-0001`, `CNF-DEL-0001`, `SAL-DEL-0001`
141
  - **Bangalore**: `DIS-BLR-0001`, `SAL-BLR-0001`
142
 
143
  ## API Usage Examples
@@ -199,7 +199,7 @@ merchant_code: Optional[constr(pattern=MERCHANT_CODE_REGEX.pattern)] = Field(
199
 
200
  ### Examples of Valid Codes:
201
  - ✅ `SAL-MUM-0001`
202
- - ✅ `NCNF-DEL-0025`
203
  - ✅ `DIS-BLR-1000`
204
  - ✅ `CNF-UNK-0001` (unknown city)
205
 
 
130
  ## Merchant Code Examples
131
 
132
  ### By Merchant Type:
133
+ - **National CNF**: `ncnf-MUM-0001`, `ncnf-DEL-0002`
134
  - **CNF**: `CNF-BLR-0001`, `CNF-CHN-0002`
135
  - **Distributor**: `DIS-HYD-0001`, `DIS-PUN-0002`
136
  - **retail/Retail**: `SAL-KOL-0001`, `SAL-AMD-0002`
137
 
138
  ### By City:
139
  - **Mumbai**: `SAL-MUM-0001`, `CNF-MUM-0001`, `DIS-MUM-0001`
140
+ - **Delhi**: `ncnf-DEL-0001`, `CNF-DEL-0001`, `SAL-DEL-0001`
141
  - **Bangalore**: `DIS-BLR-0001`, `SAL-BLR-0001`
142
 
143
  ## API Usage Examples
 
199
 
200
  ### Examples of Valid Codes:
201
  - ✅ `SAL-MUM-0001`
202
+ - ✅ `ncnf-DEL-0025`
203
  - ✅ `DIS-BLR-1000`
204
  - ✅ `CNF-UNK-0001` (unknown city)
205
 
MERCHANT_OPTIONAL_FIELDS_IMPLEMENTATION_SUMMARY.md CHANGED
@@ -85,7 +85,7 @@ else:
85
  - **Auto-generated**: System generates based on merchant type if not provided
86
  - **Format**: `{PREFIX}-{SEQUENCE}` (e.g., `SAL-0001`, `CNF-0025`)
87
  - **Prefixes**:
88
- - `NCNF` for NCNF
89
  - `CNF` for cnf
90
  - `DIS` for distributor
91
  - `SAL` for retail/retail
@@ -144,7 +144,7 @@ else:
144
  {
145
  "merchant_id": "123e4567-e89b-12d3-a456-426614174000",
146
  "merchant_code": "BOTH-001",
147
- "merchant_type": "NCNF",
148
  "merchant_name": "Custom National CNF",
149
  "location": "Test Location",
150
  "contact": { ... },
 
85
  - **Auto-generated**: System generates based on merchant type if not provided
86
  - **Format**: `{PREFIX}-{SEQUENCE}` (e.g., `SAL-0001`, `CNF-0025`)
87
  - **Prefixes**:
88
+ - `ncnf` for ncnf
89
  - `CNF` for cnf
90
  - `DIS` for distributor
91
  - `SAL` for retail/retail
 
144
  {
145
  "merchant_id": "123e4567-e89b-12d3-a456-426614174000",
146
  "merchant_code": "BOTH-001",
147
+ "merchant_type": "ncnf",
148
  "merchant_name": "Custom National CNF",
149
  "location": "Test Location",
150
  "contact": { ... },
MERCHANT_UPDATE_TESTING_COMPLETE.md CHANGED
@@ -27,8 +27,8 @@ Successfully tested and verified all merchant update functionality. All issues f
27
 
28
  ## Key Issues Fixed
29
 
30
- ### 1. AttributeError: NCNF
31
- - **Problem**: `MerchantType.NCNF` was not defined (should be `MerchantType.ncnf`)
32
  - **Solution**: Updated all enum references to use lowercase values
33
  - **Files**: `constants.py`, `models/model.py`, `services/service.py`
34
 
@@ -73,7 +73,7 @@ Successfully tested and verified all merchant update functionality. All issues f
73
  ```
74
  **Result**: ✅ Schema validation passes, auto-generates proper values
75
 
76
- ### Request 2: NCNF Merchant (Tiruvannamalai) ✅
77
  ```json
78
  {
79
  "merchant_type": "ncnf", // Enum works correctly
@@ -89,12 +89,12 @@ Successfully tested and verified all merchant update functionality. All issues f
89
  ## Auto-Generation Verified
90
 
91
  - **merchant_id**: UUID format (e.g., `10462379-1204-46d5-b429-57648daac3b1`)
92
- - **merchant_code**: `TYPE-CITY-SEQUENCE` (e.g., `RET-CHN-0001`, `NCNF-TIR-0001`)
93
  - **location**: From city (e.g., `"Chennai Branch"`, `"Tiruvannamalai Branch"`)
94
 
95
  ## Error Handling
96
 
97
- - **500 Internal Server Error**: Fixed AttributeError with MerchantType.NCNF
98
  - **422 Validation Error**: Fixed all field validation issues
99
  - **Missing Fields**: Auto-generation handles missing required fields
100
  - **Invalid Formats**: Lenient validation with auto-correction
@@ -120,7 +120,7 @@ The merchant API is now ready for production use. All user-reported errors have
120
  1. ✅ Empty geo_location handling
121
  2. ✅ Invalid merchant_code handling
122
  3. ✅ Missing location field handling
123
- 4. ✅ MerchantType.NCNF enum error fixed
124
  5. ✅ Lenient KYC validation for retail merchants
125
  6. ✅ Auto-generation of all required fields
126
 
 
27
 
28
  ## Key Issues Fixed
29
 
30
+ ### 1. AttributeError: ncnf
31
+ - **Problem**: `MerchantType.ncnf` was not defined (should be `MerchantType.ncnf`)
32
  - **Solution**: Updated all enum references to use lowercase values
33
  - **Files**: `constants.py`, `models/model.py`, `services/service.py`
34
 
 
73
  ```
74
  **Result**: ✅ Schema validation passes, auto-generates proper values
75
 
76
+ ### Request 2: ncnf Merchant (Tiruvannamalai) ✅
77
  ```json
78
  {
79
  "merchant_type": "ncnf", // Enum works correctly
 
89
  ## Auto-Generation Verified
90
 
91
  - **merchant_id**: UUID format (e.g., `10462379-1204-46d5-b429-57648daac3b1`)
92
+ - **merchant_code**: `TYPE-CITY-SEQUENCE` (e.g., `RET-CHN-0001`, `ncnf-TIR-0001`)
93
  - **location**: From city (e.g., `"Chennai Branch"`, `"Tiruvannamalai Branch"`)
94
 
95
  ## Error Handling
96
 
97
+ - **500 Internal Server Error**: Fixed AttributeError with MerchantType.ncnf
98
  - **422 Validation Error**: Fixed all field validation issues
99
  - **Missing Fields**: Auto-generation handles missing required fields
100
  - **Invalid Formats**: Lenient validation with auto-correction
 
120
  1. ✅ Empty geo_location handling
121
  2. ✅ Invalid merchant_code handling
122
  3. ✅ Missing location field handling
123
+ 4. ✅ MerchantType.ncnf enum error fixed
124
  5. ✅ Lenient KYC validation for retail merchants
125
  6. ✅ Auto-generation of all required fields
126
 
app/merchants/controllers/router.py CHANGED
@@ -53,8 +53,8 @@ async def create_merchant(
53
  Create a new merchant.
54
 
55
  **Business Rules:**
56
- - `NCNF`: No parent required, full KYC mandatory
57
- - `cnf`: Parent must be NCNF, full KYC mandatory
58
  - `distributor`: Parent must be cnf, full KYC mandatory
59
  - `retail`: Parent must be distributor, geo_location required, PAN mandatory
60
 
@@ -161,7 +161,7 @@ async def list_merchants(
161
  List merchants with filters and optional field projection.
162
 
163
  **Request Body:**
164
- - `merchant_type`: Filter by type (NCNF, cnf, distributor, retail)
165
  - `merchant_types`: Filter by multiple merchant types
166
  - `can_transact_with`: Filter by merchants that can transact with this merchant ID
167
  - `can_transact_in`: Filter by merchants that can transact in this location code
 
53
  Create a new merchant.
54
 
55
  **Business Rules:**
56
+ - `ncnf`: No parent required, full KYC mandatory
57
+ - `cnf`: Parent must be ncnf, full KYC mandatory
58
  - `distributor`: Parent must be cnf, full KYC mandatory
59
  - `retail`: Parent must be distributor, geo_location required, PAN mandatory
60
 
 
161
  List merchants with filters and optional field projection.
162
 
163
  **Request Body:**
164
+ - `merchant_type`: Filter by type (ncnf, cnf, distributor, retail)
165
  - `merchant_types`: Filter by multiple merchant types
166
  - `can_transact_with`: Filter by merchants that can transact with this merchant ID
167
  - `can_transact_in`: Filter by merchants that can transact in this location code
app/merchants/services/service.py CHANGED
@@ -34,7 +34,7 @@ MERCHANT_SEQ_MAP = {
34
  }
35
 
36
  MERCHANT_CODE_PREFIX = {
37
- "ncnf": "NCNF",
38
  "cnf": "CNF",
39
  "distributor": "DIS",
40
  "retail": "RET"
 
34
  }
35
 
36
  MERCHANT_CODE_PREFIX = {
37
+ "ncnf": "ncnf",
38
  "cnf": "CNF",
39
  "distributor": "DIS",
40
  "retail": "RET"
app/system_users/models/model.py CHANGED
@@ -24,7 +24,7 @@ class SystemUserModel(BaseModel):
24
  full_name: Optional[str] = Field(None, description="Full name of the user")
25
  role_id: Optional[str] = Field(None, description="Role identifier e.g. role_distributor_manager")
26
  merchant_id: Optional[str] = Field(None, description="Owning merchant id")
27
- merchant_type: Optional[str] = Field(None, description="Merchant type (NCNF, cnf, distributor, retail)")
28
  status: UserStatus = Field(default=UserStatus.ACTIVE, description="Account status")
29
  last_login: Optional[datetime] = Field(None, description="Timestamp of last successful login")
30
  created_by: str = Field(..., description="User id who created this record")
 
24
  full_name: Optional[str] = Field(None, description="Full name of the user")
25
  role_id: Optional[str] = Field(None, description="Role identifier e.g. role_distributor_manager")
26
  merchant_id: Optional[str] = Field(None, description="Owning merchant id")
27
+ merchant_type: Optional[str] = Field(None, description="Merchant type (ncnf, cnf, distributor, retail)")
28
  status: UserStatus = Field(default=UserStatus.ACTIVE, description="Account status")
29
  last_login: Optional[datetime] = Field(None, description="Timestamp of last successful login")
30
  created_by: str = Field(..., description="User id who created this record")
app/system_users/schemas/schema.py CHANGED
@@ -44,7 +44,7 @@ class CreateUserRequest(BaseModel):
44
  username: str = Field(..., description="Unique username", min_length=3, max_length=50)
45
  email: EmailStr = Field(..., description="Email address")
46
  merchant_id: str = Field(..., description="Merchant identifier")
47
- merchant_type: Optional[str] = Field(None, description="Merchant type (NCNF, cnf, distributor, retail)")
48
  password: str = Field(..., description="Password", min_length=8, max_length=100)
49
  full_name: str = Field(..., description="Full name", min_length=1, max_length=100)
50
  role_id: str = Field(..., description="Role identifier")
@@ -78,7 +78,7 @@ class UpdateUserRequest(BaseModel):
78
  full_name: Optional[str] = Field(None, description="Full name", min_length=1, max_length=100)
79
  role_id: Optional[str] = Field(None, description="Role identifier")
80
  merchant_id: Optional[str] = Field(None, description="Merchant identifier")
81
- merchant_type: Optional[str] = Field(None, description="Merchant type (NCNF, cnf, distributor, retail)")
82
  status: Optional[UserStatus] = Field(None, description="Account status")
83
  metadata: Optional[Dict[str, str]] = Field(None, description="Optional metadata")
84
 
 
44
  username: str = Field(..., description="Unique username", min_length=3, max_length=50)
45
  email: EmailStr = Field(..., description="Email address")
46
  merchant_id: str = Field(..., description="Merchant identifier")
47
+ merchant_type: Optional[str] = Field(None, description="Merchant type (ncnf, cnf, distributor, retail)")
48
  password: str = Field(..., description="Password", min_length=8, max_length=100)
49
  full_name: str = Field(..., description="Full name", min_length=1, max_length=100)
50
  role_id: str = Field(..., description="Role identifier")
 
78
  full_name: Optional[str] = Field(None, description="Full name", min_length=1, max_length=100)
79
  role_id: Optional[str] = Field(None, description="Role identifier")
80
  merchant_id: Optional[str] = Field(None, description="Merchant identifier")
81
+ merchant_type: Optional[str] = Field(None, description="Merchant type (ncnf, cnf, distributor, retail)")
82
  status: Optional[UserStatus] = Field(None, description="Account status")
83
  metadata: Optional[Dict[str, str]] = Field(None, description="Optional metadata")
84
 
app/taxonomy/controllers/router.py CHANGED
@@ -815,7 +815,7 @@ async def get_merchant_types(
815
  **Returns**: Merchant types data with metadata
816
 
817
  **Default Merchant Types**: If no custom merchant types exist, returns:
818
- - ncnf (NCNF) - order 1
819
  - cnf - order 2
820
  - distributor - order 3
821
  - retail - order 4
 
815
  **Returns**: Merchant types data with metadata
816
 
817
  **Default Merchant Types**: If no custom merchant types exist, returns:
818
+ - ncnf (ncnf) - order 1
819
  - cnf - order 2
820
  - distributor - order 3
821
  - retail - order 4
app/taxonomy/models/defaults.py CHANGED
@@ -35,11 +35,11 @@ class DefaultMerchantTypes:
35
  """Centralized default merchant types configuration."""
36
 
37
  # Define default merchant types with proper ordering and allowed levels
38
- NCNF = DefaultMerchantType(
39
  code="ncnf",
40
- label="NCNF",
41
  order=1,
42
- description="NCNF - Top level distributor",
43
  allowed_levels=["ncnf", "cnf", "distributor", "retail"]
44
  )
45
 
@@ -69,7 +69,7 @@ class DefaultMerchantTypes:
69
 
70
  # Legacy mappings for backward compatibility
71
  LEGACY_MAPPINGS = {
72
- "NCNF": "ncnf",
73
  "retail": "retail",
74
  "company": "retail" # Company is treated as retail level
75
  }
@@ -77,7 +77,7 @@ class DefaultMerchantTypes:
77
  @classmethod
78
  def get_all(cls) -> List[DefaultMerchantType]:
79
  """Get all default merchant types in order."""
80
- return [cls.NCNF, cls.CNF, cls.DISTRIBUTOR, cls.RETAIL]
81
 
82
  @classmethod
83
  def get_all_as_dict(cls) -> List[Dict[str, Any]]:
@@ -88,7 +88,7 @@ class DefaultMerchantTypes:
88
  def get_by_code(cls, code: str) -> DefaultMerchantType:
89
  """Get default merchant type by code."""
90
  code_map = {
91
- "ncnf": cls.NCNF,
92
  "cnf": cls.CNF,
93
  "distributor": cls.DISTRIBUTOR,
94
  "retail": cls.RETAIL
 
35
  """Centralized default merchant types configuration."""
36
 
37
  # Define default merchant types with proper ordering and allowed levels
38
+ ncnf = DefaultMerchantType(
39
  code="ncnf",
40
+ label="ncnf",
41
  order=1,
42
+ description="ncnf - Top level distributor",
43
  allowed_levels=["ncnf", "cnf", "distributor", "retail"]
44
  )
45
 
 
69
 
70
  # Legacy mappings for backward compatibility
71
  LEGACY_MAPPINGS = {
72
+ "ncnf": "ncnf",
73
  "retail": "retail",
74
  "company": "retail" # Company is treated as retail level
75
  }
 
77
  @classmethod
78
  def get_all(cls) -> List[DefaultMerchantType]:
79
  """Get all default merchant types in order."""
80
+ return [cls.ncnf, cls.CNF, cls.DISTRIBUTOR, cls.RETAIL]
81
 
82
  @classmethod
83
  def get_all_as_dict(cls) -> List[Dict[str, Any]]:
 
88
  def get_by_code(cls, code: str) -> DefaultMerchantType:
89
  """Get default merchant type by code."""
90
  code_map = {
91
+ "ncnf": cls.ncnf,
92
  "cnf": cls.CNF,
93
  "distributor": cls.DISTRIBUTOR,
94
  "retail": cls.RETAIL
app/taxonomy/utils/defaults.py CHANGED
@@ -32,7 +32,7 @@ def get_default_merchant_type_labels() -> List[str]:
32
  Get list of default merchant type labels.
33
 
34
  Returns:
35
- List of merchant type labels: ["NCNF", "CNF", "Distributor", "Retail"]
36
  """
37
  return DefaultMerchantTypes.get_labels()
38
 
@@ -118,7 +118,7 @@ def normalize_merchant_type(merchant_type: str) -> str:
118
  Normalize legacy merchant type codes to current standards.
119
 
120
  Args:
121
- merchant_type: The merchant type code (may be legacy like "retail", "NCNF")
122
 
123
  Returns:
124
  Normalized merchant type code
 
32
  Get list of default merchant type labels.
33
 
34
  Returns:
35
+ List of merchant type labels: ["ncnf", "CNF", "Distributor", "Retail"]
36
  """
37
  return DefaultMerchantTypes.get_labels()
38
 
 
118
  Normalize legacy merchant type codes to current standards.
119
 
120
  Args:
121
+ merchant_type: The merchant type code (may be legacy like "retail", "ncnf")
122
 
123
  Returns:
124
  Normalized merchant type code
app/trade_sales/services/service.py CHANGED
@@ -89,7 +89,7 @@ class TradeSalesService:
89
  LEFT JOIN trans.scm_po_item poi ON po.po_id = poi.po_id
90
  LEFT JOIN trans.scm_trade_shipment_item tsi ON poi.po_item_id = tsi.po_item_id
91
  LEFT JOIN trans.merchants_ref mr ON po.buyer_id = mr.merchant_id
92
- WHERE po.supplier_type IN ('cnf', 'ncnf', 'NCNF', 'cnf')
93
  AND po.buyer_type IN ('distributor', 'distributor', 'cnf', 'cnf')
94
  """
95
 
@@ -297,7 +297,7 @@ class TradeSalesService:
297
  LEFT JOIN trans.scm_po_item poi ON po.po_id = poi.po_id
298
  LEFT JOIN trans.scm_trade_shipment_item tsi ON poi.po_item_id = tsi.po_item_id
299
  LEFT JOIN trans.scm_trade_shipment ts ON tsi.shipment_id = ts.shipment_id
300
- WHERE po.supplier_type IN ('cnf', 'ncnf', 'NCNF', 'cnf')
301
  AND po.buyer_type IN ('distributor', 'distributor', 'cnf', 'cnf')
302
  """
303
 
 
89
  LEFT JOIN trans.scm_po_item poi ON po.po_id = poi.po_id
90
  LEFT JOIN trans.scm_trade_shipment_item tsi ON poi.po_item_id = tsi.po_item_id
91
  LEFT JOIN trans.merchants_ref mr ON po.buyer_id = mr.merchant_id
92
+ WHERE po.supplier_type IN ('cnf', 'ncnf', 'ncnf', 'cnf')
93
  AND po.buyer_type IN ('distributor', 'distributor', 'cnf', 'cnf')
94
  """
95
 
 
297
  LEFT JOIN trans.scm_po_item poi ON po.po_id = poi.po_id
298
  LEFT JOIN trans.scm_trade_shipment_item tsi ON poi.po_item_id = tsi.po_item_id
299
  LEFT JOIN trans.scm_trade_shipment ts ON tsi.shipment_id = ts.shipment_id
300
+ WHERE po.supplier_type IN ('cnf', 'ncnf', 'ncnf', 'cnf')
301
  AND po.buyer_type IN ('distributor', 'distributor', 'cnf', 'cnf')
302
  """
303
 
app/warehouses/README.md CHANGED
@@ -93,7 +93,7 @@ Create a new warehouse with mandatory address, contact, and capability informati
93
 
94
  **Validations:**
95
  - warehouse_type: MAIN, SECONDARY, STORE, TEMP
96
- - merchant_type: NCNF, cnf, distributor, retail
97
  - warehouse_code must be unique per merchant
98
  - All required address fields must be provided
99
  - Geo coordinates must be within valid ranges
@@ -355,7 +355,7 @@ All endpoints return appropriate HTTP status codes:
355
  - Case-insensitive (converted to uppercase)
356
 
357
  **Merchant Type:**
358
- - Must be one of: NCNF, cnf, distributor, retail
359
  - Case-insensitive (converted to lowercase)
360
 
361
  **Geo Coordinates:**
 
93
 
94
  **Validations:**
95
  - warehouse_type: MAIN, SECONDARY, STORE, TEMP
96
+ - merchant_type: ncnf, cnf, distributor, retail
97
  - warehouse_code must be unique per merchant
98
  - All required address fields must be provided
99
  - Geo coordinates must be within valid ranges
 
355
  - Case-insensitive (converted to uppercase)
356
 
357
  **Merchant Type:**
358
+ - Must be one of: ncnf, cnf, distributor, retail
359
  - Case-insensitive (converted to lowercase)
360
 
361
  **Geo Coordinates:**
app/warehouses/models/model.py CHANGED
@@ -88,7 +88,7 @@ class WarehouseModel(BaseModel):
88
  """
89
  warehouse_id: str = Field(..., description="Unique warehouse identifier (UUID)")
90
  merchant_id: str = Field(..., description="Merchant ID who owns this warehouse")
91
- merchant_type: str = Field(..., description="Type of merchant (NCNF, cnf, distributor, retail)")
92
 
93
  warehouse_code: str = Field(..., description="Unique warehouse code (e.g., WH-DIST-0001)")
94
  warehouse_name: str = Field(..., description="Warehouse display name")
 
88
  """
89
  warehouse_id: str = Field(..., description="Unique warehouse identifier (UUID)")
90
  merchant_id: str = Field(..., description="Merchant ID who owns this warehouse")
91
+ merchant_type: str = Field(..., description="Type of merchant (ncnf, cnf, distributor, retail)")
92
 
93
  warehouse_code: str = Field(..., description="Unique warehouse code (e.g., WH-DIST-0001)")
94
  warehouse_name: str = Field(..., description="Warehouse display name")
app/warehouses/schemas/schema.py CHANGED
@@ -43,7 +43,7 @@ class WarehouseCreate(BaseModel):
43
  @field_validator("merchant_type")
44
  def validate_merchant_type(cls, v):
45
  """Validate merchant type"""
46
- valid_types = {"NCNF", "cnf", "distributor", "retail"}
47
  if v.lower() not in valid_types:
48
  raise ValueError(f"merchant_type must be one of: {valid_types}")
49
  return v.lower()
 
43
  @field_validator("merchant_type")
44
  def validate_merchant_type(cls, v):
45
  """Validate merchant type"""
46
+ valid_types = {"ncnf", "cnf", "distributor", "retail"}
47
  if v.lower() not in valid_types:
48
  raise ValueError(f"merchant_type must be one of: {valid_types}")
49
  return v.lower()
docs/api-guides/MERCHANT_KYC_WORKFLOW.md CHANGED
@@ -68,7 +68,7 @@ http://localhost:8001/api/v1
68
  **Requirements:**
69
  - Merchant must be in `draft` status
70
  - Required KYC fields must be completed based on merchant type:
71
- - **NCNF, cnf, distributor:** GST, PAN, bank_account_number, bank_ifsc
72
  - **retail:** PAN (minimum)
73
 
74
  **Status Transition:**
@@ -246,7 +246,7 @@ draft/submitted → rejected
246
 
247
  ## KYC Requirements by Merchant Type
248
 
249
- ### NCNF / cnf / distributor
250
  **Required Fields:**
251
  - `gst_number`
252
  - `pan_number`
 
68
  **Requirements:**
69
  - Merchant must be in `draft` status
70
  - Required KYC fields must be completed based on merchant type:
71
+ - **ncnf, cnf, distributor:** GST, PAN, bank_account_number, bank_ifsc
72
  - **retail:** PAN (minimum)
73
 
74
  **Status Transition:**
 
246
 
247
  ## KYC Requirements by Merchant Type
248
 
249
+ ### ncnf / cnf / distributor
250
  **Required Fields:**
251
  - `gst_number`
252
  - `pan_number`
docs/examples/po_grn_rma_workflow.py CHANGED
@@ -18,7 +18,7 @@ async def complete_workflow():
18
  print("="*80)
19
 
20
  # Assume we have existing merchants from previous setup
21
- # NCNF → cnf → distributor → retail
22
 
23
  # Step 1: retail creates PO to distributor
24
  print("\n1. retail CREATES PURCHASE ORDER TO distributor")
 
18
  print("="*80)
19
 
20
  # Assume we have existing merchants from previous setup
21
+ # ncnf → cnf → distributor → retail
22
 
23
  # Step 1: retail creates PO to distributor
24
  print("\n1. retail CREATES PURCHASE ORDER TO distributor")
docs/examples/quick_start.py CHANGED
@@ -12,10 +12,10 @@ async def create_sample_merchants():
12
  """Create a complete merchant hierarchy for testing"""
13
 
14
  async with httpx.AsyncClient() as client:
15
- # 1. Create NCNF (Root)
16
- print("Creating NCNF...")
17
- NCNF = {
18
- "merchant_type": "NCNF",
19
  "merchant_code": "ncnf-INDIA-001",
20
  "merchant_name": "National Distribution Center India",
21
  "contact": {
@@ -46,12 +46,12 @@ async def create_sample_merchants():
46
  "created_by": "system_admin"
47
  }
48
 
49
- response = await client.post(f"{BASE_URL}/merchants", json=NCNF)
50
  if response.status_code == 201:
51
  ncnf_data = response.json()
52
- print(f"✓ Created NCNF: {ncnf_data['merchant_id']}")
53
 
54
- # 2. Create cnf under NCNF
55
  print("\nCreating cnf...")
56
  cnf = {
57
  "merchant_type": "cnf",
@@ -167,7 +167,7 @@ async def create_sample_merchants():
167
  print("\n" + "="*60)
168
  print("MERCHANT HIERARCHY CREATED SUCCESSFULLY")
169
  print("="*60)
170
- print(f"NCNF: {ncnf_data['merchant_id']}")
171
  print(f" └── cnf: {cnf_data['merchant_id']}")
172
  print(f" └── distributor: {dist_data['merchant_id']}")
173
  print(f" └── retail: {retail_data['merchant_id']}")
@@ -190,7 +190,7 @@ async def create_sample_merchants():
190
  else:
191
  print(f"✗ Failed to create cnf: {response.text}")
192
  else:
193
- print(f"✗ Failed to create NCNF: {response.text}")
194
 
195
  return False
196
 
 
12
  """Create a complete merchant hierarchy for testing"""
13
 
14
  async with httpx.AsyncClient() as client:
15
+ # 1. Create ncnf (Root)
16
+ print("Creating ncnf...")
17
+ ncnf = {
18
+ "merchant_type": "ncnf",
19
  "merchant_code": "ncnf-INDIA-001",
20
  "merchant_name": "National Distribution Center India",
21
  "contact": {
 
46
  "created_by": "system_admin"
47
  }
48
 
49
+ response = await client.post(f"{BASE_URL}/merchants", json=ncnf)
50
  if response.status_code == 201:
51
  ncnf_data = response.json()
52
+ print(f"✓ Created ncnf: {ncnf_data['merchant_id']}")
53
 
54
+ # 2. Create cnf under ncnf
55
  print("\nCreating cnf...")
56
  cnf = {
57
  "merchant_type": "cnf",
 
167
  print("\n" + "="*60)
168
  print("MERCHANT HIERARCHY CREATED SUCCESSFULLY")
169
  print("="*60)
170
+ print(f"ncnf: {ncnf_data['merchant_id']}")
171
  print(f" └── cnf: {cnf_data['merchant_id']}")
172
  print(f" └── distributor: {dist_data['merchant_id']}")
173
  print(f" └── retail: {retail_data['merchant_id']}")
 
190
  else:
191
  print(f"✗ Failed to create cnf: {response.text}")
192
  else:
193
+ print(f"✗ Failed to create ncnf: {response.text}")
194
 
195
  return False
196
 
docs/implementation-summaries/IMPLEMENTATION_SUMMARY.md CHANGED
@@ -232,4 +232,4 @@ Successfully implemented complete Purchase Order, GRN, and RMA modules with:
232
  - **Full audit trail** and traceability
233
  - **Production-ready** code with error handling
234
 
235
- All modules follow the specification requirements for the Company → NCNF → cnf → distributor → retail hierarchy with proper validation, tracking, and integration points.
 
232
  - **Full audit trail** and traceability
233
  - **Production-ready** code with error handling
234
 
235
+ All modules follow the specification requirements for the Company → ncnf → cnf → distributor → retail hierarchy with proper validation, tracking, and integration points.
docs/implementation-summaries/MERCHANT_API_SUMMARY.md CHANGED
@@ -36,7 +36,7 @@
36
  {
37
  "_id": {"$oid": "6929fd805b6e93f6f31a6ef7"},
38
  "merchant_id": "mch_Tl6fTpA4Bbzv7rAFLyDwgA",
39
- "merchant_type": "NCNF",
40
  "parent_merchant_id": null,
41
  "merchant_code": "ncnf-INDIA-001",
42
  "merchant_name": "National Distribution Center India",
 
36
  {
37
  "_id": {"$oid": "6929fd805b6e93f6f31a6ef7"},
38
  "merchant_id": "mch_Tl6fTpA4Bbzv7rAFLyDwgA",
39
+ "merchant_type": "ncnf",
40
  "parent_merchant_id": null,
41
  "merchant_code": "ncnf-INDIA-001",
42
  "merchant_name": "National Distribution Center India",
test_merchant_api_endpoints.py CHANGED
@@ -53,7 +53,7 @@ test_requests = [
53
  }
54
  },
55
  {
56
- "name": "Create NCNF Merchant (Tiruvannamalai)",
57
  "method": "POST",
58
  "endpoint": "/merchants",
59
  "payload": {
@@ -222,7 +222,7 @@ def test_enum_compatibility():
222
  # Test the specific error case from the logs
223
  print("1. Testing MerchantType.ncnf access...")
224
 
225
- # This should work now (was failing before with MerchantType.NCNF)
226
  merchant_type = MerchantType.ncnf
227
  print(f" ✅ MerchantType.ncnf = '{merchant_type}'")
228
 
@@ -237,8 +237,8 @@ def test_enum_compatibility():
237
  test_data = {
238
  "merchant_id": "550e8400-e29b-41d4-a716-446655440000", # Proper UUID format
239
  "merchant_type": "ncnf",
240
- "merchant_code": "NCNF-CHN-0001",
241
- "merchant_name": "Test NCNF",
242
  "contact": {
243
  "phone": "+919876543210",
244
  "email": "test@example.com",
@@ -266,7 +266,7 @@ def test_enum_compatibility():
266
  "metadata": {}
267
  }
268
 
269
- # This was failing before with AttributeError: NCNF
270
  merchant_model = MerchantModel(**test_data)
271
  print(f" ✅ MerchantModel creation with ncnf type works")
272
 
@@ -300,7 +300,7 @@ def main():
300
  print(" • merchant_code auto-generation with city codes")
301
  print(" • merchant_id UUID auto-generation")
302
  print(" • location auto-generation from city")
303
- print(" • MerchantType.ncnf enum access (was MerchantType.NCNF)")
304
  print(" • Lenient KYC validation for retail merchants")
305
  print(" • Empty geo_location handling")
306
  print(" • Backward compatibility for legacy merchant types")
 
53
  }
54
  },
55
  {
56
+ "name": "Create ncnf Merchant (Tiruvannamalai)",
57
  "method": "POST",
58
  "endpoint": "/merchants",
59
  "payload": {
 
222
  # Test the specific error case from the logs
223
  print("1. Testing MerchantType.ncnf access...")
224
 
225
+ # This should work now (was failing before with MerchantType.ncnf)
226
  merchant_type = MerchantType.ncnf
227
  print(f" ✅ MerchantType.ncnf = '{merchant_type}'")
228
 
 
237
  test_data = {
238
  "merchant_id": "550e8400-e29b-41d4-a716-446655440000", # Proper UUID format
239
  "merchant_type": "ncnf",
240
+ "merchant_code": "ncnf-CHN-0001",
241
+ "merchant_name": "Test ncnf",
242
  "contact": {
243
  "phone": "+919876543210",
244
  "email": "test@example.com",
 
266
  "metadata": {}
267
  }
268
 
269
+ # This was failing before with AttributeError: ncnf
270
  merchant_model = MerchantModel(**test_data)
271
  print(f" ✅ MerchantModel creation with ncnf type works")
272
 
 
300
  print(" • merchant_code auto-generation with city codes")
301
  print(" • merchant_id UUID auto-generation")
302
  print(" • location auto-generation from city")
303
+ print(" • MerchantType.ncnf enum access (was MerchantType.ncnf)")
304
  print(" • Lenient KYC validation for retail merchants")
305
  print(" • Empty geo_location handling")
306
  print(" • Backward compatibility for legacy merchant types")
test_merchant_optional_fields.py CHANGED
@@ -66,7 +66,7 @@ async def test_merchant_optional_fields():
66
  print("\n3️⃣ Testing merchant_code generation with city...")
67
 
68
  test_codes = [
69
- ("ncnf", "Mumbai", 1, "NCNF-MUM-0001"),
70
  ("cnf", "Delhi", 25, "CNF-DEL-0025"),
71
  ("distributor", "Bangalore", 100, "DIS-BLR-0100"),
72
  ("retail", "Chennai", 5, "SAL-CHN-0005")
@@ -147,8 +147,8 @@ async def test_merchant_optional_fields():
147
  test_uuid = str(uuid.uuid4())
148
  merchant_data = MerchantCreate(
149
  merchant_id=test_uuid,
150
- merchant_code="NCNF-PUN-0001",
151
- merchant_type=MerchantType.NCNF,
152
  merchant_name="Test National CNF",
153
  location="Test Location",
154
  contact=contact,
 
66
  print("\n3️⃣ Testing merchant_code generation with city...")
67
 
68
  test_codes = [
69
+ ("ncnf", "Mumbai", 1, "ncnf-MUM-0001"),
70
  ("cnf", "Delhi", 25, "CNF-DEL-0025"),
71
  ("distributor", "Bangalore", 100, "DIS-BLR-0100"),
72
  ("retail", "Chennai", 5, "SAL-CHN-0005")
 
147
  test_uuid = str(uuid.uuid4())
148
  merchant_data = MerchantCreate(
149
  merchant_id=test_uuid,
150
+ merchant_code="ncnf-PUN-0001",
151
+ merchant_type=MerchantType.ncnf,
152
  merchant_name="Test National CNF",
153
  location="Test Location",
154
  contact=contact,
test_merchant_type_mapping_refactor.py CHANGED
@@ -34,7 +34,7 @@ def test_default_merchant_types():
34
  # Test legacy mappings
35
  print("\n🔄 Legacy mapping tests:")
36
  legacy_tests = [
37
- ("NCNF", "ncnf"),
38
  ("retail", "retail"),
39
  ("company", "retail")
40
  ]
@@ -71,7 +71,7 @@ def test_utility_functions():
71
 
72
  # Test merchant type level resolution
73
  print("\n📋 Merchant type level resolution:")
74
- test_merchant_types = ["ncnf", "cnf", "distributor", "retail", "retail", "NCNF", "company"]
75
 
76
  for mt in test_merchant_types:
77
  normalized = normalize_merchant_type(mt)
@@ -100,7 +100,7 @@ def test_catalogue_utils_integration():
100
  ("distributor", "merchant_789"),
101
  ("retail", "merchant_abc"),
102
  ("retail", "merchant_def"), # Legacy mapping
103
- ("NCNF", "merchant_ghi"), # Legacy mapping
104
  ("company", "merchant_jkl") # Legacy mapping
105
  ]
106
 
@@ -135,7 +135,7 @@ def test_no_hardcoded_mappings():
135
  # Check for old hardcoded patterns
136
  hardcoded_patterns = [
137
  'merchant_type_mapping = {',
138
- '"NCNF": {"ncnf"',
139
  '"retail": {"retail"}',
140
  '"company": {"company"'
141
  ]
@@ -170,7 +170,7 @@ def test_hierarchy_logic():
170
 
171
  from app.taxonomy.utils.defaults import get_allowed_levels_for_merchant_type
172
 
173
- # Test hierarchy: NCNF can access all levels, retail can only access retail
174
  hierarchy_tests = [
175
  ("ncnf", ["ncnf", "cnf", "distributor", "retail"]),
176
  ("cnf", ["cnf", "distributor", "retail"]),
 
34
  # Test legacy mappings
35
  print("\n🔄 Legacy mapping tests:")
36
  legacy_tests = [
37
+ ("ncnf", "ncnf"),
38
  ("retail", "retail"),
39
  ("company", "retail")
40
  ]
 
71
 
72
  # Test merchant type level resolution
73
  print("\n📋 Merchant type level resolution:")
74
+ test_merchant_types = ["ncnf", "cnf", "distributor", "retail", "retail", "ncnf", "company"]
75
 
76
  for mt in test_merchant_types:
77
  normalized = normalize_merchant_type(mt)
 
100
  ("distributor", "merchant_789"),
101
  ("retail", "merchant_abc"),
102
  ("retail", "merchant_def"), # Legacy mapping
103
+ ("ncnf", "merchant_ghi"), # Legacy mapping
104
  ("company", "merchant_jkl") # Legacy mapping
105
  ]
106
 
 
135
  # Check for old hardcoded patterns
136
  hardcoded_patterns = [
137
  'merchant_type_mapping = {',
138
+ '"ncnf": {"ncnf"',
139
  '"retail": {"retail"}',
140
  '"company": {"company"'
141
  ]
 
170
 
171
  from app.taxonomy.utils.defaults import get_allowed_levels_for_merchant_type
172
 
173
+ # Test hierarchy: ncnf can access all levels, retail can only access retail
174
  hierarchy_tests = [
175
  ("ncnf", ["ncnf", "cnf", "distributor", "retail"]),
176
  ("cnf", ["cnf", "distributor", "retail"]),
tests/test_merchant_list_filtering.py CHANGED
@@ -93,7 +93,7 @@ async def test_merchant_filtering():
93
  try:
94
  # First get a merchant that has children
95
  all_merchants = await MerchantService.list_merchants()
96
- parent_merchants = [m for m in all_merchants if m.merchant_type in [MerchantType.NCNF, MerchantType.cnf, MerchantType.distributor]]
97
 
98
  if parent_merchants:
99
  test_parent = parent_merchants[0]
 
93
  try:
94
  # First get a merchant that has children
95
  all_merchants = await MerchantService.list_merchants()
96
+ parent_merchants = [m for m in all_merchants if m.merchant_type in [MerchantType.ncnf, MerchantType.cnf, MerchantType.distributor]]
97
 
98
  if parent_merchants:
99
  test_parent = parent_merchants[0]
tests/test_merchant_schemas.py CHANGED
@@ -252,14 +252,14 @@ class TestMerchantCreate:
252
  )
253
  assert "retail requires pan_number" in str(exc_info.value)
254
 
255
- def test_NCNF_no_parent(self):
256
- """Test NCNF must not have parent"""
257
  with pytest.raises(ValidationError) as exc_info:
258
  MerchantCreate(
259
- merchant_type=MerchantType.NCNF,
260
  parent_merchant_id="SOME_PARENT", # Should not have parent
261
  merchant_code="ncnf-001",
262
- merchant_name="NCNF",
263
  contact=ContactModel(
264
  phone="+919876543210",
265
  email="ncnf@test.com",
@@ -277,7 +277,7 @@ class TestMerchantCreate:
277
  ),
278
  created_by="admin_001"
279
  )
280
- assert "NCNF must not have a parent_merchant_id" in str(exc_info.value)
281
 
282
  def test_cnf_requires_full_kyc(self):
283
  """Test cnf requires complete KYC"""
 
252
  )
253
  assert "retail requires pan_number" in str(exc_info.value)
254
 
255
+ def test_ncnf_no_parent(self):
256
+ """Test ncnf must not have parent"""
257
  with pytest.raises(ValidationError) as exc_info:
258
  MerchantCreate(
259
+ merchant_type=MerchantType.ncnf,
260
  parent_merchant_id="SOME_PARENT", # Should not have parent
261
  merchant_code="ncnf-001",
262
+ merchant_name="ncnf",
263
  contact=ContactModel(
264
  phone="+919876543210",
265
  email="ncnf@test.com",
 
277
  ),
278
  created_by="admin_001"
279
  )
280
+ assert "ncnf must not have a parent_merchant_id" in str(exc_info.value)
281
 
282
  def test_cnf_requires_full_kyc(self):
283
  """Test cnf requires complete KYC"""