Spaces:
Sleeping
Sleeping
Commit ·
349a6d0
1
Parent(s): 1c0fd58
docs(merchant_type): Update merchant type references from salon to retail and NCNF
Browse files- Update supported merchant types documentation from `national_cnf`, `cnf`, `distributor`, `salon` to `NCNF`, `cnf`, `distributor`, `retail`
- Replace all `salon` references with `retail` across documentation and example code
- Update role mapping from `role_salon_owner` to `role_retail_owner` in SCM permissions integration
- Update example payloads and API documentation to use retail merchant type
- Update model field descriptions and schema examples to reflect new merchant type values
- Update test data and demo scripts to use retail instead of salon
- Standardize merchant type naming conventions across all internal schemas and API endpoints
- MERCHANT_TYPE_IMPLEMENTATION.md +13 -13
- SCM_PERMISSIONS_INTEGRATION.md +1 -1
- app/internal/schemas.py +5 -5
- app/system_users/controllers/router.py +1 -1
- app/system_users/models/model.py +3 -3
- app/system_users/schemas/schema.py +1 -1
- app/system_users/services/service.py +2 -2
- create_initial_users.py +3 -3
- demo_merchant_type_users.py +5 -5
- migration_add_merchant_type.py +1 -1
- test_scm_permissions.py +1 -1
- test_system_users_with_merchant_type.py +7 -7
MERCHANT_TYPE_IMPLEMENTATION.md
CHANGED
|
@@ -9,7 +9,7 @@ Enhanced the system users functionality to capture and store `merchant_type` inf
|
|
| 9 |
### 1. Merchant Type Capture
|
| 10 |
- **Automatic Detection**: When creating a user, if `merchant_type` is not provided, the system attempts to fetch it from the SCM service using the `merchant_id`
|
| 11 |
- **Manual Override**: `merchant_type` can be explicitly provided during user creation
|
| 12 |
-
- **Supported Types**: `
|
| 13 |
|
| 14 |
### 2. Enhanced Data Model
|
| 15 |
- Added `merchant_type` field to `SystemUserModel`
|
|
@@ -26,10 +26,10 @@ Enhanced the system users functionality to capture and store `merchant_type` inf
|
|
| 26 |
### Model Changes
|
| 27 |
```python
|
| 28 |
# SystemUserModel
|
| 29 |
-
merchant_type: Optional[str] = Field(None, description="Merchant type (
|
| 30 |
|
| 31 |
# CreateUserRequest
|
| 32 |
-
merchant_type: Optional[str] = Field(None, description="Merchant type (
|
| 33 |
|
| 34 |
# UserInfoResponse
|
| 35 |
merchant_id: str = Field(..., description="Merchant ID")
|
|
@@ -67,8 +67,8 @@ POST /auth/users/list
|
|
| 67 |
"projection_list": ["user_id", "username", "merchant_type", "role"],
|
| 68 |
"status_filter": "active",
|
| 69 |
"role_filter": "admin",
|
| 70 |
-
"merchant_id_filter": "
|
| 71 |
-
"merchant_type_filter": "
|
| 72 |
}
|
| 73 |
```
|
| 74 |
|
|
@@ -79,8 +79,8 @@ POST /auth/users/list
|
|
| 79 |
"data": [
|
| 80 |
{
|
| 81 |
"user_id": "usr_123",
|
| 82 |
-
"username": "
|
| 83 |
-
"merchant_type": "
|
| 84 |
"role": "user"
|
| 85 |
}
|
| 86 |
],
|
|
@@ -95,10 +95,10 @@ POST /auth/users/list
|
|
| 95 |
### 1. Create User with Explicit Merchant Type
|
| 96 |
```python
|
| 97 |
user_data = CreateUserRequest(
|
| 98 |
-
username="
|
| 99 |
-
email="owner@
|
| 100 |
-
merchant_id="
|
| 101 |
-
merchant_type="
|
| 102 |
password="SecurePass@123!",
|
| 103 |
first_name="retail",
|
| 104 |
last_name="Owner",
|
|
@@ -122,9 +122,9 @@ user_data = CreateUserRequest(
|
|
| 122 |
|
| 123 |
### 3. List Users by Merchant Type
|
| 124 |
```python
|
| 125 |
-
# Get all
|
| 126 |
payload = {
|
| 127 |
-
"merchant_type_filter": "
|
| 128 |
"projection_list": ["user_id", "username", "merchant_id", "merchant_type"]
|
| 129 |
}
|
| 130 |
```
|
|
|
|
| 9 |
### 1. Merchant Type Capture
|
| 10 |
- **Automatic Detection**: When creating a user, if `merchant_type` is not provided, the system attempts to fetch it from the SCM service using the `merchant_id`
|
| 11 |
- **Manual Override**: `merchant_type` can be explicitly provided during user creation
|
| 12 |
+
- **Supported Types**: `NCNF`, `cnf`, `distributor`, `retail`
|
| 13 |
|
| 14 |
### 2. Enhanced Data Model
|
| 15 |
- Added `merchant_type` field to `SystemUserModel`
|
|
|
|
| 26 |
### Model Changes
|
| 27 |
```python
|
| 28 |
# SystemUserModel
|
| 29 |
+
merchant_type: Optional[str] = Field(None, description="Merchant type (NCNF, cnf, distributor, retail)")
|
| 30 |
|
| 31 |
# CreateUserRequest
|
| 32 |
+
merchant_type: Optional[str] = Field(None, description="Merchant type (NCNF, cnf, distributor, retail)")
|
| 33 |
|
| 34 |
# UserInfoResponse
|
| 35 |
merchant_id: str = Field(..., description="Merchant ID")
|
|
|
|
| 67 |
"projection_list": ["user_id", "username", "merchant_type", "role"],
|
| 68 |
"status_filter": "active",
|
| 69 |
"role_filter": "admin",
|
| 70 |
+
"merchant_id_filter": "mch_retail_001",
|
| 71 |
+
"merchant_type_filter": "retail"
|
| 72 |
}
|
| 73 |
```
|
| 74 |
|
|
|
|
| 79 |
"data": [
|
| 80 |
{
|
| 81 |
"user_id": "usr_123",
|
| 82 |
+
"username": "retail_owner",
|
| 83 |
+
"merchant_type": "retail",
|
| 84 |
"role": "user"
|
| 85 |
}
|
| 86 |
],
|
|
|
|
| 95 |
### 1. Create User with Explicit Merchant Type
|
| 96 |
```python
|
| 97 |
user_data = CreateUserRequest(
|
| 98 |
+
username="retail_owner",
|
| 99 |
+
email="owner@retail.com",
|
| 100 |
+
merchant_id="mch_retail_001",
|
| 101 |
+
merchant_type="retail", # Explicitly provided
|
| 102 |
password="SecurePass@123!",
|
| 103 |
first_name="retail",
|
| 104 |
last_name="Owner",
|
|
|
|
| 122 |
|
| 123 |
### 3. List Users by Merchant Type
|
| 124 |
```python
|
| 125 |
+
# Get all retail users with minimal fields
|
| 126 |
payload = {
|
| 127 |
+
"merchant_type_filter": "retail",
|
| 128 |
"projection_list": ["user_id", "username", "merchant_id", "merchant_type"]
|
| 129 |
}
|
| 130 |
```
|
SCM_PERMISSIONS_INTEGRATION.md
CHANGED
|
@@ -21,7 +21,7 @@ Added new method `get_scm_permissions_by_role()`:
|
|
| 21 |
- `super_admin` → `role_super_admin`
|
| 22 |
- `admin` → `role_company_admin`
|
| 23 |
- `manager` → `role_cnf_manager`
|
| 24 |
-
- `user` → `
|
| 25 |
|
| 26 |
### 3. Authentication Router (`app/auth/controllers/router.py`)
|
| 27 |
Updated `/login` endpoint:
|
|
|
|
| 21 |
- `super_admin` → `role_super_admin`
|
| 22 |
- `admin` → `role_company_admin`
|
| 23 |
- `manager` → `role_cnf_manager`
|
| 24 |
+
- `user` → `role_retail_owner`
|
| 25 |
|
| 26 |
### 3. Authentication Router (`app/auth/controllers/router.py`)
|
| 27 |
Updated `/login` endpoint:
|
app/internal/schemas.py
CHANGED
|
@@ -29,7 +29,7 @@ class CreateUserFromEmployeeRequest(BaseModel):
|
|
| 29 |
"phone": "+919876543210",
|
| 30 |
"role_id": "role_employee",
|
| 31 |
"merchant_id": "merchant_001",
|
| 32 |
-
"merchant_type": "
|
| 33 |
"username": "john_doe",
|
| 34 |
"metadata": {
|
| 35 |
"created_from": "employee",
|
|
@@ -55,12 +55,12 @@ class CreateUserFromMerchantRequest(BaseModel):
|
|
| 55 |
json_schema_extra = {
|
| 56 |
"example": {
|
| 57 |
"merchant_id": "merchant_001",
|
| 58 |
-
"merchant_type": "
|
| 59 |
-
"email": "owner@
|
| 60 |
"merchant_name": "Beauty retail",
|
| 61 |
"phone": "+919876543210",
|
| 62 |
-
"role_id": "
|
| 63 |
-
"username": "
|
| 64 |
"metadata": {
|
| 65 |
"created_from": "merchant",
|
| 66 |
"merchant_code": "SAL-001"
|
|
|
|
| 29 |
"phone": "+919876543210",
|
| 30 |
"role_id": "role_employee",
|
| 31 |
"merchant_id": "merchant_001",
|
| 32 |
+
"merchant_type": "retail",
|
| 33 |
"username": "john_doe",
|
| 34 |
"metadata": {
|
| 35 |
"created_from": "employee",
|
|
|
|
| 55 |
json_schema_extra = {
|
| 56 |
"example": {
|
| 57 |
"merchant_id": "merchant_001",
|
| 58 |
+
"merchant_type": "retail",
|
| 59 |
+
"email": "owner@retail.com",
|
| 60 |
"merchant_name": "Beauty retail",
|
| 61 |
"phone": "+919876543210",
|
| 62 |
+
"role_id": "role_retail_owner",
|
| 63 |
+
"username": "retail_owner",
|
| 64 |
"metadata": {
|
| 65 |
"created_from": "merchant",
|
| 66 |
"merchant_code": "SAL-001"
|
app/system_users/controllers/router.py
CHANGED
|
@@ -191,7 +191,7 @@ async def list_users_with_projection(
|
|
| 191 |
- `status_filter`: Filter by user status (active, inactive, suspended, etc.)
|
| 192 |
- `role_filter`: Filter by user role
|
| 193 |
- `merchant_id_filter`: Filter by merchant ID
|
| 194 |
-
- `merchant_type_filter`: Filter by merchant type (
|
| 195 |
|
| 196 |
**Projection Fields Available:**
|
| 197 |
user_id, username, email, merchant_id, merchant_type, first_name, last_name,
|
|
|
|
| 191 |
- `status_filter`: Filter by user status (active, inactive, suspended, etc.)
|
| 192 |
- `role_filter`: Filter by user role
|
| 193 |
- `merchant_id_filter`: Filter by merchant ID
|
| 194 |
+
- `merchant_type_filter`: Filter by merchant type (NCNF, cnf, distributor, retail)
|
| 195 |
|
| 196 |
**Projection Fields Available:**
|
| 197 |
user_id, username, email, merchant_id, merchant_type, first_name, last_name,
|
app/system_users/models/model.py
CHANGED
|
@@ -55,7 +55,7 @@ class SystemUserModel(BaseModel):
|
|
| 55 |
username: str = Field(..., description="Unique username (lowercase alphanumeric)")
|
| 56 |
email: EmailStr = Field(..., description="User email address")
|
| 57 |
merchant_id: str = Field(..., description="User Merchant Information")
|
| 58 |
-
merchant_type: Optional[str] = Field(None, description="Merchant type (
|
| 59 |
|
| 60 |
# Authentication
|
| 61 |
password_hash: str = Field(..., description="Bcrypt hashed password")
|
|
@@ -102,8 +102,8 @@ class SystemUserModel(BaseModel):
|
|
| 102 |
"user_id": "usr_01HZQX5K3N2P8R6T4V9W",
|
| 103 |
"username": "john.doe",
|
| 104 |
"email": "john.doe@company.com",
|
| 105 |
-
"merchant_id": "
|
| 106 |
-
"merchant_type": "
|
| 107 |
"password_hash": "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LeVMstdMT6jmDQrji",
|
| 108 |
"first_name": "John",
|
| 109 |
"last_name": "Doe",
|
|
|
|
| 55 |
username: str = Field(..., description="Unique username (lowercase alphanumeric)")
|
| 56 |
email: EmailStr = Field(..., description="User email address")
|
| 57 |
merchant_id: str = Field(..., description="User Merchant Information")
|
| 58 |
+
merchant_type: Optional[str] = Field(None, description="Merchant type (NCNF, cnf, distributor, retail)")
|
| 59 |
|
| 60 |
# Authentication
|
| 61 |
password_hash: str = Field(..., description="Bcrypt hashed password")
|
|
|
|
| 102 |
"user_id": "usr_01HZQX5K3N2P8R6T4V9W",
|
| 103 |
"username": "john.doe",
|
| 104 |
"email": "john.doe@company.com",
|
| 105 |
+
"merchant_id": "mch_retail_001",
|
| 106 |
+
"merchant_type": "retail",
|
| 107 |
"password_hash": "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LeVMstdMT6jmDQrji",
|
| 108 |
"first_name": "John",
|
| 109 |
"last_name": "Doe",
|
app/system_users/schemas/schema.py
CHANGED
|
@@ -43,7 +43,7 @@ class CreateUserRequest(BaseModel):
|
|
| 43 |
username: str = Field(..., description="Unique username", min_length=3, max_length=30)
|
| 44 |
email: EmailStr = Field(..., description="Email address")
|
| 45 |
merchant_id: str = Field(..., description="Merchant ID for the user")
|
| 46 |
-
merchant_type: Optional[str] = Field(None, description="Merchant type (
|
| 47 |
password: str = Field(..., description="Password", min_length=8, max_length=100)
|
| 48 |
first_name: str = Field(..., description="First name", min_length=1, max_length=50)
|
| 49 |
last_name: Optional[str] = Field(None, description="Last name", max_length=50)
|
|
|
|
| 43 |
username: str = Field(..., description="Unique username", min_length=3, max_length=30)
|
| 44 |
email: EmailStr = Field(..., description="Email address")
|
| 45 |
merchant_id: str = Field(..., description="Merchant ID for the user")
|
| 46 |
+
merchant_type: Optional[str] = Field(None, description="Merchant type (NCNF, cnf, distributor, retail)")
|
| 47 |
password: str = Field(..., description="Password", min_length=8, max_length=100)
|
| 48 |
first_name: str = Field(..., description="First name", min_length=1, max_length=50)
|
| 49 |
last_name: Optional[str] = Field(None, description="Last name", max_length=50)
|
app/system_users/services/service.py
CHANGED
|
@@ -547,8 +547,8 @@ class SystemUserService:
|
|
| 547 |
"super_admin": "role_super_admin",
|
| 548 |
"admin": "role_company_admin",
|
| 549 |
"manager": "role_cnf_manager",
|
| 550 |
-
"user": "
|
| 551 |
-
"read_only": "
|
| 552 |
}
|
| 553 |
|
| 554 |
# Get the SCM role_id
|
|
|
|
| 547 |
"super_admin": "role_super_admin",
|
| 548 |
"admin": "role_company_admin",
|
| 549 |
"manager": "role_cnf_manager",
|
| 550 |
+
"user": "role_retail_owner",
|
| 551 |
+
"read_only": "role_retail_staff"
|
| 552 |
}
|
| 553 |
|
| 554 |
# Get the SCM role_id
|
create_initial_users.py
CHANGED
|
@@ -28,7 +28,7 @@ async def create_initial_users():
|
|
| 28 |
"username": "superadmin",
|
| 29 |
"email": "superadmin@cuatrolabs.com",
|
| 30 |
"merchant_id": "company_cuatro_beauty_ltd",
|
| 31 |
-
"merchant_type": "
|
| 32 |
"password_hash": pwd_context.hash("SuperAdmin@123!"),
|
| 33 |
"first_name": "Super",
|
| 34 |
"last_name": "Admin",
|
|
@@ -51,7 +51,7 @@ async def create_initial_users():
|
|
| 51 |
"username": "admin",
|
| 52 |
"email": "admin@cuatrolabs.com",
|
| 53 |
"merchant_id": "company_cuatro_beauty_ltd",
|
| 54 |
-
"merchant_type": "
|
| 55 |
"password_hash": pwd_context.hash("CompanyAdmin@123!"),
|
| 56 |
"first_name": "Company",
|
| 57 |
"last_name": "Admin",
|
|
@@ -74,7 +74,7 @@ async def create_initial_users():
|
|
| 74 |
"username": "manager",
|
| 75 |
"email": "manager@cuatrolabs.com",
|
| 76 |
"merchant_id": "company_cuatro_beauty_ltd",
|
| 77 |
-
"merchant_type": "
|
| 78 |
"password_hash": pwd_context.hash("Manager@123!"),
|
| 79 |
"first_name": "Team",
|
| 80 |
"last_name": "Manager",
|
|
|
|
| 28 |
"username": "superadmin",
|
| 29 |
"email": "superadmin@cuatrolabs.com",
|
| 30 |
"merchant_id": "company_cuatro_beauty_ltd",
|
| 31 |
+
"merchant_type": "NCNF",
|
| 32 |
"password_hash": pwd_context.hash("SuperAdmin@123!"),
|
| 33 |
"first_name": "Super",
|
| 34 |
"last_name": "Admin",
|
|
|
|
| 51 |
"username": "admin",
|
| 52 |
"email": "admin@cuatrolabs.com",
|
| 53 |
"merchant_id": "company_cuatro_beauty_ltd",
|
| 54 |
+
"merchant_type": "NCNF",
|
| 55 |
"password_hash": pwd_context.hash("CompanyAdmin@123!"),
|
| 56 |
"first_name": "Company",
|
| 57 |
"last_name": "Admin",
|
|
|
|
| 74 |
"username": "manager",
|
| 75 |
"email": "manager@cuatrolabs.com",
|
| 76 |
"merchant_id": "company_cuatro_beauty_ltd",
|
| 77 |
+
"merchant_type": "NCNF",
|
| 78 |
"password_hash": pwd_context.hash("Manager@123!"),
|
| 79 |
"first_name": "Team",
|
| 80 |
"last_name": "Manager",
|
demo_merchant_type_users.py
CHANGED
|
@@ -30,10 +30,10 @@ async def demo_merchant_type_users():
|
|
| 30 |
# Demo users with different merchant types
|
| 31 |
demo_users = [
|
| 32 |
{
|
| 33 |
-
"username": "
|
| 34 |
-
"email": "
|
| 35 |
-
"merchant_id": "
|
| 36 |
-
"merchant_type": "
|
| 37 |
"password": "Demoretail@123!",
|
| 38 |
"first_name": "Demo",
|
| 39 |
"last_name": "retail Manager",
|
|
@@ -112,7 +112,7 @@ async def demo_merchant_type_users():
|
|
| 112 |
print(f"\n🔍 Filtering users by merchant_type")
|
| 113 |
print("-" * 50)
|
| 114 |
|
| 115 |
-
merchant_types = ["
|
| 116 |
|
| 117 |
for merchant_type in merchant_types:
|
| 118 |
try:
|
|
|
|
| 30 |
# Demo users with different merchant types
|
| 31 |
demo_users = [
|
| 32 |
{
|
| 33 |
+
"username": "retail_manager_demo",
|
| 34 |
+
"email": "retail.manager.demo@example.com",
|
| 35 |
+
"merchant_id": "mch_demo_retail_001",
|
| 36 |
+
"merchant_type": "retail", # Explicitly provided
|
| 37 |
"password": "Demoretail@123!",
|
| 38 |
"first_name": "Demo",
|
| 39 |
"last_name": "retail Manager",
|
|
|
|
| 112 |
print(f"\n🔍 Filtering users by merchant_type")
|
| 113 |
print("-" * 50)
|
| 114 |
|
| 115 |
+
merchant_types = ["retail", "distributor", "cnf", "NCNF"]
|
| 116 |
|
| 117 |
for merchant_type in merchant_types:
|
| 118 |
try:
|
migration_add_merchant_type.py
CHANGED
|
@@ -85,7 +85,7 @@ async def migrate_merchant_type():
|
|
| 85 |
|
| 86 |
if not merchant_type:
|
| 87 |
logger.warning(f"⚠️ No merchant found for merchant_id {merchant_id} (user {user_id}), setting default")
|
| 88 |
-
merchant_type = "
|
| 89 |
|
| 90 |
# Update user with merchant_type
|
| 91 |
result = await auth_users_collection.update_one(
|
|
|
|
| 85 |
|
| 86 |
if not merchant_type:
|
| 87 |
logger.warning(f"⚠️ No merchant found for merchant_id {merchant_id} (user {user_id}), setting default")
|
| 88 |
+
merchant_type = "retail" # Default fallback
|
| 89 |
|
| 90 |
# Update user with merchant_type
|
| 91 |
result = await auth_users_collection.update_one(
|
test_scm_permissions.py
CHANGED
|
@@ -21,7 +21,7 @@ async def test_scm_permissions():
|
|
| 21 |
"super_admin": "role_super_admin",
|
| 22 |
"admin": "role_company_admin",
|
| 23 |
"manager": "role_cnf_manager",
|
| 24 |
-
"user": "
|
| 25 |
}
|
| 26 |
|
| 27 |
for user_role, scm_role_id in role_mapping.items():
|
|
|
|
| 21 |
"super_admin": "role_super_admin",
|
| 22 |
"admin": "role_company_admin",
|
| 23 |
"manager": "role_cnf_manager",
|
| 24 |
+
"user": "role_retail_owner"
|
| 25 |
}
|
| 26 |
|
| 27 |
for user_role, scm_role_id in role_mapping.items():
|
test_system_users_with_merchant_type.py
CHANGED
|
@@ -44,10 +44,10 @@ async def test_create_user_with_merchant_type():
|
|
| 44 |
# Test user data with different merchant types
|
| 45 |
test_users = [
|
| 46 |
{
|
| 47 |
-
"username": "
|
| 48 |
-
"email": "
|
| 49 |
-
"merchant_id": "
|
| 50 |
-
"merchant_type": "
|
| 51 |
"password": "retailOwner@123!",
|
| 52 |
"first_name": "retail",
|
| 53 |
"last_name": "Owner",
|
|
@@ -120,9 +120,9 @@ async def test_list_users_with_projection():
|
|
| 120 |
}
|
| 121 |
},
|
| 122 |
{
|
| 123 |
-
"name": "Filter by merchant_type =
|
| 124 |
"payload": {
|
| 125 |
-
"merchant_type_filter": "
|
| 126 |
"projection_list": ["user_id", "username", "email", "merchant_id", "merchant_type"]
|
| 127 |
}
|
| 128 |
},
|
|
@@ -181,7 +181,7 @@ async def test_merchant_type_filtering():
|
|
| 181 |
|
| 182 |
headers = {"Authorization": f"Bearer {token}"}
|
| 183 |
|
| 184 |
-
merchant_types = ["
|
| 185 |
|
| 186 |
async with aiohttp.ClientSession() as session:
|
| 187 |
for merchant_type in merchant_types:
|
|
|
|
| 44 |
# Test user data with different merchant types
|
| 45 |
test_users = [
|
| 46 |
{
|
| 47 |
+
"username": "retail_owner_001",
|
| 48 |
+
"email": "retail.owner@example.com",
|
| 49 |
+
"merchant_id": "mch_retail_001",
|
| 50 |
+
"merchant_type": "retail",
|
| 51 |
"password": "retailOwner@123!",
|
| 52 |
"first_name": "retail",
|
| 53 |
"last_name": "Owner",
|
|
|
|
| 120 |
}
|
| 121 |
},
|
| 122 |
{
|
| 123 |
+
"name": "Filter by merchant_type = retail",
|
| 124 |
"payload": {
|
| 125 |
+
"merchant_type_filter": "retail",
|
| 126 |
"projection_list": ["user_id", "username", "email", "merchant_id", "merchant_type"]
|
| 127 |
}
|
| 128 |
},
|
|
|
|
| 181 |
|
| 182 |
headers = {"Authorization": f"Bearer {token}"}
|
| 183 |
|
| 184 |
+
merchant_types = ["retail", "distributor", "cnf", "NCNF"]
|
| 185 |
|
| 186 |
async with aiohttp.ClientSession() as session:
|
| 187 |
for merchant_type in merchant_types:
|