Spaces:
Runtime error
Runtime error
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 +3 -3
- MERCHANT_CITY_CODE_ENHANCEMENT_SUMMARY.md +3 -3
- MERCHANT_OPTIONAL_FIELDS_IMPLEMENTATION_SUMMARY.md +2 -2
- MERCHANT_UPDATE_TESTING_COMPLETE.md +6 -6
- app/merchants/controllers/router.py +3 -3
- app/merchants/services/service.py +1 -1
- app/system_users/models/model.py +1 -1
- app/system_users/schemas/schema.py +2 -2
- app/taxonomy/controllers/router.py +1 -1
- app/taxonomy/models/defaults.py +6 -6
- app/taxonomy/utils/defaults.py +2 -2
- app/trade_sales/services/service.py +2 -2
- app/warehouses/README.md +2 -2
- app/warehouses/models/model.py +1 -1
- app/warehouses/schemas/schema.py +1 -1
- docs/api-guides/MERCHANT_KYC_WORKFLOW.md +2 -2
- docs/examples/po_grn_rma_workflow.py +1 -1
- docs/examples/quick_start.py +9 -9
- docs/implementation-summaries/IMPLEMENTATION_SUMMARY.md +1 -1
- docs/implementation-summaries/MERCHANT_API_SUMMARY.md +1 -1
- test_merchant_api_endpoints.py +6 -6
- test_merchant_optional_fields.py +3 -3
- test_merchant_type_mapping_refactor.py +5 -5
- tests/test_merchant_list_filtering.py +1 -1
- tests/test_merchant_schemas.py +5 -5
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 |
-
|
| 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": "
|
| 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": "
|
| 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**: `
|
| 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**: `
|
| 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 |
-
- ✅ `
|
| 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 |
-
- `
|
| 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": "
|
| 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:
|
| 31 |
-
- **Problem**: `MerchantType.
|
| 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:
|
| 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`, `
|
| 93 |
- **location**: From city (e.g., `"Chennai Branch"`, `"Tiruvannamalai Branch"`)
|
| 94 |
|
| 95 |
## Error Handling
|
| 96 |
|
| 97 |
-
- **500 Internal Server Error**: Fixed AttributeError with MerchantType.
|
| 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.
|
| 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 |
-
- `
|
| 57 |
-
- `cnf`: Parent must be
|
| 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 (
|
| 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": "
|
| 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 (
|
| 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 (
|
| 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 (
|
| 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 (
|
| 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 |
-
|
| 39 |
code="ncnf",
|
| 40 |
-
label="
|
| 41 |
order=1,
|
| 42 |
-
description="
|
| 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 |
-
"
|
| 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.
|
| 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.
|
| 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: ["
|
| 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", "
|
| 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', '
|
| 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', '
|
| 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:
|
| 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:
|
| 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 (
|
| 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 = {"
|
| 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 |
-
- **
|
| 72 |
- **retail:** PAN (minimum)
|
| 73 |
|
| 74 |
**Status Transition:**
|
|
@@ -246,7 +246,7 @@ draft/submitted → rejected
|
|
| 246 |
|
| 247 |
## KYC Requirements by Merchant Type
|
| 248 |
|
| 249 |
-
###
|
| 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 |
-
#
|
| 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
|
| 16 |
-
print("Creating
|
| 17 |
-
|
| 18 |
-
"merchant_type": "
|
| 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=
|
| 50 |
if response.status_code == 201:
|
| 51 |
ncnf_data = response.json()
|
| 52 |
-
print(f"✓ Created
|
| 53 |
|
| 54 |
-
# 2. Create cnf under
|
| 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"
|
| 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
|
| 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 →
|
|
|
|
| 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": "
|
| 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
|
| 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.
|
| 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": "
|
| 241 |
-
"merchant_name": "Test
|
| 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:
|
| 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.
|
| 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, "
|
| 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="
|
| 151 |
-
merchant_type=MerchantType.
|
| 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 |
-
("
|
| 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", "
|
| 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 |
-
("
|
| 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 |
-
'"
|
| 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:
|
| 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.
|
| 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
|
| 256 |
-
"""Test
|
| 257 |
with pytest.raises(ValidationError) as exc_info:
|
| 258 |
MerchantCreate(
|
| 259 |
-
merchant_type=MerchantType.
|
| 260 |
parent_merchant_id="SOME_PARENT", # Should not have parent
|
| 261 |
merchant_code="ncnf-001",
|
| 262 |
-
merchant_name="
|
| 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 "
|
| 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"""
|