Spaces:
Runtime error
Runtime error
Commit ·
8b7caeb
1
Parent(s): 28b1d5e
feat: Add scripts for managing merchant settings data
Browse files- Created `export_sample_data.py` to export merchant settings to JSON format.
- Developed `manage_db.py` for database initialization and verification commands.
- Implemented `sample_records.py` for inserting, viewing, and clearing sample merchant settings data.
- DATABASE_SCHEMA.py +119 -0
- HIERARCHICAL_DATA_STRUCTURE.md +300 -0
- MERCHANT_SETTINGS_README.md +68 -1
- SAMPLE_RECORDS.md +176 -0
- UI_API_INTEGRATION_PROMPT.md +492 -0
- app/constants/collections.py +1 -0
- app/db_init.py +131 -0
- app/db_init/__init__.py +3 -0
- app/db_init/merchant_settings_init.py +303 -0
- create_hierarchy.py +1122 -0
- export_sample_data.py +96 -0
- manage_db.py +155 -0
- sample_records.py +518 -0
DATABASE_SCHEMA.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Database Schema Documentation
|
| 3 |
+
Shows the structure of the merchant_settings collection.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
MERCHANT_SETTINGS_SCHEMA = {
|
| 7 |
+
"collection": "scm_merchant_settings",
|
| 8 |
+
"description": "Merchant-specific configuration settings",
|
| 9 |
+
|
| 10 |
+
"indexes": [
|
| 11 |
+
{"name": "merchant_id_unique", "type": "unique", "fields": ["merchant_id"], "description": "Ensures one settings record per merchant"},
|
| 12 |
+
{"name": "is_active_index", "type": "single", "fields": ["is_active"], "description": "Quick filtering by active status"},
|
| 13 |
+
{"name": "active_merchant_index", "type": "compound", "fields": ["is_active", "merchant_id"], "description": "Combined active merchant queries"},
|
| 14 |
+
{"name": "created_at_index", "type": "single", "fields": ["created_at"], "description": "Sorting by creation time"},
|
| 15 |
+
{"name": "updated_at_index", "type": "single", "fields": ["updated_at"], "description": "Sorting by update time"},
|
| 16 |
+
{"name": "created_by_index", "type": "single", "fields": ["created_by"], "description": "Audit queries by creator"},
|
| 17 |
+
{"name": "updated_by_index", "type": "single", "fields": ["updated_by"], "description": "Audit queries by updater"},
|
| 18 |
+
{"name": "audit_created_index", "type": "compound", "fields": ["created_by", "created_at"], "description": "Audit trail queries"},
|
| 19 |
+
{"name": "notifications_index", "type": "compound", "fields": ["email_notifications", "sms_notifications", "is_active"], "description": "Notification preference queries"},
|
| 20 |
+
{"name": "inventory_settings_index", "type": "compound", "fields": ["auto_allocate_stock", "enable_backorder", "auto_reorder", "is_active"], "description": "Inventory configuration queries"}
|
| 21 |
+
],
|
| 22 |
+
|
| 23 |
+
"validation": {
|
| 24 |
+
"required_fields": ["merchant_id", "created_by", "created_at", "is_active"],
|
| 25 |
+
"field_types": {
|
| 26 |
+
"merchant_id": "string",
|
| 27 |
+
"auto_allocate_stock": "bool",
|
| 28 |
+
"credit_limit": "double|null (>= 0)",
|
| 29 |
+
"payment_terms_days": "int (0-365)",
|
| 30 |
+
"low_stock_threshold": "int (>= 0)",
|
| 31 |
+
"enable_backorder": "bool",
|
| 32 |
+
"auto_reorder": "bool",
|
| 33 |
+
"email_notifications": "bool",
|
| 34 |
+
"sms_notifications": "bool",
|
| 35 |
+
"low_stock_alerts": "bool",
|
| 36 |
+
"order_status_updates": "bool",
|
| 37 |
+
"operating_hours": "object|null",
|
| 38 |
+
"default_markup_percentage": "double|null (0-1000)",
|
| 39 |
+
"allow_discount": "bool",
|
| 40 |
+
"max_discount_percentage": "double|null (0-100)",
|
| 41 |
+
"tax_settings": "object|null",
|
| 42 |
+
"is_active": "bool",
|
| 43 |
+
"created_by": "string",
|
| 44 |
+
"created_at": "date",
|
| 45 |
+
"updated_by": "string|null",
|
| 46 |
+
"updated_at": "date|null",
|
| 47 |
+
"custom_fields": "object|null"
|
| 48 |
+
}
|
| 49 |
+
},
|
| 50 |
+
|
| 51 |
+
"example_document": {
|
| 52 |
+
"merchant_id": "mch_01HZQX5K3N2P8R6T4V9W",
|
| 53 |
+
"auto_allocate_stock": True,
|
| 54 |
+
"credit_limit": 500000.0,
|
| 55 |
+
"payment_terms_days": 30,
|
| 56 |
+
"low_stock_threshold": 10,
|
| 57 |
+
"enable_backorder": False,
|
| 58 |
+
"auto_reorder": True,
|
| 59 |
+
"email_notifications": True,
|
| 60 |
+
"sms_notifications": False,
|
| 61 |
+
"low_stock_alerts": True,
|
| 62 |
+
"order_status_updates": True,
|
| 63 |
+
"operating_hours": {
|
| 64 |
+
"monday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 65 |
+
"tuesday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 66 |
+
"wednesday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 67 |
+
"thursday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 68 |
+
"friday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 69 |
+
"saturday": {"is_open": True, "open_time": "09:00", "close_time": "14:00"},
|
| 70 |
+
"sunday": {"is_open": False}
|
| 71 |
+
},
|
| 72 |
+
"default_markup_percentage": 25.0,
|
| 73 |
+
"allow_discount": True,
|
| 74 |
+
"max_discount_percentage": 10.0,
|
| 75 |
+
"tax_settings": {
|
| 76 |
+
"gst_rate": 18.0,
|
| 77 |
+
"inclusive_pricing": True,
|
| 78 |
+
"tax_exemption": False
|
| 79 |
+
},
|
| 80 |
+
"is_active": True,
|
| 81 |
+
"created_by": "admin_001",
|
| 82 |
+
"created_at": "2024-11-30T12:00:00Z",
|
| 83 |
+
"updated_by": "admin_001",
|
| 84 |
+
"updated_at": "2024-11-30T15:30:00Z",
|
| 85 |
+
"custom_fields": {
|
| 86 |
+
"region": "Mumbai",
|
| 87 |
+
"category": "premium"
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
def print_schema_info():
|
| 93 |
+
"""Print formatted schema information."""
|
| 94 |
+
schema = MERCHANT_SETTINGS_SCHEMA
|
| 95 |
+
|
| 96 |
+
print("=" * 60)
|
| 97 |
+
print(f"MongoDB Collection: {schema['collection']}")
|
| 98 |
+
print(f"Description: {schema['description']}")
|
| 99 |
+
print("=" * 60)
|
| 100 |
+
|
| 101 |
+
print("\n📊 INDEXES:")
|
| 102 |
+
for idx in schema['indexes']:
|
| 103 |
+
print(f" • {idx['name']} ({idx['type']})")
|
| 104 |
+
print(f" Fields: {idx['fields']}")
|
| 105 |
+
print(f" Purpose: {idx['description']}")
|
| 106 |
+
print()
|
| 107 |
+
|
| 108 |
+
print("🔒 VALIDATION RULES:")
|
| 109 |
+
print(f" Required Fields: {', '.join(schema['validation']['required_fields'])}")
|
| 110 |
+
print("\n Field Types:")
|
| 111 |
+
for field, field_type in schema['validation']['field_types'].items():
|
| 112 |
+
print(f" • {field}: {field_type}")
|
| 113 |
+
|
| 114 |
+
print("\n📄 EXAMPLE DOCUMENT:")
|
| 115 |
+
import json
|
| 116 |
+
print(json.dumps(schema['example_document'], indent=2))
|
| 117 |
+
|
| 118 |
+
if __name__ == "__main__":
|
| 119 |
+
print_schema_info()
|
HIERARCHICAL_DATA_STRUCTURE.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Hierarchical Merchant Data Structure
|
| 2 |
+
|
| 3 |
+
This document describes the complete hierarchical merchant ecosystem created for the SCM system.
|
| 4 |
+
|
| 5 |
+
## Data Collections Created
|
| 6 |
+
|
| 7 |
+
### 1. scm_merchants
|
| 8 |
+
**Parent Company → CNF → Distributors → Salons hierarchy**
|
| 9 |
+
|
| 10 |
+
#### Parent Company (1)
|
| 11 |
+
- **Cuatro Beauty Limited** (`company_cuatro_beauty_ltd`)
|
| 12 |
+
- Type: `parent_company`
|
| 13 |
+
- Code: `CUATRO-HQ`
|
| 14 |
+
- Location: Mumbai, Maharashtra
|
| 15 |
+
- GST: `27AAPCU0939F1ZV`
|
| 16 |
+
|
| 17 |
+
#### CNFs (3) - Regional Distribution Centers
|
| 18 |
+
- **North India Beauty Hub CNF** (`cnf_north_india_hub`)
|
| 19 |
+
- Parent: `company_cuatro_beauty_ltd`
|
| 20 |
+
- Code: `CNF-NORTH-001`
|
| 21 |
+
- Location: Gurgaon, Haryana
|
| 22 |
+
|
| 23 |
+
- **South India Beauty Hub CNF** (`cnf_south_india_hub`)
|
| 24 |
+
- Parent: `company_cuatro_beauty_ltd`
|
| 25 |
+
- Code: `CNF-SOUTH-001`
|
| 26 |
+
- Location: Chennai, Tamil Nadu
|
| 27 |
+
|
| 28 |
+
- **West India Beauty Hub CNF** (`cnf_west_india_hub`)
|
| 29 |
+
- Parent: `company_cuatro_beauty_ltd`
|
| 30 |
+
- Code: `CNF-WEST-001`
|
| 31 |
+
- Location: Mumbai, Maharashtra
|
| 32 |
+
|
| 33 |
+
#### Distributors (6) - Regional Distribution Partners
|
| 34 |
+
**North India CNF Distributors:**
|
| 35 |
+
- **Delhi Premium Beauty Distributors** (`dist_delhi_premium`)
|
| 36 |
+
- Parent: `cnf_north_india_hub`
|
| 37 |
+
- Code: `DIST-DEL-001`
|
| 38 |
+
- Location: New Delhi
|
| 39 |
+
|
| 40 |
+
- **Punjab Beauty Wholesale Hub** (`dist_punjab_wholesale`)
|
| 41 |
+
- Parent: `cnf_north_india_hub`
|
| 42 |
+
- Code: `DIST-PB-001`
|
| 43 |
+
- Location: Ludhiana, Punjab
|
| 44 |
+
|
| 45 |
+
**South India CNF Distributors:**
|
| 46 |
+
- **Chennai Elite Beauty Distribution** (`dist_chennai_elite`)
|
| 47 |
+
- Parent: `cnf_south_india_hub`
|
| 48 |
+
- Code: `DIST-CHN-001`
|
| 49 |
+
- Location: Chennai, Tamil Nadu
|
| 50 |
+
|
| 51 |
+
- **Bangalore Metro Beauty Supplies** (`dist_bangalore_metro`)
|
| 52 |
+
- Parent: `cnf_south_india_hub`
|
| 53 |
+
- Code: `DIST-BLR-001`
|
| 54 |
+
- Location: Bangalore, Karnataka
|
| 55 |
+
|
| 56 |
+
**West India CNF Distributors:**
|
| 57 |
+
- **Mumbai Central Beauty Distribution** (`dist_mumbai_central`)
|
| 58 |
+
- Parent: `cnf_west_india_hub`
|
| 59 |
+
- Code: `DIST-MUM-001`
|
| 60 |
+
- Location: Mumbai, Maharashtra
|
| 61 |
+
|
| 62 |
+
- **Pune Premium Beauty Hub** (`dist_pune_premium`)
|
| 63 |
+
- Parent: `cnf_west_india_hub`
|
| 64 |
+
- Code: `DIST-PUN-001`
|
| 65 |
+
- Location: Pune, Maharashtra
|
| 66 |
+
|
| 67 |
+
#### Salons (9) - End Customers
|
| 68 |
+
**Delhi Distributor Salons:**
|
| 69 |
+
- **Connaught Place Luxury Salon** (`salon_delhi_cp_luxury`)
|
| 70 |
+
- Parent: `dist_delhi_premium`
|
| 71 |
+
- Code: `SALON-DEL-001`
|
| 72 |
+
|
| 73 |
+
- **Khan Market Beauty Studio** (`salon_delhi_khan_market`)
|
| 74 |
+
- Parent: `dist_delhi_premium`
|
| 75 |
+
- Code: `SALON-DEL-002`
|
| 76 |
+
|
| 77 |
+
**Punjab Distributor Salons:**
|
| 78 |
+
- **Model Town Beauty Parlour** (`salon_ludhiana_model_town`)
|
| 79 |
+
- Parent: `dist_punjab_wholesale`
|
| 80 |
+
- Code: `SALON-LDH-001`
|
| 81 |
+
|
| 82 |
+
**Chennai Distributor Salons:**
|
| 83 |
+
- **T.Nagar Glamour Studio** (`salon_chennai_tnagar`)
|
| 84 |
+
- Parent: `dist_chennai_elite`
|
| 85 |
+
- Code: `SALON-CHN-001`
|
| 86 |
+
|
| 87 |
+
- **Express Avenue Beauty Lounge** (`salon_chennai_express_avenue`)
|
| 88 |
+
- Parent: `dist_chennai_elite`
|
| 89 |
+
- Code: `SALON-CHN-002`
|
| 90 |
+
|
| 91 |
+
**Bangalore Distributor Salons:**
|
| 92 |
+
- **Brigade Road Style Studio** (`salon_bangalore_brigade_road`)
|
| 93 |
+
- Parent: `dist_bangalore_metro`
|
| 94 |
+
- Code: `SALON-BLR-001`
|
| 95 |
+
|
| 96 |
+
**Mumbai Distributor Salons:**
|
| 97 |
+
- **Linking Road Fashion Salon** (`salon_mumbai_linking_road`)
|
| 98 |
+
- Parent: `dist_mumbai_central`
|
| 99 |
+
- Code: `SALON-MUM-001`
|
| 100 |
+
|
| 101 |
+
- **Colaba Causeway Beauty Hub** (`salon_mumbai_colaba`)
|
| 102 |
+
- Parent: `dist_mumbai_central`
|
| 103 |
+
- Code: `SALON-MUM-002`
|
| 104 |
+
|
| 105 |
+
**Pune Distributor Salons:**
|
| 106 |
+
- **Koregaon Park Elite Salon** (`salon_pune_koregaon_park`)
|
| 107 |
+
- Parent: `dist_pune_premium`
|
| 108 |
+
- Code: `SALON-PUN-001`
|
| 109 |
+
|
| 110 |
+
### 2. scm_access_roles (6 Roles)
|
| 111 |
+
|
| 112 |
+
#### Role Hierarchy & Permissions
|
| 113 |
+
- **Super Administrator** (`role_super_admin`)
|
| 114 |
+
- Full system access
|
| 115 |
+
- All permissions including DELETE operations
|
| 116 |
+
|
| 117 |
+
- **Company Administrator** (`role_company_admin`)
|
| 118 |
+
- Company-wide management
|
| 119 |
+
- No DELETE permissions for security
|
| 120 |
+
|
| 121 |
+
- **CNF Manager** (`role_cnf_manager`)
|
| 122 |
+
- Regional operations management
|
| 123 |
+
- Can create distributors
|
| 124 |
+
|
| 125 |
+
- **Distributor Manager** (`role_distributor_manager`)
|
| 126 |
+
- Local distribution operations
|
| 127 |
+
- Can create salons
|
| 128 |
+
|
| 129 |
+
- **Salon Owner** (`role_salon_owner`)
|
| 130 |
+
- Salon management and operations
|
| 131 |
+
- Can place orders
|
| 132 |
+
|
| 133 |
+
- **Salon Staff** (`role_salon_staff`)
|
| 134 |
+
- Basic operations only
|
| 135 |
+
- Read-only access
|
| 136 |
+
|
| 137 |
+
### 3. scm_system_users (20 Users)
|
| 138 |
+
|
| 139 |
+
#### Administrative Users
|
| 140 |
+
- **Super Admin**: `superadmin` / `SuperAdmin@123`
|
| 141 |
+
- **Company Admin**: `companyadmin` / `CompanyAdmin@123`
|
| 142 |
+
|
| 143 |
+
#### CNF Managers
|
| 144 |
+
- **North**: `cnfmanager_north` / `CNFManager@123` (Rajesh Kumar)
|
| 145 |
+
- **South**: `cnfmanager_south` / `CNFManager@123` (Suresh Reddy)
|
| 146 |
+
- **West**: `cnfmanager_west` / `CNFManager@123` (Amit Sharma)
|
| 147 |
+
|
| 148 |
+
#### Distributor Managers
|
| 149 |
+
- **Delhi**: `distmanager_delhi` / `DistManager@123` (Vikram Singh)
|
| 150 |
+
- **Punjab**: `distmanager_punjab` / `DistManager@123` (Hardeep Singh)
|
| 151 |
+
- **Chennai**: `distmanager_chennai` / `DistManager@123` (Ramesh Kumar)
|
| 152 |
+
- **Bangalore**: `distmanager_bangalore` / `DistManager@123` (Srinivas Rao)
|
| 153 |
+
- **Mumbai**: `distmanager_mumbai` / `DistManager@123` (Rohit Patil)
|
| 154 |
+
- **Pune**: `distmanager_pune` / `DistManager@123` (Pradeep Joshi)
|
| 155 |
+
|
| 156 |
+
#### Salon Owners (9 users with role `role_salon_owner`)
|
| 157 |
+
- Individual credentials: `SalonOwner@123`
|
| 158 |
+
|
| 159 |
+
### 4. scm_merchant_settings (19 Settings)
|
| 160 |
+
|
| 161 |
+
#### Settings by Merchant Type
|
| 162 |
+
|
| 163 |
+
**Parent Company Settings:**
|
| 164 |
+
- No credit limit
|
| 165 |
+
- 0% markup (cost price)
|
| 166 |
+
- No discounts allowed
|
| 167 |
+
- Business hours: Mon-Fri 9:00-18:00
|
| 168 |
+
|
| 169 |
+
**CNF Settings:**
|
| 170 |
+
- Credit Limit: ₹1 Crore
|
| 171 |
+
- Payment Terms: 90 days
|
| 172 |
+
- Markup: 5%
|
| 173 |
+
- Max Discount: 2%
|
| 174 |
+
- Operating Hours: Mon-Sat 7:00-19:00
|
| 175 |
+
|
| 176 |
+
**Distributor Settings:**
|
| 177 |
+
- Credit Limit: ₹20L-50L (based on region)
|
| 178 |
+
- Payment Terms: 60 days
|
| 179 |
+
- Markup: 15%
|
| 180 |
+
- Max Discount: 7%
|
| 181 |
+
- Operating Hours: Mon-Sat 8:00-18:00
|
| 182 |
+
|
| 183 |
+
**Salon Settings:**
|
| 184 |
+
- Credit Limit: ₹2L-8L (based on category)
|
| 185 |
+
- Payment Terms: 15-30 days
|
| 186 |
+
- Markup: 25-35% (based on category)
|
| 187 |
+
- Max Discount: 15-20%
|
| 188 |
+
- Operating Hours: Mon-Sun 9:00-22:00
|
| 189 |
+
|
| 190 |
+
## Hierarchy Visualization
|
| 191 |
+
|
| 192 |
+
```
|
| 193 |
+
Cuatro Beauty Limited (Parent Company)
|
| 194 |
+
├── North India CNF
|
| 195 |
+
│ ├── Delhi Premium Distributors
|
| 196 |
+
│ │ ├── CP Luxury Salon
|
| 197 |
+
│ │ └── Khan Market Beauty Studio
|
| 198 |
+
│ └── Punjab Wholesale Hub
|
| 199 |
+
│ └── Model Town Beauty Parlour
|
| 200 |
+
├── South India CNF
|
| 201 |
+
│ ├── Chennai Elite Distribution
|
| 202 |
+
│ │ ├── T.Nagar Glamour Studio
|
| 203 |
+
│ │ └── Express Avenue Beauty Lounge
|
| 204 |
+
│ └── Bangalore Metro Supplies
|
| 205 |
+
│ └── Brigade Road Style Studio
|
| 206 |
+
└── West India CNF
|
| 207 |
+
├── Mumbai Central Distribution
|
| 208 |
+
│ ├── Linking Road Fashion Salon
|
| 209 |
+
│ └── Colaba Causeway Beauty Hub
|
| 210 |
+
└── Pune Premium Hub
|
| 211 |
+
└── Koregaon Park Elite Salon
|
| 212 |
+
```
|
| 213 |
+
|
| 214 |
+
## Usage Examples
|
| 215 |
+
|
| 216 |
+
### API Testing with Hierarchy Data
|
| 217 |
+
|
| 218 |
+
1. **Start FastAPI Server:**
|
| 219 |
+
```bash
|
| 220 |
+
uvicorn app.main:app --reload
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
2. **Access API Documentation:**
|
| 224 |
+
```
|
| 225 |
+
http://localhost:8000/docs
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
3. **Test Merchant Hierarchy Endpoints:**
|
| 229 |
+
- Get all merchants: `GET /merchants/`
|
| 230 |
+
- Get CNF merchants: `GET /merchants/?merchant_type=cnf`
|
| 231 |
+
- Get child merchants: `GET /merchants/children/{parent_merchant_id}`
|
| 232 |
+
- Get merchant settings: `GET /merchant-settings/{merchant_id}`
|
| 233 |
+
|
| 234 |
+
### Database Queries
|
| 235 |
+
|
| 236 |
+
**Get complete hierarchy for a CNF:**
|
| 237 |
+
```javascript
|
| 238 |
+
db.scm_merchants.find({"parent_merchant_id": "cnf_north_india_hub"})
|
| 239 |
+
```
|
| 240 |
+
|
| 241 |
+
**Get all salons under a specific distributor:**
|
| 242 |
+
```javascript
|
| 243 |
+
db.scm_merchants.find({
|
| 244 |
+
"parent_merchant_id": "dist_delhi_premium",
|
| 245 |
+
"merchant_type": "salon"
|
| 246 |
+
})
|
| 247 |
+
```
|
| 248 |
+
|
| 249 |
+
**Get users by role:**
|
| 250 |
+
```javascript
|
| 251 |
+
db.scm_system_users.find({"role_id": "role_cnf_manager"})
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
## Business Logic Implementation
|
| 255 |
+
|
| 256 |
+
### Credit Limits by Type
|
| 257 |
+
- **Parent Company**: No limit (unlimited)
|
| 258 |
+
- **CNF**: ₹1 Crore (high volume wholesale)
|
| 259 |
+
- **Distributor**: ₹20L-50L (regional wholesale)
|
| 260 |
+
- **Salon**: ₹2L-8L (retail based on category)
|
| 261 |
+
|
| 262 |
+
### Markup Strategy
|
| 263 |
+
- **Parent Company**: 0% (cost price)
|
| 264 |
+
- **CNF**: 5% (minimal margin for operations)
|
| 265 |
+
- **Distributor**: 15% (wholesale margin)
|
| 266 |
+
- **Salon**: 25-35% (retail margin based on premium level)
|
| 267 |
+
|
| 268 |
+
### Operating Hours
|
| 269 |
+
- **Corporate**: Business hours (9-6)
|
| 270 |
+
- **CNF**: Extended hours (7-7) for operations
|
| 271 |
+
- **Distributor**: Business hours (8-6)
|
| 272 |
+
- **Salon**: Customer hours (9-10 PM, 7 days)
|
| 273 |
+
|
| 274 |
+
## Data Management Commands
|
| 275 |
+
|
| 276 |
+
```bash
|
| 277 |
+
# Create complete hierarchy
|
| 278 |
+
python3 create_hierarchy.py create
|
| 279 |
+
|
| 280 |
+
# Verify created data
|
| 281 |
+
python3 create_hierarchy.py verify
|
| 282 |
+
|
| 283 |
+
# Show help
|
| 284 |
+
python3 create_hierarchy.py help
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
## Security & Authentication
|
| 288 |
+
|
| 289 |
+
- **Password Hashing**: SHA256 (production should use bcrypt)
|
| 290 |
+
- **Role-Based Access**: Hierarchical permissions
|
| 291 |
+
- **Merchant Isolation**: Users can only access their merchant and children
|
| 292 |
+
- **API Authentication**: JWT token-based (implement as needed)
|
| 293 |
+
|
| 294 |
+
## Next Steps
|
| 295 |
+
|
| 296 |
+
1. **API Integration Testing**: Test all endpoints with hierarchical data
|
| 297 |
+
2. **Business Logic Validation**: Verify credit limits and permissions
|
| 298 |
+
3. **Reporting Queries**: Implement hierarchy-aware reporting
|
| 299 |
+
4. **Data Relationships**: Add foreign key validation in application layer
|
| 300 |
+
5. **Performance Optimization**: Add database indexes for hierarchy queries
|
MERCHANT_SETTINGS_README.md
CHANGED
|
@@ -165,8 +165,75 @@ PATCH /merchant-settings/mch_123/operating-hours
|
|
| 165 |
- Extensible for future requirements
|
| 166 |
- Follows microservice patterns
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
## Testing
|
| 169 |
- Use the FastAPI docs at `/docs` to test endpoints
|
| 170 |
- Ensure MongoDB is running and connected
|
|
|
|
| 171 |
- Create a merchant first before creating settings
|
| 172 |
-
- Test with valid JWT tokens for authentication
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
- Extensible for future requirements
|
| 166 |
- Follows microservice patterns
|
| 167 |
|
| 168 |
+
## Database Setup
|
| 169 |
+
|
| 170 |
+
### Collection Structure
|
| 171 |
+
- **Collection Name**: `scm_merchant_settings`
|
| 172 |
+
- **Database**: MongoDB with schema validation
|
| 173 |
+
- **Indexes**: 11 optimized indexes for performance
|
| 174 |
+
|
| 175 |
+
### Initialize Database
|
| 176 |
+
Run the database initialization script to create the collection and indexes:
|
| 177 |
+
|
| 178 |
+
```bash
|
| 179 |
+
# Initialize database structure
|
| 180 |
+
python3 manage_db.py init-db
|
| 181 |
+
|
| 182 |
+
# Verify database setup
|
| 183 |
+
python3 manage_db.py verify-db
|
| 184 |
+
```
|
| 185 |
+
|
| 186 |
+
### Collection Features
|
| 187 |
+
- **Schema Validation**: MongoDB validates all documents against the defined schema
|
| 188 |
+
- **Unique Constraint**: Only one settings record per merchant (`merchant_id_unique` index)
|
| 189 |
+
- **Performance Indexes**: Optimized for common query patterns
|
| 190 |
+
- **Audit Trail**: Tracks creation and update history with user information
|
| 191 |
+
|
| 192 |
+
### Key Indexes
|
| 193 |
+
- `merchant_id_unique` - Ensures one settings per merchant
|
| 194 |
+
- `is_active_index` - Fast filtering by active status
|
| 195 |
+
- `active_merchant_index` - Combined queries for active merchants
|
| 196 |
+
- `notifications_index` - Notification preference queries
|
| 197 |
+
- `inventory_settings_index` - Inventory configuration queries
|
| 198 |
+
- `audit_created_index` - Audit trail queries
|
| 199 |
+
|
| 200 |
## Testing
|
| 201 |
- Use the FastAPI docs at `/docs` to test endpoints
|
| 202 |
- Ensure MongoDB is running and connected
|
| 203 |
+
- Initialize database: `python3 manage_db.py init-db`
|
| 204 |
- Create a merchant first before creating settings
|
| 205 |
+
- Test with valid JWT tokens for authentication
|
| 206 |
+
|
| 207 |
+
## Database Commands
|
| 208 |
+
```bash
|
| 209 |
+
# Initialize database collections and indexes
|
| 210 |
+
python3 manage_db.py init-db
|
| 211 |
+
|
| 212 |
+
# Verify database structure
|
| 213 |
+
python3 manage_db.py verify-db
|
| 214 |
+
|
| 215 |
+
# View database schema documentation
|
| 216 |
+
python3 DATABASE_SCHEMA.py
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
## Sample Data Management
|
| 220 |
+
```bash
|
| 221 |
+
# Insert sample merchant settings records (5 realistic examples)
|
| 222 |
+
python3 sample_records.py insert
|
| 223 |
+
|
| 224 |
+
# View sample records in terminal
|
| 225 |
+
python3 sample_records.py view
|
| 226 |
+
|
| 227 |
+
# Export sample data to JSON file
|
| 228 |
+
python3 export_sample_data.py
|
| 229 |
+
|
| 230 |
+
# Clear all sample data
|
| 231 |
+
python3 sample_records.py clear
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
### Sample Records Include:
|
| 235 |
+
1. **Premium Salon (Mumbai)** - High-end salon with full features
|
| 236 |
+
2. **Distributor (Delhi)** - Wholesale distributor with large credit limit
|
| 237 |
+
3. **Standard Salon (Bangalore)** - Regular salon with standard settings
|
| 238 |
+
4. **CNF (Chennai)** - Carrying & Forwarding agent with enterprise settings
|
| 239 |
+
5. **Budget Salon (Pune)** - Small salon with basic configuration (inactive for testing)
|
SAMPLE_RECORDS.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Sample Merchant Settings Records
|
| 2 |
+
|
| 3 |
+
This document shows the 5 sample merchant settings records that have been created for testing and demonstration.
|
| 4 |
+
|
| 5 |
+
## Quick Summary
|
| 6 |
+
|
| 7 |
+
| Merchant ID | Type | Region | Credit Limit | Status | Category |
|
| 8 |
+
|-------------|------|--------|--------------|--------|----------|
|
| 9 |
+
| mch_salon_mumbai_001 | Salon | Mumbai | ₹2,50,000 | 🟢 Active | Premium |
|
| 10 |
+
| mch_distributor_delhi_001 | Distributor | Delhi | ₹20,00,000 | 🟢 Active | Wholesale |
|
| 11 |
+
| mch_salon_bangalore_002 | Salon | Bangalore | ₹1,50,000 | 🟢 Active | Standard |
|
| 12 |
+
| mch_cnf_chennai_001 | CNF | Chennai | ₹50,00,000 | 🟢 Active | Enterprise |
|
| 13 |
+
| mch_salon_pune_003 | Salon | Pune | ₹1,00,000 | 🔴 Inactive | Budget |
|
| 14 |
+
|
| 15 |
+
## Detailed Record Examples
|
| 16 |
+
|
| 17 |
+
### 1. Premium Salon (Mumbai) - `mch_salon_mumbai_001`
|
| 18 |
+
```json
|
| 19 |
+
{
|
| 20 |
+
"merchant_id": "mch_salon_mumbai_001",
|
| 21 |
+
"auto_allocate_stock": true,
|
| 22 |
+
"credit_limit": 250000.0,
|
| 23 |
+
"payment_terms_days": 15,
|
| 24 |
+
"low_stock_threshold": 5,
|
| 25 |
+
"enable_backorder": false,
|
| 26 |
+
"auto_reorder": true,
|
| 27 |
+
"email_notifications": true,
|
| 28 |
+
"sms_notifications": true,
|
| 29 |
+
"low_stock_alerts": true,
|
| 30 |
+
"order_status_updates": true,
|
| 31 |
+
"operating_hours": {
|
| 32 |
+
"monday": {"is_open": true, "open_time": "10:00", "close_time": "20:00"},
|
| 33 |
+
"friday": {"is_open": true, "open_time": "10:00", "close_time": "21:00"},
|
| 34 |
+
"saturday": {"is_open": true, "open_time": "09:00", "close_time": "22:00"},
|
| 35 |
+
"sunday": {"is_open": true, "open_time": "11:00", "close_time": "20:00"}
|
| 36 |
+
},
|
| 37 |
+
"default_markup_percentage": 30.0,
|
| 38 |
+
"allow_discount": true,
|
| 39 |
+
"max_discount_percentage": 15.0,
|
| 40 |
+
"tax_settings": {
|
| 41 |
+
"gst_rate": 18.0,
|
| 42 |
+
"inclusive_pricing": true,
|
| 43 |
+
"tax_exemption": false
|
| 44 |
+
},
|
| 45 |
+
"custom_fields": {
|
| 46 |
+
"region": "Mumbai",
|
| 47 |
+
"category": "premium",
|
| 48 |
+
"loyalty_program": true,
|
| 49 |
+
"online_booking": true
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
### 2. Wholesale Distributor (Delhi) - `mch_distributor_delhi_001`
|
| 55 |
+
```json
|
| 56 |
+
{
|
| 57 |
+
"merchant_id": "mch_distributor_delhi_001",
|
| 58 |
+
"auto_allocate_stock": false,
|
| 59 |
+
"credit_limit": 2000000.0,
|
| 60 |
+
"payment_terms_days": 45,
|
| 61 |
+
"low_stock_threshold": 50,
|
| 62 |
+
"enable_backorder": true,
|
| 63 |
+
"auto_reorder": false,
|
| 64 |
+
"email_notifications": true,
|
| 65 |
+
"sms_notifications": false,
|
| 66 |
+
"operating_hours": {
|
| 67 |
+
"monday": {"is_open": true, "open_time": "08:00", "close_time": "18:00"},
|
| 68 |
+
"saturday": {"is_open": true, "open_time": "09:00", "close_time": "16:00"},
|
| 69 |
+
"sunday": {"is_open": false}
|
| 70 |
+
},
|
| 71 |
+
"default_markup_percentage": 12.0,
|
| 72 |
+
"allow_discount": true,
|
| 73 |
+
"max_discount_percentage": 5.0,
|
| 74 |
+
"tax_settings": {
|
| 75 |
+
"gst_rate": 18.0,
|
| 76 |
+
"inclusive_pricing": false,
|
| 77 |
+
"tax_exemption": false
|
| 78 |
+
},
|
| 79 |
+
"custom_fields": {
|
| 80 |
+
"region": "North India",
|
| 81 |
+
"category": "wholesale",
|
| 82 |
+
"warehouse_count": 3,
|
| 83 |
+
"delivery_zones": ["Delhi", "Gurgaon", "Noida", "Faridabad"]
|
| 84 |
+
}
|
| 85 |
+
}
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### 3. CNF Enterprise (Chennai) - `mch_cnf_chennai_001`
|
| 89 |
+
```json
|
| 90 |
+
{
|
| 91 |
+
"merchant_id": "mch_cnf_chennai_001",
|
| 92 |
+
"auto_allocate_stock": false,
|
| 93 |
+
"credit_limit": 5000000.0,
|
| 94 |
+
"payment_terms_days": 60,
|
| 95 |
+
"low_stock_threshold": 100,
|
| 96 |
+
"enable_backorder": true,
|
| 97 |
+
"auto_reorder": true,
|
| 98 |
+
"email_notifications": true,
|
| 99 |
+
"sms_notifications": false,
|
| 100 |
+
"operating_hours": {
|
| 101 |
+
"monday": {"is_open": true, "open_time": "07:00", "close_time": "19:00"},
|
| 102 |
+
"sunday": {"is_open": false}
|
| 103 |
+
},
|
| 104 |
+
"default_markup_percentage": 8.0,
|
| 105 |
+
"allow_discount": true,
|
| 106 |
+
"max_discount_percentage": 3.0,
|
| 107 |
+
"tax_settings": {
|
| 108 |
+
"gst_rate": 18.0,
|
| 109 |
+
"inclusive_pricing": false,
|
| 110 |
+
"tax_exemption": false
|
| 111 |
+
},
|
| 112 |
+
"custom_fields": {
|
| 113 |
+
"region": "South India",
|
| 114 |
+
"category": "cnf",
|
| 115 |
+
"warehouse_capacity": "50000 sqft",
|
| 116 |
+
"distribution_network": ["Chennai", "Coimbatore", "Madurai", "Trichy"],
|
| 117 |
+
"cold_storage": true
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
## Business Logic Variations
|
| 123 |
+
|
| 124 |
+
### Auto Stock Allocation
|
| 125 |
+
- **Enabled**: Salons (for quick service)
|
| 126 |
+
- **Disabled**: Distributors/CNF (for manual control)
|
| 127 |
+
|
| 128 |
+
### Credit Limits by Type
|
| 129 |
+
- **Salon**: ₹1,00,000 - ₹2,50,000
|
| 130 |
+
- **Distributor**: ₹20,00,000
|
| 131 |
+
- **CNF**: ₹50,00,000
|
| 132 |
+
|
| 133 |
+
### Payment Terms
|
| 134 |
+
- **Salon**: 15-30 days (quick turnover)
|
| 135 |
+
- **Distributor**: 45 days (standard B2B)
|
| 136 |
+
- **CNF**: 60 days (enterprise terms)
|
| 137 |
+
|
| 138 |
+
### Operating Hours
|
| 139 |
+
- **Salons**: Customer-facing hours (9 AM - 9 PM, open on weekends)
|
| 140 |
+
- **Distributors**: Business hours (8 AM - 6 PM, limited weekends)
|
| 141 |
+
- **CNF**: Extended business hours (7 AM - 7 PM, no Sunday)
|
| 142 |
+
|
| 143 |
+
### Markup Percentages
|
| 144 |
+
- **CNF**: 8% (wholesale)
|
| 145 |
+
- **Distributor**: 12% (distribution)
|
| 146 |
+
- **Salon**: 22-30% (retail)
|
| 147 |
+
|
| 148 |
+
### Notification Preferences
|
| 149 |
+
- **All**: Email notifications enabled
|
| 150 |
+
- **Salons**: SMS enabled for customer-facing operations
|
| 151 |
+
- **B2B**: SMS disabled to reduce noise
|
| 152 |
+
|
| 153 |
+
## API Testing Examples
|
| 154 |
+
|
| 155 |
+
Use these merchant IDs to test the merchant settings API endpoints:
|
| 156 |
+
|
| 157 |
+
```bash
|
| 158 |
+
# Get active salon settings
|
| 159 |
+
GET /merchant-settings/mch_salon_mumbai_001
|
| 160 |
+
|
| 161 |
+
# Get distributor settings
|
| 162 |
+
GET /merchant-settings/mch_distributor_delhi_001
|
| 163 |
+
|
| 164 |
+
# Get inactive salon (for testing edge cases)
|
| 165 |
+
GET /merchant-settings/mch_salon_pune_003
|
| 166 |
+
|
| 167 |
+
# List active settings only
|
| 168 |
+
GET /merchant-settings?status=active
|
| 169 |
+
|
| 170 |
+
# Update notification preferences
|
| 171 |
+
PATCH /merchant-settings/mch_salon_bangalore_002/notifications
|
| 172 |
+
{
|
| 173 |
+
"email_notifications": false,
|
| 174 |
+
"sms_notifications": true
|
| 175 |
+
}
|
| 176 |
+
```
|
UI_API_INTEGRATION_PROMPT.md
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# UI API Integration Prompt Instructions
|
| 2 |
+
|
| 3 |
+
## 🎯 **System Overview**
|
| 4 |
+
You are building a UI for a hierarchical B2B beauty supply chain management system. The backend provides REST APIs for managing merchants in a 4-level hierarchy: **Parent Company → CNF → Distributors → Salons**.
|
| 5 |
+
|
| 6 |
+
## 🏗️ **API Base Configuration**
|
| 7 |
+
|
| 8 |
+
### Base URL & Authentication
|
| 9 |
+
```javascript
|
| 10 |
+
const API_BASE_URL = 'http://localhost:8000'
|
| 11 |
+
const API_ENDPOINTS = {
|
| 12 |
+
merchants: '/merchants',
|
| 13 |
+
merchantSettings: '/merchant-settings',
|
| 14 |
+
users: '/system-users',
|
| 15 |
+
roles: '/access-roles',
|
| 16 |
+
auth: '/auth'
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
// JWT Token Authentication (implement as needed)
|
| 20 |
+
const apiHeaders = {
|
| 21 |
+
'Content-Type': 'application/json',
|
| 22 |
+
'Authorization': `Bearer ${localStorage.getItem('authToken')}`
|
| 23 |
+
}
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### Test Credentials for Development
|
| 27 |
+
```javascript
|
| 28 |
+
const TEST_CREDENTIALS = {
|
| 29 |
+
superAdmin: { username: 'superadmin', password: 'SuperAdmin@123' },
|
| 30 |
+
companyAdmin: { username: 'companyadmin', password: 'CompanyAdmin@123' },
|
| 31 |
+
cnfManager: { username: 'cnfmanager_north', password: 'CNFManager@123' },
|
| 32 |
+
distManager: { username: 'distmanager_delhi', password: 'DistManager@123' },
|
| 33 |
+
salonOwner: { username: 'owner_cp_luxury', password: 'SalonOwner@123' }
|
| 34 |
+
}
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
## 📱 **Core UI Components to Build**
|
| 38 |
+
|
| 39 |
+
### 1. **Merchant Hierarchy Tree View**
|
| 40 |
+
```javascript
|
| 41 |
+
// GET /merchants - Fetch all merchants
|
| 42 |
+
const fetchMerchantsHierarchy = async () => {
|
| 43 |
+
const response = await fetch(`${API_BASE_URL}/merchants`, {
|
| 44 |
+
headers: apiHeaders
|
| 45 |
+
})
|
| 46 |
+
const merchants = await response.json()
|
| 47 |
+
|
| 48 |
+
// Build tree structure
|
| 49 |
+
return buildHierarchyTree(merchants)
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
// Expected merchant object structure:
|
| 53 |
+
const merchantStructure = {
|
| 54 |
+
merchant_id: "string",
|
| 55 |
+
merchant_name: "string",
|
| 56 |
+
merchant_type: "parent_company|cnf|distributor|salon",
|
| 57 |
+
merchant_code: "string",
|
| 58 |
+
parent_merchant_id: "string|null",
|
| 59 |
+
status: "active|inactive",
|
| 60 |
+
contact: {
|
| 61 |
+
phone: "string",
|
| 62 |
+
email: "string",
|
| 63 |
+
address_line1: "string",
|
| 64 |
+
city: "string",
|
| 65 |
+
state: "string",
|
| 66 |
+
pincode: "string"
|
| 67 |
+
},
|
| 68 |
+
kyc: { /* GST, PAN details */ }
|
| 69 |
+
}
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
### 2. **Merchant Management Dashboard**
|
| 73 |
+
Create these UI sections:
|
| 74 |
+
|
| 75 |
+
#### **A. Merchant List/Grid View**
|
| 76 |
+
```javascript
|
| 77 |
+
// API Calls:
|
| 78 |
+
// GET /merchants?merchant_type=salon - Filter by type
|
| 79 |
+
// GET /merchants?status=active - Filter by status
|
| 80 |
+
// GET /merchants/children/{parent_id} - Get child merchants
|
| 81 |
+
|
| 82 |
+
const merchantListConfig = {
|
| 83 |
+
columns: [
|
| 84 |
+
{ key: 'merchant_code', label: 'Code', sortable: true },
|
| 85 |
+
{ key: 'merchant_name', label: 'Name', sortable: true },
|
| 86 |
+
{ key: 'merchant_type', label: 'Type', filterable: true },
|
| 87 |
+
{ key: 'contact.city', label: 'City' },
|
| 88 |
+
{ key: 'status', label: 'Status', filterable: true },
|
| 89 |
+
{ key: 'actions', label: 'Actions' }
|
| 90 |
+
],
|
| 91 |
+
filters: ['merchant_type', 'status', 'city'],
|
| 92 |
+
actions: ['view', 'edit', 'settings', 'delete']
|
| 93 |
+
}
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
#### **B. Merchant Creation Form**
|
| 97 |
+
```javascript
|
| 98 |
+
// POST /merchants - Create new merchant
|
| 99 |
+
const createMerchantForm = {
|
| 100 |
+
fields: {
|
| 101 |
+
merchant_name: { type: 'text', required: true },
|
| 102 |
+
merchant_type: {
|
| 103 |
+
type: 'select',
|
| 104 |
+
options: ['cnf', 'distributor', 'salon'],
|
| 105 |
+
required: true
|
| 106 |
+
},
|
| 107 |
+
parent_merchant_id: {
|
| 108 |
+
type: 'autocomplete',
|
| 109 |
+
source: '/merchants', // Dynamic based on type
|
| 110 |
+
required: true
|
| 111 |
+
},
|
| 112 |
+
contact: {
|
| 113 |
+
phone: { type: 'tel', required: true },
|
| 114 |
+
email: { type: 'email', required: true },
|
| 115 |
+
address_line1: { type: 'text', required: true },
|
| 116 |
+
city: { type: 'text', required: true },
|
| 117 |
+
state: { type: 'select', options: 'INDIAN_STATES' },
|
| 118 |
+
pincode: { type: 'text', pattern: '[0-9]{6}' }
|
| 119 |
+
},
|
| 120 |
+
kyc: {
|
| 121 |
+
gst_number: { type: 'text', pattern: 'GST_REGEX' },
|
| 122 |
+
pan_number: { type: 'text', pattern: 'PAN_REGEX' }
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
validation: 'client-side + server-side'
|
| 126 |
+
}
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
### 3. **Merchant Settings Management**
|
| 130 |
+
```javascript
|
| 131 |
+
// GET /merchant-settings/{merchant_id} - Fetch settings
|
| 132 |
+
// PUT /merchant-settings/{merchant_id} - Update settings
|
| 133 |
+
|
| 134 |
+
const merchantSettingsForm = {
|
| 135 |
+
businessSettings: {
|
| 136 |
+
auto_allocate_stock: { type: 'toggle' },
|
| 137 |
+
credit_limit: { type: 'currency', min: 0 },
|
| 138 |
+
payment_terms_days: { type: 'number', min: 0, max: 365 }
|
| 139 |
+
},
|
| 140 |
+
inventorySettings: {
|
| 141 |
+
low_stock_threshold: { type: 'number', min: 1 },
|
| 142 |
+
enable_backorder: { type: 'toggle' },
|
| 143 |
+
auto_reorder: { type: 'toggle' }
|
| 144 |
+
},
|
| 145 |
+
notifications: {
|
| 146 |
+
email_notifications: { type: 'toggle' },
|
| 147 |
+
sms_notifications: { type: 'toggle' },
|
| 148 |
+
low_stock_alerts: { type: 'toggle' }
|
| 149 |
+
},
|
| 150 |
+
operatingHours: {
|
| 151 |
+
// Dynamic day-wise hour picker
|
| 152 |
+
format: { open_time: 'HH:mm', close_time: 'HH:mm', is_open: boolean }
|
| 153 |
+
},
|
| 154 |
+
pricing: {
|
| 155 |
+
default_markup_percentage: { type: 'percentage', min: 0, max: 100 },
|
| 156 |
+
allow_discount: { type: 'toggle' },
|
| 157 |
+
max_discount_percentage: { type: 'percentage', min: 0, max: 50 }
|
| 158 |
+
},
|
| 159 |
+
taxSettings: {
|
| 160 |
+
gst_rate: { type: 'percentage', default: 18 },
|
| 161 |
+
inclusive_pricing: { type: 'toggle' },
|
| 162 |
+
tax_exemption: { type: 'toggle' }
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
### 4. **User & Role Management**
|
| 168 |
+
```javascript
|
| 169 |
+
// GET /access-roles - Fetch all roles
|
| 170 |
+
// GET /system-users - Fetch all users
|
| 171 |
+
// POST /system-users - Create user
|
| 172 |
+
|
| 173 |
+
const userManagementConfig = {
|
| 174 |
+
userForm: {
|
| 175 |
+
username: { type: 'text', unique: true },
|
| 176 |
+
email: { type: 'email', unique: true },
|
| 177 |
+
full_name: { type: 'text' },
|
| 178 |
+
role_id: {
|
| 179 |
+
type: 'select',
|
| 180 |
+
source: '/access-roles',
|
| 181 |
+
displayField: 'role_name'
|
| 182 |
+
},
|
| 183 |
+
merchant_id: {
|
| 184 |
+
type: 'autocomplete',
|
| 185 |
+
source: '/merchants',
|
| 186 |
+
filter: 'based on role permissions'
|
| 187 |
+
},
|
| 188 |
+
password: { type: 'password', minLength: 8 }
|
| 189 |
+
},
|
| 190 |
+
roleBasedAccess: {
|
| 191 |
+
// Show/hide features based on user role
|
| 192 |
+
'role_super_admin': 'ALL_FEATURES',
|
| 193 |
+
'role_company_admin': 'EXCLUDE_DELETE_OPERATIONS',
|
| 194 |
+
'role_cnf_manager': 'REGIONAL_SCOPE_ONLY',
|
| 195 |
+
'role_distributor_manager': 'LOCAL_SCOPE_ONLY',
|
| 196 |
+
'role_salon_owner': 'OWN_MERCHANT_ONLY'
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
## 🔄 **Key API Integration Patterns**
|
| 202 |
+
|
| 203 |
+
### 1. **Hierarchical Data Loading**
|
| 204 |
+
```javascript
|
| 205 |
+
// Pattern: Load parent first, then children on-demand
|
| 206 |
+
const loadHierarchicalData = async (parentId = null) => {
|
| 207 |
+
if (!parentId) {
|
| 208 |
+
// Load root level (Parent Company + CNFs)
|
| 209 |
+
return await fetch(`${API_BASE_URL}/merchants?merchant_type=parent_company,cnf`)
|
| 210 |
+
} else {
|
| 211 |
+
// Load children of specific parent
|
| 212 |
+
return await fetch(`${API_BASE_URL}/merchants/children/${parentId}`)
|
| 213 |
+
}
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
// Tree expansion pattern
|
| 217 |
+
const expandTreeNode = async (nodeId) => {
|
| 218 |
+
const children = await fetch(`${API_BASE_URL}/merchants/children/${nodeId}`)
|
| 219 |
+
// Update tree state with children
|
| 220 |
+
}
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
### 2. **Context-Aware Form Fields**
|
| 224 |
+
```javascript
|
| 225 |
+
// Parent selection based on merchant type
|
| 226 |
+
const getParentOptions = async (merchantType) => {
|
| 227 |
+
const parentTypes = {
|
| 228 |
+
'cnf': ['parent_company'],
|
| 229 |
+
'distributor': ['cnf'],
|
| 230 |
+
'salon': ['distributor']
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
const parentType = parentTypes[merchantType]?.[0]
|
| 234 |
+
if (parentType) {
|
| 235 |
+
return await fetch(`${API_BASE_URL}/merchants?merchant_type=${parentType}`)
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
// Settings defaults based on merchant type
|
| 240 |
+
const getSettingsDefaults = (merchantType) => {
|
| 241 |
+
const defaults = {
|
| 242 |
+
'cnf': { credit_limit: 10000000, payment_terms_days: 90, markup: 5 },
|
| 243 |
+
'distributor': { credit_limit: 3000000, payment_terms_days: 60, markup: 15 },
|
| 244 |
+
'salon': { credit_limit: 500000, payment_terms_days: 30, markup: 30 }
|
| 245 |
+
}
|
| 246 |
+
return defaults[merchantType] || {}
|
| 247 |
+
}
|
| 248 |
+
```
|
| 249 |
+
|
| 250 |
+
### 3. **Error Handling & Validation**
|
| 251 |
+
```javascript
|
| 252 |
+
const apiErrorHandler = (error) => {
|
| 253 |
+
const errorMessages = {
|
| 254 |
+
400: 'Invalid request data. Please check your input.',
|
| 255 |
+
401: 'Authentication required. Please login.',
|
| 256 |
+
403: 'You do not have permission for this action.',
|
| 257 |
+
404: 'Resource not found.',
|
| 258 |
+
409: 'Duplicate entry. This record already exists.',
|
| 259 |
+
422: 'Validation failed. Please check highlighted fields.',
|
| 260 |
+
500: 'Server error. Please try again later.'
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
return errorMessages[error.status] || 'An unexpected error occurred.'
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
// Form validation
|
| 267 |
+
const validateMerchantForm = (data) => {
|
| 268 |
+
const errors = {}
|
| 269 |
+
|
| 270 |
+
if (!data.merchant_name?.trim()) {
|
| 271 |
+
errors.merchant_name = 'Merchant name is required'
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
if (data.merchant_type === 'salon' && !data.parent_merchant_id) {
|
| 275 |
+
errors.parent_merchant_id = 'Parent distributor is required for salons'
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
// GST validation for Indian businesses
|
| 279 |
+
if (data.kyc?.gst_number && !/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/.test(data.kyc.gst_number)) {
|
| 280 |
+
errors['kyc.gst_number'] = 'Invalid GST number format'
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
return errors
|
| 284 |
+
}
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
## 🎨 **UI/UX Implementation Guidelines**
|
| 288 |
+
|
| 289 |
+
### 1. **Responsive Design Requirements**
|
| 290 |
+
```css
|
| 291 |
+
/* Mobile-first approach for merchant management */
|
| 292 |
+
.merchant-card {
|
| 293 |
+
/* Stack vertically on mobile */
|
| 294 |
+
@media (max-width: 768px) {
|
| 295 |
+
flex-direction: column;
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
.merchant-hierarchy-tree {
|
| 300 |
+
/* Horizontal scroll on mobile */
|
| 301 |
+
@media (max-width: 768px) {
|
| 302 |
+
overflow-x: auto;
|
| 303 |
+
white-space: nowrap;
|
| 304 |
+
}
|
| 305 |
+
}
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
### 2. **State Management Pattern**
|
| 309 |
+
```javascript
|
| 310 |
+
// Redux/Zustand store structure
|
| 311 |
+
const merchantStore = {
|
| 312 |
+
state: {
|
| 313 |
+
merchants: [],
|
| 314 |
+
selectedMerchant: null,
|
| 315 |
+
hierarchyTree: {},
|
| 316 |
+
filters: { type: 'all', status: 'active' },
|
| 317 |
+
loading: false,
|
| 318 |
+
error: null
|
| 319 |
+
},
|
| 320 |
+
actions: {
|
| 321 |
+
fetchMerchants: async (filters) => { },
|
| 322 |
+
createMerchant: async (data) => { },
|
| 323 |
+
updateMerchant: async (id, data) => { },
|
| 324 |
+
deleteMerchant: async (id) => { },
|
| 325 |
+
setSelectedMerchant: (merchant) => { },
|
| 326 |
+
setFilters: (filters) => { }
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
```
|
| 330 |
+
|
| 331 |
+
### 3. **Component Architecture**
|
| 332 |
+
```javascript
|
| 333 |
+
// Component hierarchy
|
| 334 |
+
const ComponentStructure = {
|
| 335 |
+
'MerchantDashboard': {
|
| 336 |
+
'MerchantHierarchyTree': ['TreeNode', 'TreeNodeActions'],
|
| 337 |
+
'MerchantListView': ['MerchantCard', 'FilterBar', 'Pagination'],
|
| 338 |
+
'MerchantFormModal': ['FormFields', 'ValidationErrors'],
|
| 339 |
+
'MerchantSettingsPanel': ['SettingsSections', 'OperatingHours']
|
| 340 |
+
},
|
| 341 |
+
'UserManagement': {
|
| 342 |
+
'UserListView': ['UserCard', 'RoleFilter'],
|
| 343 |
+
'UserFormModal': ['UserForm', 'RoleSelector'],
|
| 344 |
+
'PermissionsMatrix': ['RolePermissions']
|
| 345 |
+
}
|
| 346 |
+
}
|
| 347 |
+
```
|
| 348 |
+
|
| 349 |
+
## 📋 **Testing Checklist**
|
| 350 |
+
|
| 351 |
+
### 1. **API Integration Tests**
|
| 352 |
+
- [ ] Fetch merchants hierarchy successfully
|
| 353 |
+
- [ ] Create merchant with valid parent relationship
|
| 354 |
+
- [ ] Update merchant settings with validation
|
| 355 |
+
- [ ] Handle API errors gracefully
|
| 356 |
+
- [ ] Implement proper loading states
|
| 357 |
+
|
| 358 |
+
### 2. **UI Functionality Tests**
|
| 359 |
+
- [ ] Tree view expands/collapses correctly
|
| 360 |
+
- [ ] Form validation works client-side
|
| 361 |
+
- [ ] Filter/search functionality
|
| 362 |
+
- [ ] Responsive design on mobile
|
| 363 |
+
- [ ] Role-based feature visibility
|
| 364 |
+
|
| 365 |
+
### 3. **Business Logic Tests**
|
| 366 |
+
- [ ] CNF can only have Parent Company as parent
|
| 367 |
+
- [ ] Distributor can only have CNF as parent
|
| 368 |
+
- [ ] Salon can only have Distributor as parent
|
| 369 |
+
- [ ] Credit limits are enforced based on merchant type
|
| 370 |
+
- [ ] Operating hours format validation
|
| 371 |
+
|
| 372 |
+
## 🚀 **Development Workflow**
|
| 373 |
+
|
| 374 |
+
### 1. **Setup & Configuration**
|
| 375 |
+
```bash
|
| 376 |
+
# Start the API server
|
| 377 |
+
uvicorn app.main:app --reload --port 8000
|
| 378 |
+
|
| 379 |
+
# Access API documentation
|
| 380 |
+
open http://localhost:8000/docs
|
| 381 |
+
|
| 382 |
+
# Test API endpoints
|
| 383 |
+
curl -X GET "http://localhost:8000/merchants" -H "Content-Type: application/json"
|
| 384 |
+
```
|
| 385 |
+
|
| 386 |
+
### 2. **Implementation Order**
|
| 387 |
+
1. **Phase 1**: Basic merchant CRUD operations
|
| 388 |
+
2. **Phase 2**: Hierarchy tree view and navigation
|
| 389 |
+
3. **Phase 3**: Merchant settings management
|
| 390 |
+
4. **Phase 4**: User and role management
|
| 391 |
+
5. **Phase 5**: Advanced features (reports, analytics)
|
| 392 |
+
|
| 393 |
+
### 3. **Data Flow Examples**
|
| 394 |
+
```javascript
|
| 395 |
+
// Example: Creating a new salon
|
| 396 |
+
const createSalon = async (salonData) => {
|
| 397 |
+
// 1. Validate form data
|
| 398 |
+
const errors = validateMerchantForm(salonData)
|
| 399 |
+
if (Object.keys(errors).length > 0) {
|
| 400 |
+
return { success: false, errors }
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
// 2. Call API
|
| 404 |
+
const response = await fetch(`${API_BASE_URL}/merchants`, {
|
| 405 |
+
method: 'POST',
|
| 406 |
+
headers: apiHeaders,
|
| 407 |
+
body: JSON.stringify({...salonData, merchant_type: 'salon'})
|
| 408 |
+
})
|
| 409 |
+
|
| 410 |
+
// 3. Handle response
|
| 411 |
+
if (response.ok) {
|
| 412 |
+
const newSalon = await response.json()
|
| 413 |
+
// Update local state
|
| 414 |
+
addMerchantToStore(newSalon)
|
| 415 |
+
// Create default settings
|
| 416 |
+
await createDefaultSettings(newSalon.merchant_id)
|
| 417 |
+
return { success: true, data: newSalon }
|
| 418 |
+
} else {
|
| 419 |
+
const error = await response.json()
|
| 420 |
+
return { success: false, error: error.detail }
|
| 421 |
+
}
|
| 422 |
+
}
|
| 423 |
+
```
|
| 424 |
+
|
| 425 |
+
## 🔧 **Common Implementation Patterns**
|
| 426 |
+
|
| 427 |
+
### 1. **Autocomplete for Parent Selection**
|
| 428 |
+
```javascript
|
| 429 |
+
const ParentMerchantSelector = ({ merchantType, value, onChange }) => {
|
| 430 |
+
const [options, setOptions] = useState([])
|
| 431 |
+
|
| 432 |
+
useEffect(() => {
|
| 433 |
+
const loadParentOptions = async () => {
|
| 434 |
+
const parentType = getParentType(merchantType)
|
| 435 |
+
const response = await fetch(`${API_BASE_URL}/merchants?merchant_type=${parentType}`)
|
| 436 |
+
setOptions(await response.json())
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
if (merchantType) {
|
| 440 |
+
loadParentOptions()
|
| 441 |
+
}
|
| 442 |
+
}, [merchantType])
|
| 443 |
+
|
| 444 |
+
return (
|
| 445 |
+
<Autocomplete
|
| 446 |
+
options={options}
|
| 447 |
+
getOptionLabel={(option) => `${option.merchant_name} (${option.merchant_code})`}
|
| 448 |
+
value={value}
|
| 449 |
+
onChange={onChange}
|
| 450 |
+
placeholder="Select parent merchant..."
|
| 451 |
+
/>
|
| 452 |
+
)
|
| 453 |
+
}
|
| 454 |
+
```
|
| 455 |
+
|
| 456 |
+
### 2. **Operating Hours Component**
|
| 457 |
+
```javascript
|
| 458 |
+
const OperatingHoursEditor = ({ value, onChange }) => {
|
| 459 |
+
const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
|
| 460 |
+
|
| 461 |
+
return (
|
| 462 |
+
<div className="operating-hours-grid">
|
| 463 |
+
{days.map(day => (
|
| 464 |
+
<div key={day} className="day-row">
|
| 465 |
+
<label>{day.charAt(0).toUpperCase() + day.slice(1)}</label>
|
| 466 |
+
<input
|
| 467 |
+
type="checkbox"
|
| 468 |
+
checked={value[day]?.is_open || false}
|
| 469 |
+
onChange={(e) => onChange(day, 'is_open', e.target.checked)}
|
| 470 |
+
/>
|
| 471 |
+
{value[day]?.is_open && (
|
| 472 |
+
<>
|
| 473 |
+
<input
|
| 474 |
+
type="time"
|
| 475 |
+
value={value[day]?.open_time || '09:00'}
|
| 476 |
+
onChange={(e) => onChange(day, 'open_time', e.target.value)}
|
| 477 |
+
/>
|
| 478 |
+
<input
|
| 479 |
+
type="time"
|
| 480 |
+
value={value[day]?.close_time || '18:00'}
|
| 481 |
+
onChange={(e) => onChange(day, 'close_time', e.target.value)}
|
| 482 |
+
/>
|
| 483 |
+
</>
|
| 484 |
+
)}
|
| 485 |
+
</div>
|
| 486 |
+
))}
|
| 487 |
+
</div>
|
| 488 |
+
)
|
| 489 |
+
}
|
| 490 |
+
```
|
| 491 |
+
|
| 492 |
+
This prompt provides comprehensive guidance for building a production-ready UI that integrates seamlessly with your hierarchical merchant API system.
|
app/constants/collections.py
CHANGED
|
@@ -7,6 +7,7 @@ SCM_MERCHANTS_COLLECTION = "scm_merchants"
|
|
| 7 |
SCM_MERCHANT_SETTINGS_COLLECTION = "scm_merchant_settings"
|
| 8 |
SCM_EMPLOYEES_COLLECTION = "scm_employees"
|
| 9 |
SCM_ROLES_COLLECTION = "scm_roles"
|
|
|
|
| 10 |
SCM_AUTH_LOGS_COLLECTION = "scm_auth_logs"
|
| 11 |
SCM_SYSTEM_USERS_COLLECTION = "scm_system_users"
|
| 12 |
|
|
|
|
| 7 |
SCM_MERCHANT_SETTINGS_COLLECTION = "scm_merchant_settings"
|
| 8 |
SCM_EMPLOYEES_COLLECTION = "scm_employees"
|
| 9 |
SCM_ROLES_COLLECTION = "scm_roles"
|
| 10 |
+
SCM_ACCESS_ROLES_COLLECTION = "scm_access_roles"
|
| 11 |
SCM_AUTH_LOGS_COLLECTION = "scm_auth_logs"
|
| 12 |
SCM_SYSTEM_USERS_COLLECTION = "scm_system_users"
|
| 13 |
|
app/db_init.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Database initialization script for SCM microservice.
|
| 3 |
+
Creates all collections, indexes, and validation rules.
|
| 4 |
+
"""
|
| 5 |
+
import asyncio
|
| 6 |
+
from motor.motor_asyncio import AsyncIOMotorClient
|
| 7 |
+
from insightfy_utils.logging import get_logger
|
| 8 |
+
from app.core.config import settings
|
| 9 |
+
from app.db_init.merchant_settings_init import initialize_merchant_settings_database
|
| 10 |
+
|
| 11 |
+
logger = get_logger(__name__)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
async def initialize_database():
|
| 15 |
+
"""
|
| 16 |
+
Initialize the complete SCM database structure.
|
| 17 |
+
|
| 18 |
+
This script creates:
|
| 19 |
+
- Collections with schema validation
|
| 20 |
+
- Performance indexes
|
| 21 |
+
- Default data (if needed)
|
| 22 |
+
"""
|
| 23 |
+
client = None
|
| 24 |
+
try:
|
| 25 |
+
logger.info("Starting database initialization")
|
| 26 |
+
|
| 27 |
+
# Connect to MongoDB
|
| 28 |
+
logger.info(f"Connecting to MongoDB: {settings.MONGODB_URI}")
|
| 29 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 30 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 31 |
+
|
| 32 |
+
# Test connection
|
| 33 |
+
await client.admin.command('ping')
|
| 34 |
+
logger.info(f"Connected to database: {settings.MONGODB_DB_NAME}")
|
| 35 |
+
|
| 36 |
+
# Initialize merchant settings
|
| 37 |
+
await initialize_merchant_settings_database(db)
|
| 38 |
+
|
| 39 |
+
# Add other collection initializations here as needed
|
| 40 |
+
# await initialize_merchants_database(db)
|
| 41 |
+
# await initialize_employees_database(db)
|
| 42 |
+
# await initialize_orders_database(db)
|
| 43 |
+
|
| 44 |
+
logger.info("Database initialization completed successfully")
|
| 45 |
+
|
| 46 |
+
except Exception as e:
|
| 47 |
+
logger.error("Database initialization failed", exc_info=e)
|
| 48 |
+
raise
|
| 49 |
+
finally:
|
| 50 |
+
if client:
|
| 51 |
+
client.close()
|
| 52 |
+
logger.info("Database connection closed")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
async def verify_database_setup():
|
| 56 |
+
"""
|
| 57 |
+
Verify that the database setup is correct.
|
| 58 |
+
"""
|
| 59 |
+
client = None
|
| 60 |
+
try:
|
| 61 |
+
logger.info("Starting database verification")
|
| 62 |
+
|
| 63 |
+
# Connect to MongoDB
|
| 64 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 65 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 66 |
+
|
| 67 |
+
# Test connection
|
| 68 |
+
await client.admin.command('ping')
|
| 69 |
+
|
| 70 |
+
# List all collections
|
| 71 |
+
collections = await db.list_collection_names()
|
| 72 |
+
logger.info(f"Found collections: {collections}")
|
| 73 |
+
|
| 74 |
+
# Check merchant_settings collection
|
| 75 |
+
from app.constants.collections import SCM_MERCHANT_SETTINGS_COLLECTION
|
| 76 |
+
|
| 77 |
+
if SCM_MERCHANT_SETTINGS_COLLECTION in collections:
|
| 78 |
+
collection = db[SCM_MERCHANT_SETTINGS_COLLECTION]
|
| 79 |
+
|
| 80 |
+
# Check indexes
|
| 81 |
+
indexes = await collection.index_information()
|
| 82 |
+
logger.info(f"Merchant settings indexes: {list(indexes.keys())}")
|
| 83 |
+
|
| 84 |
+
# Check collection stats
|
| 85 |
+
stats = await db.command("collStats", SCM_MERCHANT_SETTINGS_COLLECTION)
|
| 86 |
+
logger.info(f"Merchant settings collection stats:", extra={
|
| 87 |
+
"count": stats.get("count", 0),
|
| 88 |
+
"size": stats.get("size", 0),
|
| 89 |
+
"indexes": stats.get("nindexes", 0)
|
| 90 |
+
})
|
| 91 |
+
|
| 92 |
+
logger.info("✅ Merchant settings collection is properly configured")
|
| 93 |
+
else:
|
| 94 |
+
logger.warning(f"❌ Collection {SCM_MERCHANT_SETTINGS_COLLECTION} not found")
|
| 95 |
+
|
| 96 |
+
logger.info("Database verification completed")
|
| 97 |
+
|
| 98 |
+
except Exception as e:
|
| 99 |
+
logger.error("Database verification failed", exc_info=e)
|
| 100 |
+
raise
|
| 101 |
+
finally:
|
| 102 |
+
if client:
|
| 103 |
+
client.close()
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
if __name__ == "__main__":
|
| 107 |
+
import sys
|
| 108 |
+
|
| 109 |
+
# Check command line arguments
|
| 110 |
+
if len(sys.argv) > 1:
|
| 111 |
+
command = sys.argv[1]
|
| 112 |
+
|
| 113 |
+
if command == "init":
|
| 114 |
+
print("🚀 Initializing database...")
|
| 115 |
+
asyncio.run(initialize_database())
|
| 116 |
+
print("✅ Database initialization completed")
|
| 117 |
+
|
| 118 |
+
elif command == "verify":
|
| 119 |
+
print("🔍 Verifying database setup...")
|
| 120 |
+
asyncio.run(verify_database_setup())
|
| 121 |
+
print("✅ Database verification completed")
|
| 122 |
+
|
| 123 |
+
else:
|
| 124 |
+
print(f"Unknown command: {command}")
|
| 125 |
+
print("Usage: python db_init.py [init|verify]")
|
| 126 |
+
sys.exit(1)
|
| 127 |
+
else:
|
| 128 |
+
print("Usage:")
|
| 129 |
+
print(" python db_init.py init - Initialize database structure")
|
| 130 |
+
print(" python db_init.py verify - Verify database setup")
|
| 131 |
+
sys.exit(1)
|
app/db_init/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Database initialization package for SCM microservice.
|
| 3 |
+
"""
|
app/db_init/merchant_settings_init.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Database initialization script for merchant settings collection.
|
| 3 |
+
Creates collections, indexes, and validation rules.
|
| 4 |
+
"""
|
| 5 |
+
from motor.motor_asyncio import AsyncIOMotorDatabase
|
| 6 |
+
from pymongo import IndexModel, ASCENDING, TEXT
|
| 7 |
+
from insightfy_utils.logging import get_logger
|
| 8 |
+
|
| 9 |
+
from app.constants.collections import SCM_MERCHANT_SETTINGS_COLLECTION
|
| 10 |
+
|
| 11 |
+
logger = get_logger(__name__)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
async def create_merchant_settings_collection(db: AsyncIOMotorDatabase):
|
| 15 |
+
"""
|
| 16 |
+
Create the merchant_settings collection with proper indexes and validation.
|
| 17 |
+
|
| 18 |
+
Args:
|
| 19 |
+
db: MongoDB database instance
|
| 20 |
+
"""
|
| 21 |
+
try:
|
| 22 |
+
collection_name = SCM_MERCHANT_SETTINGS_COLLECTION
|
| 23 |
+
|
| 24 |
+
# Check if collection already exists
|
| 25 |
+
if collection_name in await db.list_collection_names():
|
| 26 |
+
logger.info(f"Collection {collection_name} already exists")
|
| 27 |
+
return
|
| 28 |
+
|
| 29 |
+
# Create collection with schema validation
|
| 30 |
+
validation_schema = {
|
| 31 |
+
"$jsonSchema": {
|
| 32 |
+
"bsonType": "object",
|
| 33 |
+
"required": ["merchant_id", "created_by", "created_at", "is_active"],
|
| 34 |
+
"properties": {
|
| 35 |
+
"merchant_id": {
|
| 36 |
+
"bsonType": "string",
|
| 37 |
+
"description": "Unique merchant identifier - required"
|
| 38 |
+
},
|
| 39 |
+
"auto_allocate_stock": {
|
| 40 |
+
"bsonType": "bool",
|
| 41 |
+
"description": "Whether to automatically allocate stock on orders"
|
| 42 |
+
},
|
| 43 |
+
"credit_limit": {
|
| 44 |
+
"bsonType": ["double", "null"],
|
| 45 |
+
"minimum": 0,
|
| 46 |
+
"description": "Credit limit in base currency"
|
| 47 |
+
},
|
| 48 |
+
"payment_terms_days": {
|
| 49 |
+
"bsonType": "int",
|
| 50 |
+
"minimum": 0,
|
| 51 |
+
"maximum": 365,
|
| 52 |
+
"description": "Payment terms in days"
|
| 53 |
+
},
|
| 54 |
+
"low_stock_threshold": {
|
| 55 |
+
"bsonType": "int",
|
| 56 |
+
"minimum": 0,
|
| 57 |
+
"description": "Low stock alert threshold"
|
| 58 |
+
},
|
| 59 |
+
"enable_backorder": {
|
| 60 |
+
"bsonType": "bool",
|
| 61 |
+
"description": "Allow backorders when out of stock"
|
| 62 |
+
},
|
| 63 |
+
"auto_reorder": {
|
| 64 |
+
"bsonType": "bool",
|
| 65 |
+
"description": "Automatically create reorder when stock is low"
|
| 66 |
+
},
|
| 67 |
+
"email_notifications": {
|
| 68 |
+
"bsonType": "bool",
|
| 69 |
+
"description": "Enable email notifications"
|
| 70 |
+
},
|
| 71 |
+
"sms_notifications": {
|
| 72 |
+
"bsonType": "bool",
|
| 73 |
+
"description": "Enable SMS notifications"
|
| 74 |
+
},
|
| 75 |
+
"low_stock_alerts": {
|
| 76 |
+
"bsonType": "bool",
|
| 77 |
+
"description": "Enable low stock alerts"
|
| 78 |
+
},
|
| 79 |
+
"order_status_updates": {
|
| 80 |
+
"bsonType": "bool",
|
| 81 |
+
"description": "Send order status update notifications"
|
| 82 |
+
},
|
| 83 |
+
"operating_hours": {
|
| 84 |
+
"bsonType": ["object", "null"],
|
| 85 |
+
"description": "Operating hours configuration by day"
|
| 86 |
+
},
|
| 87 |
+
"default_markup_percentage": {
|
| 88 |
+
"bsonType": ["double", "null"],
|
| 89 |
+
"minimum": 0,
|
| 90 |
+
"maximum": 1000,
|
| 91 |
+
"description": "Default markup percentage"
|
| 92 |
+
},
|
| 93 |
+
"allow_discount": {
|
| 94 |
+
"bsonType": "bool",
|
| 95 |
+
"description": "Allow discounts on orders"
|
| 96 |
+
},
|
| 97 |
+
"max_discount_percentage": {
|
| 98 |
+
"bsonType": ["double", "null"],
|
| 99 |
+
"minimum": 0,
|
| 100 |
+
"maximum": 100,
|
| 101 |
+
"description": "Maximum discount percentage allowed"
|
| 102 |
+
},
|
| 103 |
+
"tax_settings": {
|
| 104 |
+
"bsonType": ["object", "null"],
|
| 105 |
+
"description": "Tax configuration"
|
| 106 |
+
},
|
| 107 |
+
"is_active": {
|
| 108 |
+
"bsonType": "bool",
|
| 109 |
+
"description": "Whether settings are active - required"
|
| 110 |
+
},
|
| 111 |
+
"created_by": {
|
| 112 |
+
"bsonType": "string",
|
| 113 |
+
"description": "User ID who created these settings - required"
|
| 114 |
+
},
|
| 115 |
+
"created_at": {
|
| 116 |
+
"bsonType": "date",
|
| 117 |
+
"description": "Creation timestamp - required"
|
| 118 |
+
},
|
| 119 |
+
"updated_by": {
|
| 120 |
+
"bsonType": ["string", "null"],
|
| 121 |
+
"description": "User ID who last updated these settings"
|
| 122 |
+
},
|
| 123 |
+
"updated_at": {
|
| 124 |
+
"bsonType": ["date", "null"],
|
| 125 |
+
"description": "Last update timestamp"
|
| 126 |
+
},
|
| 127 |
+
"custom_fields": {
|
| 128 |
+
"bsonType": ["object", "null"],
|
| 129 |
+
"description": "Custom configuration fields"
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
# Create collection with validation
|
| 136 |
+
await db.create_collection(
|
| 137 |
+
collection_name,
|
| 138 |
+
validator=validation_schema,
|
| 139 |
+
validationLevel="moderate", # Allow updates that don't include all required fields
|
| 140 |
+
validationAction="error" # Reject documents that don't match schema
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
logger.info(f"Created collection {collection_name} with schema validation")
|
| 144 |
+
|
| 145 |
+
# Create indexes for performance
|
| 146 |
+
indexes = [
|
| 147 |
+
# Unique index on merchant_id (one settings record per merchant)
|
| 148 |
+
IndexModel([("merchant_id", ASCENDING)], unique=True, name="merchant_id_unique"),
|
| 149 |
+
|
| 150 |
+
# Index on is_active for filtering active/inactive settings
|
| 151 |
+
IndexModel([("is_active", ASCENDING)], name="is_active_index"),
|
| 152 |
+
|
| 153 |
+
# Compound index for active settings queries
|
| 154 |
+
IndexModel([("is_active", ASCENDING), ("merchant_id", ASCENDING)], name="active_merchant_index"),
|
| 155 |
+
|
| 156 |
+
# Index on created_at for sorting by creation time
|
| 157 |
+
IndexModel([("created_at", ASCENDING)], name="created_at_index"),
|
| 158 |
+
|
| 159 |
+
# Index on updated_at for sorting by update time
|
| 160 |
+
IndexModel([("updated_at", ASCENDING)], name="updated_at_index"),
|
| 161 |
+
|
| 162 |
+
# Index on created_by for audit queries
|
| 163 |
+
IndexModel([("created_by", ASCENDING)], name="created_by_index"),
|
| 164 |
+
|
| 165 |
+
# Index on updated_by for audit queries
|
| 166 |
+
IndexModel([("updated_by", ASCENDING)], name="updated_by_index"),
|
| 167 |
+
|
| 168 |
+
# Compound index for audit queries
|
| 169 |
+
IndexModel([("created_by", ASCENDING), ("created_at", ASCENDING)], name="audit_created_index"),
|
| 170 |
+
|
| 171 |
+
# Index for notification settings queries
|
| 172 |
+
IndexModel([
|
| 173 |
+
("email_notifications", ASCENDING),
|
| 174 |
+
("sms_notifications", ASCENDING),
|
| 175 |
+
("is_active", ASCENDING)
|
| 176 |
+
], name="notifications_index"),
|
| 177 |
+
|
| 178 |
+
# Index for inventory settings queries
|
| 179 |
+
IndexModel([
|
| 180 |
+
("auto_allocate_stock", ASCENDING),
|
| 181 |
+
("enable_backorder", ASCENDING),
|
| 182 |
+
("auto_reorder", ASCENDING),
|
| 183 |
+
("is_active", ASCENDING)
|
| 184 |
+
], name="inventory_settings_index")
|
| 185 |
+
]
|
| 186 |
+
|
| 187 |
+
# Create all indexes
|
| 188 |
+
collection = db[collection_name]
|
| 189 |
+
await collection.create_indexes(indexes)
|
| 190 |
+
|
| 191 |
+
logger.info(f"Created {len(indexes)} indexes for {collection_name}")
|
| 192 |
+
|
| 193 |
+
# Log index information
|
| 194 |
+
index_info = await collection.index_information()
|
| 195 |
+
logger.info(f"Indexes created for {collection_name}:", extra={
|
| 196 |
+
"indexes": list(index_info.keys())
|
| 197 |
+
})
|
| 198 |
+
|
| 199 |
+
except Exception as e:
|
| 200 |
+
logger.error(f"Error creating merchant_settings collection: {e}", exc_info=e)
|
| 201 |
+
raise
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
async def initialize_merchant_settings_database(db: AsyncIOMotorDatabase):
|
| 205 |
+
"""
|
| 206 |
+
Initialize the complete merchant settings database structure.
|
| 207 |
+
|
| 208 |
+
Args:
|
| 209 |
+
db: MongoDB database instance
|
| 210 |
+
"""
|
| 211 |
+
try:
|
| 212 |
+
logger.info("Initializing merchant settings database structure")
|
| 213 |
+
|
| 214 |
+
# Create merchant settings collection
|
| 215 |
+
await create_merchant_settings_collection(db)
|
| 216 |
+
|
| 217 |
+
# Add any default/sample data if needed (optional)
|
| 218 |
+
# await create_default_settings(db)
|
| 219 |
+
|
| 220 |
+
logger.info("Merchant settings database initialization completed successfully")
|
| 221 |
+
|
| 222 |
+
except Exception as e:
|
| 223 |
+
logger.error("Failed to initialize merchant settings database", exc_info=e)
|
| 224 |
+
raise
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
async def create_default_settings(db: AsyncIOMotorDatabase):
|
| 228 |
+
"""
|
| 229 |
+
Create default merchant settings for existing merchants (optional).
|
| 230 |
+
This can be used for migration purposes.
|
| 231 |
+
|
| 232 |
+
Args:
|
| 233 |
+
db: MongoDB database instance
|
| 234 |
+
"""
|
| 235 |
+
try:
|
| 236 |
+
from datetime import datetime
|
| 237 |
+
from app.constants.collections import SCM_MERCHANTS_COLLECTION
|
| 238 |
+
|
| 239 |
+
collection = db[SCM_MERCHANT_SETTINGS_COLLECTION]
|
| 240 |
+
merchants_collection = db[SCM_MERCHANTS_COLLECTION]
|
| 241 |
+
|
| 242 |
+
# Get merchants that don't have settings yet
|
| 243 |
+
merchants_without_settings = []
|
| 244 |
+
async for merchant in merchants_collection.find({"status": "active"}):
|
| 245 |
+
existing_settings = await collection.find_one({"merchant_id": merchant["merchant_id"]})
|
| 246 |
+
if not existing_settings:
|
| 247 |
+
merchants_without_settings.append(merchant)
|
| 248 |
+
|
| 249 |
+
if not merchants_without_settings:
|
| 250 |
+
logger.info("All active merchants already have settings")
|
| 251 |
+
return
|
| 252 |
+
|
| 253 |
+
# Create default settings for merchants without settings
|
| 254 |
+
default_settings_template = {
|
| 255 |
+
"auto_allocate_stock": False,
|
| 256 |
+
"credit_limit": None,
|
| 257 |
+
"payment_terms_days": 30,
|
| 258 |
+
"low_stock_threshold": 10,
|
| 259 |
+
"enable_backorder": False,
|
| 260 |
+
"auto_reorder": False,
|
| 261 |
+
"email_notifications": True,
|
| 262 |
+
"sms_notifications": False,
|
| 263 |
+
"low_stock_alerts": True,
|
| 264 |
+
"order_status_updates": True,
|
| 265 |
+
"operating_hours": {
|
| 266 |
+
"monday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 267 |
+
"tuesday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 268 |
+
"wednesday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 269 |
+
"thursday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 270 |
+
"friday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 271 |
+
"saturday": {"is_open": True, "open_time": "09:00", "close_time": "14:00"},
|
| 272 |
+
"sunday": {"is_open": False}
|
| 273 |
+
},
|
| 274 |
+
"default_markup_percentage": None,
|
| 275 |
+
"allow_discount": True,
|
| 276 |
+
"max_discount_percentage": None,
|
| 277 |
+
"tax_settings": {
|
| 278 |
+
"gst_rate": 18.0,
|
| 279 |
+
"inclusive_pricing": True,
|
| 280 |
+
"tax_exemption": False
|
| 281 |
+
},
|
| 282 |
+
"is_active": True,
|
| 283 |
+
"created_by": "system_migration",
|
| 284 |
+
"created_at": datetime.utcnow(),
|
| 285 |
+
"updated_by": None,
|
| 286 |
+
"updated_at": None,
|
| 287 |
+
"custom_fields": None
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
# Insert default settings for each merchant
|
| 291 |
+
settings_to_insert = []
|
| 292 |
+
for merchant in merchants_without_settings:
|
| 293 |
+
settings = default_settings_template.copy()
|
| 294 |
+
settings["merchant_id"] = merchant["merchant_id"]
|
| 295 |
+
settings_to_insert.append(settings)
|
| 296 |
+
|
| 297 |
+
if settings_to_insert:
|
| 298 |
+
result = await collection.insert_many(settings_to_insert)
|
| 299 |
+
logger.info(f"Created default settings for {len(result.inserted_ids)} merchants")
|
| 300 |
+
|
| 301 |
+
except Exception as e:
|
| 302 |
+
logger.error("Error creating default settings", exc_info=e)
|
| 303 |
+
raise
|
create_hierarchy.py
ADDED
|
@@ -0,0 +1,1122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hierarchical merchant data creation script.
|
| 3 |
+
Creates a complete merchant ecosystem: Parent Company -> CNF -> Distributors -> Salons
|
| 4 |
+
with corresponding users, roles, and settings.
|
| 5 |
+
"""
|
| 6 |
+
import asyncio
|
| 7 |
+
from datetime import datetime, timedelta
|
| 8 |
+
from motor.motor_asyncio import AsyncIOMotorClient
|
| 9 |
+
from insightfy_utils.logging import get_logger
|
| 10 |
+
import sys
|
| 11 |
+
import os
|
| 12 |
+
import hashlib
|
| 13 |
+
import secrets
|
| 14 |
+
|
| 15 |
+
# Add the current directory to Python path
|
| 16 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 17 |
+
|
| 18 |
+
from app.core.config import settings
|
| 19 |
+
from app.constants.collections import (
|
| 20 |
+
SCM_MERCHANTS_COLLECTION,
|
| 21 |
+
SCM_MERCHANT_SETTINGS_COLLECTION,
|
| 22 |
+
SCM_SYSTEM_USERS_COLLECTION,
|
| 23 |
+
SCM_ACCESS_ROLES_COLLECTION
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
logger = get_logger(__name__)
|
| 27 |
+
|
| 28 |
+
def generate_merchant_id(prefix: str) -> str:
|
| 29 |
+
"""Generate a unique merchant ID with prefix."""
|
| 30 |
+
return f"{prefix}_{secrets.token_urlsafe(8)}"
|
| 31 |
+
|
| 32 |
+
def generate_user_id() -> str:
|
| 33 |
+
"""Generate a unique user ID."""
|
| 34 |
+
return f"user_{secrets.token_urlsafe(8)}"
|
| 35 |
+
|
| 36 |
+
def hash_password(password: str) -> str:
|
| 37 |
+
"""Hash password for storage."""
|
| 38 |
+
return hashlib.sha256(password.encode()).hexdigest()
|
| 39 |
+
|
| 40 |
+
# Merchant hierarchy data
|
| 41 |
+
MERCHANT_HIERARCHY = {
|
| 42 |
+
"parent_company": {
|
| 43 |
+
"merchant_id": "company_cuatro_beauty_ltd",
|
| 44 |
+
"merchant_type": "parent_company",
|
| 45 |
+
"merchant_code": "CUATRO-HQ",
|
| 46 |
+
"merchant_name": "Cuatro Beauty Limited",
|
| 47 |
+
"parent_merchant_id": None,
|
| 48 |
+
"contact": {
|
| 49 |
+
"phone": "+911234567890",
|
| 50 |
+
"email": "info@cuatrobeauty.com",
|
| 51 |
+
"address_line1": "Cuatro Beauty Tower, Business District",
|
| 52 |
+
"city": "Mumbai",
|
| 53 |
+
"state": "Maharashtra",
|
| 54 |
+
"pincode": "400001",
|
| 55 |
+
"country": "India"
|
| 56 |
+
},
|
| 57 |
+
"geo_location": {"lat": 19.0760, "lng": 72.8777},
|
| 58 |
+
"kyc": {
|
| 59 |
+
"gst_number": "27AAPCU0939F1ZV",
|
| 60 |
+
"pan_number": "AAPCU0939F",
|
| 61 |
+
"bank_account_number": "1234567890123456",
|
| 62 |
+
"bank_ifsc": "HDFC0001234"
|
| 63 |
+
},
|
| 64 |
+
"status": "active",
|
| 65 |
+
"created_by": "system_admin"
|
| 66 |
+
},
|
| 67 |
+
"cnfs": [
|
| 68 |
+
{
|
| 69 |
+
"merchant_id": "cnf_north_india_hub",
|
| 70 |
+
"merchant_type": "cnf",
|
| 71 |
+
"merchant_code": "CNF-NORTH-001",
|
| 72 |
+
"merchant_name": "North India Beauty Hub CNF",
|
| 73 |
+
"parent_merchant_id": "company_cuatro_beauty_ltd",
|
| 74 |
+
"contact": {
|
| 75 |
+
"phone": "+911987654321",
|
| 76 |
+
"email": "north@cuatrobeauty.com",
|
| 77 |
+
"address_line1": "Industrial Area Phase-1, Sector 58",
|
| 78 |
+
"city": "Gurgaon",
|
| 79 |
+
"state": "Haryana",
|
| 80 |
+
"pincode": "122001",
|
| 81 |
+
"country": "India"
|
| 82 |
+
},
|
| 83 |
+
"geo_location": {"lat": 28.4595, "lng": 77.0266},
|
| 84 |
+
"kyc": {
|
| 85 |
+
"gst_number": "06AAPCU0939F1ZW",
|
| 86 |
+
"pan_number": "AAPCU0939G",
|
| 87 |
+
"bank_account_number": "2345678901234567",
|
| 88 |
+
"bank_ifsc": "ICIC0001235"
|
| 89 |
+
},
|
| 90 |
+
"status": "active",
|
| 91 |
+
"created_by": "head_office"
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"merchant_id": "cnf_south_india_hub",
|
| 95 |
+
"merchant_type": "cnf",
|
| 96 |
+
"merchant_code": "CNF-SOUTH-001",
|
| 97 |
+
"merchant_name": "South India Beauty Hub CNF",
|
| 98 |
+
"parent_merchant_id": "company_cuatro_beauty_ltd",
|
| 99 |
+
"contact": {
|
| 100 |
+
"phone": "+914412345678",
|
| 101 |
+
"email": "south@cuatrobeauty.com",
|
| 102 |
+
"address_line1": "Ambattur Industrial Estate",
|
| 103 |
+
"city": "Chennai",
|
| 104 |
+
"state": "Tamil Nadu",
|
| 105 |
+
"pincode": "600058",
|
| 106 |
+
"country": "India"
|
| 107 |
+
},
|
| 108 |
+
"geo_location": {"lat": 13.0827, "lng": 80.2707},
|
| 109 |
+
"kyc": {
|
| 110 |
+
"gst_number": "33AAPCU0939F1ZX",
|
| 111 |
+
"pan_number": "AAPCU0939H",
|
| 112 |
+
"bank_account_number": "3456789012345678",
|
| 113 |
+
"bank_ifsc": "AXIS0001236"
|
| 114 |
+
},
|
| 115 |
+
"status": "active",
|
| 116 |
+
"created_by": "head_office"
|
| 117 |
+
},
|
| 118 |
+
{
|
| 119 |
+
"merchant_id": "cnf_west_india_hub",
|
| 120 |
+
"merchant_type": "cnf",
|
| 121 |
+
"merchant_code": "CNF-WEST-001",
|
| 122 |
+
"merchant_name": "West India Beauty Hub CNF",
|
| 123 |
+
"parent_merchant_id": "company_cuatro_beauty_ltd",
|
| 124 |
+
"contact": {
|
| 125 |
+
"phone": "+912223456789",
|
| 126 |
+
"email": "west@cuatrobeauty.com",
|
| 127 |
+
"address_line1": "MIDC Industrial Area, Andheri East",
|
| 128 |
+
"city": "Mumbai",
|
| 129 |
+
"state": "Maharashtra",
|
| 130 |
+
"pincode": "400093",
|
| 131 |
+
"country": "India"
|
| 132 |
+
},
|
| 133 |
+
"geo_location": {"lat": 19.1136, "lng": 72.8697},
|
| 134 |
+
"kyc": {
|
| 135 |
+
"gst_number": "27AAPCU0939F1ZY",
|
| 136 |
+
"pan_number": "AAPCU0939I",
|
| 137 |
+
"bank_account_number": "4567890123456789",
|
| 138 |
+
"bank_ifsc": "SBIN0001237"
|
| 139 |
+
},
|
| 140 |
+
"status": "active",
|
| 141 |
+
"created_by": "head_office"
|
| 142 |
+
}
|
| 143 |
+
],
|
| 144 |
+
"distributors": [
|
| 145 |
+
# North India CNF Distributors
|
| 146 |
+
{
|
| 147 |
+
"merchant_id": "dist_delhi_premium",
|
| 148 |
+
"merchant_type": "distributor",
|
| 149 |
+
"merchant_code": "DIST-DEL-001",
|
| 150 |
+
"merchant_name": "Delhi Premium Beauty Distributors",
|
| 151 |
+
"parent_merchant_id": "cnf_north_india_hub",
|
| 152 |
+
"contact": {
|
| 153 |
+
"phone": "+911134567890",
|
| 154 |
+
"email": "delhi@premiumbeauty.com",
|
| 155 |
+
"address_line1": "Karol Bagh Market Complex",
|
| 156 |
+
"city": "New Delhi",
|
| 157 |
+
"state": "Delhi",
|
| 158 |
+
"pincode": "110005",
|
| 159 |
+
"country": "India"
|
| 160 |
+
},
|
| 161 |
+
"geo_location": {"lat": 28.6519, "lng": 77.1909},
|
| 162 |
+
"kyc": {
|
| 163 |
+
"gst_number": "07AAPDE0939F1ZA",
|
| 164 |
+
"pan_number": "AAPDE0939A",
|
| 165 |
+
"bank_account_number": "5678901234567890",
|
| 166 |
+
"bank_ifsc": "HDFC0002345"
|
| 167 |
+
},
|
| 168 |
+
"status": "active",
|
| 169 |
+
"created_by": "cnf_manager_north"
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"merchant_id": "dist_punjab_wholesale",
|
| 173 |
+
"merchant_type": "distributor",
|
| 174 |
+
"merchant_code": "DIST-PB-001",
|
| 175 |
+
"merchant_name": "Punjab Beauty Wholesale Hub",
|
| 176 |
+
"parent_merchant_id": "cnf_north_india_hub",
|
| 177 |
+
"contact": {
|
| 178 |
+
"phone": "+911614567890",
|
| 179 |
+
"email": "punjab@wholesalebeauty.com",
|
| 180 |
+
"address_line1": "Model Town Extension",
|
| 181 |
+
"city": "Ludhiana",
|
| 182 |
+
"state": "Punjab",
|
| 183 |
+
"pincode": "141002",
|
| 184 |
+
"country": "India"
|
| 185 |
+
},
|
| 186 |
+
"geo_location": {"lat": 30.9010, "lng": 75.8573},
|
| 187 |
+
"kyc": {
|
| 188 |
+
"gst_number": "03AAPPB0939F1ZB",
|
| 189 |
+
"pan_number": "AAPPB0939B",
|
| 190 |
+
"bank_account_number": "6789012345678901",
|
| 191 |
+
"bank_ifsc": "PUNB0003456"
|
| 192 |
+
},
|
| 193 |
+
"status": "active",
|
| 194 |
+
"created_by": "cnf_manager_north"
|
| 195 |
+
},
|
| 196 |
+
# South India CNF Distributors
|
| 197 |
+
{
|
| 198 |
+
"merchant_id": "dist_chennai_elite",
|
| 199 |
+
"merchant_type": "distributor",
|
| 200 |
+
"merchant_code": "DIST-CHN-001",
|
| 201 |
+
"merchant_name": "Chennai Elite Beauty Distribution",
|
| 202 |
+
"parent_merchant_id": "cnf_south_india_hub",
|
| 203 |
+
"contact": {
|
| 204 |
+
"phone": "+914425678901",
|
| 205 |
+
"email": "chennai@elitebeauty.com",
|
| 206 |
+
"address_line1": "T. Nagar Commercial Complex",
|
| 207 |
+
"city": "Chennai",
|
| 208 |
+
"state": "Tamil Nadu",
|
| 209 |
+
"pincode": "600017",
|
| 210 |
+
"country": "India"
|
| 211 |
+
},
|
| 212 |
+
"geo_location": {"lat": 13.0418, "lng": 80.2341},
|
| 213 |
+
"kyc": {
|
| 214 |
+
"gst_number": "33AAPCH0939F1ZC",
|
| 215 |
+
"pan_number": "AAPCH0939C",
|
| 216 |
+
"bank_account_number": "7890123456789012",
|
| 217 |
+
"bank_ifsc": "ICIC0004567"
|
| 218 |
+
},
|
| 219 |
+
"status": "active",
|
| 220 |
+
"created_by": "cnf_manager_south"
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"merchant_id": "dist_bangalore_metro",
|
| 224 |
+
"merchant_type": "distributor",
|
| 225 |
+
"merchant_code": "DIST-BLR-001",
|
| 226 |
+
"merchant_name": "Bangalore Metro Beauty Supplies",
|
| 227 |
+
"parent_merchant_id": "cnf_south_india_hub",
|
| 228 |
+
"contact": {
|
| 229 |
+
"phone": "+918036789012",
|
| 230 |
+
"email": "bangalore@metrobeauty.com",
|
| 231 |
+
"address_line1": "Commercial Street",
|
| 232 |
+
"city": "Bangalore",
|
| 233 |
+
"state": "Karnataka",
|
| 234 |
+
"pincode": "560001",
|
| 235 |
+
"country": "India"
|
| 236 |
+
},
|
| 237 |
+
"geo_location": {"lat": 12.9716, "lng": 77.5946},
|
| 238 |
+
"kyc": {
|
| 239 |
+
"gst_number": "29AAPBL0939F1ZD",
|
| 240 |
+
"pan_number": "AAPBL0939D",
|
| 241 |
+
"bank_account_number": "8901234567890123",
|
| 242 |
+
"bank_ifsc": "AXIS0005678"
|
| 243 |
+
},
|
| 244 |
+
"status": "active",
|
| 245 |
+
"created_by": "cnf_manager_south"
|
| 246 |
+
},
|
| 247 |
+
# West India CNF Distributors
|
| 248 |
+
{
|
| 249 |
+
"merchant_id": "dist_mumbai_central",
|
| 250 |
+
"merchant_type": "distributor",
|
| 251 |
+
"merchant_code": "DIST-MUM-001",
|
| 252 |
+
"merchant_name": "Mumbai Central Beauty Distribution",
|
| 253 |
+
"parent_merchant_id": "cnf_west_india_hub",
|
| 254 |
+
"contact": {
|
| 255 |
+
"phone": "+912227890123",
|
| 256 |
+
"email": "mumbai@centralbeauty.com",
|
| 257 |
+
"address_line1": "Crawford Market Area",
|
| 258 |
+
"city": "Mumbai",
|
| 259 |
+
"state": "Maharashtra",
|
| 260 |
+
"pincode": "400001",
|
| 261 |
+
"country": "India"
|
| 262 |
+
},
|
| 263 |
+
"geo_location": {"lat": 18.9388, "lng": 72.8261},
|
| 264 |
+
"kyc": {
|
| 265 |
+
"gst_number": "27AAPMM0939F1ZE",
|
| 266 |
+
"pan_number": "AAPMM0939E",
|
| 267 |
+
"bank_account_number": "9012345678901234",
|
| 268 |
+
"bank_ifsc": "SBIN0006789"
|
| 269 |
+
},
|
| 270 |
+
"status": "active",
|
| 271 |
+
"created_by": "cnf_manager_west"
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"merchant_id": "dist_pune_premium",
|
| 275 |
+
"merchant_type": "distributor",
|
| 276 |
+
"merchant_code": "DIST-PUN-001",
|
| 277 |
+
"merchant_name": "Pune Premium Beauty Hub",
|
| 278 |
+
"parent_merchant_id": "cnf_west_india_hub",
|
| 279 |
+
"contact": {
|
| 280 |
+
"phone": "+912038901234",
|
| 281 |
+
"email": "pune@premiumhub.com",
|
| 282 |
+
"address_line1": "FC Road Commercial Area",
|
| 283 |
+
"city": "Pune",
|
| 284 |
+
"state": "Maharashtra",
|
| 285 |
+
"pincode": "411004",
|
| 286 |
+
"country": "India"
|
| 287 |
+
},
|
| 288 |
+
"geo_location": {"lat": 18.5204, "lng": 73.8567},
|
| 289 |
+
"kyc": {
|
| 290 |
+
"gst_number": "27AAPPU0939F1ZF",
|
| 291 |
+
"pan_number": "AAPPU0939F",
|
| 292 |
+
"bank_account_number": "0123456789012345",
|
| 293 |
+
"bank_ifsc": "HDFC0007890"
|
| 294 |
+
},
|
| 295 |
+
"status": "active",
|
| 296 |
+
"created_by": "cnf_manager_west"
|
| 297 |
+
}
|
| 298 |
+
],
|
| 299 |
+
"salons": [
|
| 300 |
+
# Delhi Distributor Salons
|
| 301 |
+
{
|
| 302 |
+
"merchant_id": "salon_delhi_cp_luxury",
|
| 303 |
+
"merchant_type": "salon",
|
| 304 |
+
"merchant_code": "SALON-DEL-001",
|
| 305 |
+
"merchant_name": "Connaught Place Luxury Salon",
|
| 306 |
+
"parent_merchant_id": "dist_delhi_premium",
|
| 307 |
+
"contact": {
|
| 308 |
+
"phone": "+911145678901",
|
| 309 |
+
"email": "cp@luxurysalon.com",
|
| 310 |
+
"address_line1": "Connaught Place, Block A",
|
| 311 |
+
"city": "New Delhi",
|
| 312 |
+
"state": "Delhi",
|
| 313 |
+
"pincode": "110001",
|
| 314 |
+
"country": "India"
|
| 315 |
+
},
|
| 316 |
+
"geo_location": {"lat": 28.6304, "lng": 77.2177},
|
| 317 |
+
"kyc": {"pan_number": "AALCP0939A"},
|
| 318 |
+
"status": "active",
|
| 319 |
+
"created_by": "dist_manager_delhi"
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"merchant_id": "salon_delhi_khan_market",
|
| 323 |
+
"merchant_type": "salon",
|
| 324 |
+
"merchant_code": "SALON-DEL-002",
|
| 325 |
+
"merchant_name": "Khan Market Beauty Studio",
|
| 326 |
+
"parent_merchant_id": "dist_delhi_premium",
|
| 327 |
+
"contact": {
|
| 328 |
+
"phone": "+911156789012",
|
| 329 |
+
"email": "khan@beautystudio.com",
|
| 330 |
+
"address_line1": "Khan Market, Middle Lane",
|
| 331 |
+
"city": "New Delhi",
|
| 332 |
+
"state": "Delhi",
|
| 333 |
+
"pincode": "110003",
|
| 334 |
+
"country": "India"
|
| 335 |
+
},
|
| 336 |
+
"geo_location": {"lat": 28.5982, "lng": 77.2294},
|
| 337 |
+
"kyc": {"pan_number": "AALKM0939B"},
|
| 338 |
+
"status": "active",
|
| 339 |
+
"created_by": "dist_manager_delhi"
|
| 340 |
+
},
|
| 341 |
+
# Punjab Distributor Salons
|
| 342 |
+
{
|
| 343 |
+
"merchant_id": "salon_ludhiana_model_town",
|
| 344 |
+
"merchant_type": "salon",
|
| 345 |
+
"merchant_code": "SALON-LDH-001",
|
| 346 |
+
"merchant_name": "Model Town Beauty Parlour",
|
| 347 |
+
"parent_merchant_id": "dist_punjab_wholesale",
|
| 348 |
+
"contact": {
|
| 349 |
+
"phone": "+911667890123",
|
| 350 |
+
"email": "model@beautyparour.com",
|
| 351 |
+
"address_line1": "Model Town Market",
|
| 352 |
+
"city": "Ludhiana",
|
| 353 |
+
"state": "Punjab",
|
| 354 |
+
"pincode": "141002",
|
| 355 |
+
"country": "India"
|
| 356 |
+
},
|
| 357 |
+
"geo_location": {"lat": 30.9000, "lng": 75.8500},
|
| 358 |
+
"kyc": {"pan_number": "AALMT0939C"},
|
| 359 |
+
"status": "active",
|
| 360 |
+
"created_by": "dist_manager_punjab"
|
| 361 |
+
},
|
| 362 |
+
# Chennai Distributor Salons
|
| 363 |
+
{
|
| 364 |
+
"merchant_id": "salon_chennai_tnagar",
|
| 365 |
+
"merchant_type": "salon",
|
| 366 |
+
"merchant_code": "SALON-CHN-001",
|
| 367 |
+
"merchant_name": "T.Nagar Glamour Studio",
|
| 368 |
+
"parent_merchant_id": "dist_chennai_elite",
|
| 369 |
+
"contact": {
|
| 370 |
+
"phone": "+914478901234",
|
| 371 |
+
"email": "tnagar@glamourstudio.com",
|
| 372 |
+
"address_line1": "Ranganathan Street",
|
| 373 |
+
"city": "Chennai",
|
| 374 |
+
"state": "Tamil Nadu",
|
| 375 |
+
"pincode": "600017",
|
| 376 |
+
"country": "India"
|
| 377 |
+
},
|
| 378 |
+
"geo_location": {"lat": 13.0435, "lng": 80.2349},
|
| 379 |
+
"kyc": {"pan_number": "AALTG0939D"},
|
| 380 |
+
"status": "active",
|
| 381 |
+
"created_by": "dist_manager_chennai"
|
| 382 |
+
},
|
| 383 |
+
{
|
| 384 |
+
"merchant_id": "salon_chennai_express_avenue",
|
| 385 |
+
"merchant_type": "salon",
|
| 386 |
+
"merchant_code": "SALON-CHN-002",
|
| 387 |
+
"merchant_name": "Express Avenue Beauty Lounge",
|
| 388 |
+
"parent_merchant_id": "dist_chennai_elite",
|
| 389 |
+
"contact": {
|
| 390 |
+
"phone": "+914489012345",
|
| 391 |
+
"email": "express@beautylounge.com",
|
| 392 |
+
"address_line1": "Express Avenue Mall, 2nd Floor",
|
| 393 |
+
"city": "Chennai",
|
| 394 |
+
"state": "Tamil Nadu",
|
| 395 |
+
"pincode": "600002",
|
| 396 |
+
"country": "India"
|
| 397 |
+
},
|
| 398 |
+
"geo_location": {"lat": 13.0594, "lng": 80.2584},
|
| 399 |
+
"kyc": {"pan_number": "AALEAB0939E"},
|
| 400 |
+
"status": "active",
|
| 401 |
+
"created_by": "dist_manager_chennai"
|
| 402 |
+
},
|
| 403 |
+
# Bangalore Distributor Salons
|
| 404 |
+
{
|
| 405 |
+
"merchant_id": "salon_bangalore_brigade_road",
|
| 406 |
+
"merchant_type": "salon",
|
| 407 |
+
"merchant_code": "SALON-BLR-001",
|
| 408 |
+
"merchant_name": "Brigade Road Style Studio",
|
| 409 |
+
"parent_merchant_id": "dist_bangalore_metro",
|
| 410 |
+
"contact": {
|
| 411 |
+
"phone": "+918090123456",
|
| 412 |
+
"email": "brigade@stylestudio.com",
|
| 413 |
+
"address_line1": "Brigade Road",
|
| 414 |
+
"city": "Bangalore",
|
| 415 |
+
"state": "Karnataka",
|
| 416 |
+
"pincode": "560025",
|
| 417 |
+
"country": "India"
|
| 418 |
+
},
|
| 419 |
+
"geo_location": {"lat": 12.9738, "lng": 77.6071},
|
| 420 |
+
"kyc": {"pan_number": "AALBS0939F"},
|
| 421 |
+
"status": "active",
|
| 422 |
+
"created_by": "dist_manager_bangalore"
|
| 423 |
+
},
|
| 424 |
+
# Mumbai Distributor Salons
|
| 425 |
+
{
|
| 426 |
+
"merchant_id": "salon_mumbai_linking_road",
|
| 427 |
+
"merchant_type": "salon",
|
| 428 |
+
"merchant_code": "SALON-MUM-001",
|
| 429 |
+
"merchant_name": "Linking Road Fashion Salon",
|
| 430 |
+
"parent_merchant_id": "dist_mumbai_central",
|
| 431 |
+
"contact": {
|
| 432 |
+
"phone": "+912201234567",
|
| 433 |
+
"email": "linking@fashionsalon.com",
|
| 434 |
+
"address_line1": "Linking Road, Bandra West",
|
| 435 |
+
"city": "Mumbai",
|
| 436 |
+
"state": "Maharashtra",
|
| 437 |
+
"pincode": "400050",
|
| 438 |
+
"country": "India"
|
| 439 |
+
},
|
| 440 |
+
"geo_location": {"lat": 19.0544, "lng": 72.8265},
|
| 441 |
+
"kyc": {"pan_number": "AALFS0939G"},
|
| 442 |
+
"status": "active",
|
| 443 |
+
"created_by": "dist_manager_mumbai"
|
| 444 |
+
},
|
| 445 |
+
{
|
| 446 |
+
"merchant_id": "salon_mumbai_colaba",
|
| 447 |
+
"merchant_type": "salon",
|
| 448 |
+
"merchant_code": "SALON-MUM-002",
|
| 449 |
+
"merchant_name": "Colaba Causeway Beauty Hub",
|
| 450 |
+
"parent_merchant_id": "dist_mumbai_central",
|
| 451 |
+
"contact": {
|
| 452 |
+
"phone": "+912212345678",
|
| 453 |
+
"email": "colaba@beautyhub.com",
|
| 454 |
+
"address_line1": "Colaba Causeway",
|
| 455 |
+
"city": "Mumbai",
|
| 456 |
+
"state": "Maharashtra",
|
| 457 |
+
"pincode": "400005",
|
| 458 |
+
"country": "India"
|
| 459 |
+
},
|
| 460 |
+
"geo_location": {"lat": 18.9067, "lng": 72.8147},
|
| 461 |
+
"kyc": {"pan_number": "AALCB0939H"},
|
| 462 |
+
"status": "active",
|
| 463 |
+
"created_by": "dist_manager_mumbai"
|
| 464 |
+
},
|
| 465 |
+
# Pune Distributor Salons
|
| 466 |
+
{
|
| 467 |
+
"merchant_id": "salon_pune_koregaon_park",
|
| 468 |
+
"merchant_type": "salon",
|
| 469 |
+
"merchant_code": "SALON-PUN-001",
|
| 470 |
+
"merchant_name": "Koregaon Park Elite Salon",
|
| 471 |
+
"parent_merchant_id": "dist_pune_premium",
|
| 472 |
+
"contact": {
|
| 473 |
+
"phone": "+912023456789",
|
| 474 |
+
"email": "koregaon@elitesalon.com",
|
| 475 |
+
"address_line1": "Koregaon Park",
|
| 476 |
+
"city": "Pune",
|
| 477 |
+
"state": "Maharashtra",
|
| 478 |
+
"pincode": "411001",
|
| 479 |
+
"country": "India"
|
| 480 |
+
},
|
| 481 |
+
"geo_location": {"lat": 18.5362, "lng": 73.8850},
|
| 482 |
+
"kyc": {"pan_number": "AALES0939I"},
|
| 483 |
+
"status": "active",
|
| 484 |
+
"created_by": "dist_manager_pune"
|
| 485 |
+
}
|
| 486 |
+
]
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
# Access roles data
|
| 490 |
+
ACCESS_ROLES = [
|
| 491 |
+
{
|
| 492 |
+
"role_id": "role_super_admin",
|
| 493 |
+
"role_name": "Super Administrator",
|
| 494 |
+
"description": "Full system access with all permissions",
|
| 495 |
+
"permissions": [
|
| 496 |
+
"CREATE_MERCHANT", "READ_MERCHANT", "UPDATE_MERCHANT", "DELETE_MERCHANT",
|
| 497 |
+
"CREATE_MERCHANT_SETTING", "READ_MERCHANT_SETTING", "UPDATE_MERCHANT_SETTING", "DELETE_MERCHANT_SETTING",
|
| 498 |
+
"CREATE_USER", "READ_USER", "UPDATE_USER", "DELETE_USER",
|
| 499 |
+
"CREATE_ROLE", "READ_ROLE", "UPDATE_ROLE", "DELETE_ROLE",
|
| 500 |
+
"VIEW_REPORTS", "MANAGE_SYSTEM", "AUDIT_ACCESS"
|
| 501 |
+
],
|
| 502 |
+
"is_active": True,
|
| 503 |
+
"created_by": "system",
|
| 504 |
+
"created_at": datetime.utcnow()
|
| 505 |
+
},
|
| 506 |
+
{
|
| 507 |
+
"role_id": "role_company_admin",
|
| 508 |
+
"role_name": "Company Administrator",
|
| 509 |
+
"description": "Company-wide administrative access",
|
| 510 |
+
"permissions": [
|
| 511 |
+
"CREATE_MERCHANT", "READ_MERCHANT", "UPDATE_MERCHANT",
|
| 512 |
+
"CREATE_MERCHANT_SETTING", "READ_MERCHANT_SETTING", "UPDATE_MERCHANT_SETTING",
|
| 513 |
+
"CREATE_USER", "READ_USER", "UPDATE_USER",
|
| 514 |
+
"VIEW_REPORTS", "MANAGE_HIERARCHY"
|
| 515 |
+
],
|
| 516 |
+
"is_active": True,
|
| 517 |
+
"created_by": "system",
|
| 518 |
+
"created_at": datetime.utcnow()
|
| 519 |
+
},
|
| 520 |
+
{
|
| 521 |
+
"role_id": "role_cnf_manager",
|
| 522 |
+
"role_name": "CNF Manager",
|
| 523 |
+
"description": "CNF operations management",
|
| 524 |
+
"permissions": [
|
| 525 |
+
"READ_MERCHANT", "UPDATE_MERCHANT",
|
| 526 |
+
"CREATE_MERCHANT_SETTING", "READ_MERCHANT_SETTING", "UPDATE_MERCHANT_SETTING",
|
| 527 |
+
"READ_USER", "CREATE_DISTRIBUTOR", "MANAGE_INVENTORY"
|
| 528 |
+
],
|
| 529 |
+
"is_active": True,
|
| 530 |
+
"created_by": "system",
|
| 531 |
+
"created_at": datetime.utcnow()
|
| 532 |
+
},
|
| 533 |
+
{
|
| 534 |
+
"role_id": "role_distributor_manager",
|
| 535 |
+
"role_name": "Distributor Manager",
|
| 536 |
+
"description": "Distributor operations management",
|
| 537 |
+
"permissions": [
|
| 538 |
+
"READ_MERCHANT", "UPDATE_MERCHANT",
|
| 539 |
+
"READ_MERCHANT_SETTING", "UPDATE_MERCHANT_SETTING",
|
| 540 |
+
"READ_USER", "CREATE_SALON", "MANAGE_ORDERS"
|
| 541 |
+
],
|
| 542 |
+
"is_active": True,
|
| 543 |
+
"created_by": "system",
|
| 544 |
+
"created_at": datetime.utcnow()
|
| 545 |
+
},
|
| 546 |
+
{
|
| 547 |
+
"role_id": "role_salon_owner",
|
| 548 |
+
"role_name": "Salon Owner",
|
| 549 |
+
"description": "Salon operations management",
|
| 550 |
+
"permissions": [
|
| 551 |
+
"READ_MERCHANT", "UPDATE_MERCHANT",
|
| 552 |
+
"READ_MERCHANT_SETTING", "UPDATE_MERCHANT_SETTING",
|
| 553 |
+
"READ_USER", "PLACE_ORDERS", "VIEW_INVENTORY"
|
| 554 |
+
],
|
| 555 |
+
"is_active": True,
|
| 556 |
+
"created_by": "system",
|
| 557 |
+
"created_at": datetime.utcnow()
|
| 558 |
+
},
|
| 559 |
+
{
|
| 560 |
+
"role_id": "role_salon_staff",
|
| 561 |
+
"role_name": "Salon Staff",
|
| 562 |
+
"description": "Basic salon operations",
|
| 563 |
+
"permissions": [
|
| 564 |
+
"READ_MERCHANT", "READ_MERCHANT_SETTING",
|
| 565 |
+
"READ_USER", "VIEW_INVENTORY", "BASIC_OPERATIONS"
|
| 566 |
+
],
|
| 567 |
+
"is_active": True,
|
| 568 |
+
"created_by": "system",
|
| 569 |
+
"created_at": datetime.utcnow()
|
| 570 |
+
}
|
| 571 |
+
]
|
| 572 |
+
|
| 573 |
+
# System users data
|
| 574 |
+
def generate_system_users():
|
| 575 |
+
"""Generate system users for each merchant."""
|
| 576 |
+
users = []
|
| 577 |
+
|
| 578 |
+
# Super admin
|
| 579 |
+
users.append({
|
| 580 |
+
"user_id": "user_super_admin",
|
| 581 |
+
"username": "superadmin",
|
| 582 |
+
"email": "superadmin@cuatrobeauty.com",
|
| 583 |
+
"password_hash": hash_password("SuperAdmin@123"),
|
| 584 |
+
"full_name": "System Super Administrator",
|
| 585 |
+
"role_id": "role_super_admin",
|
| 586 |
+
"merchant_id": "company_cuatro_beauty_ltd",
|
| 587 |
+
"permissions": ACCESS_ROLES[0]["permissions"],
|
| 588 |
+
"is_active": True,
|
| 589 |
+
"last_login": None,
|
| 590 |
+
"created_by": "system",
|
| 591 |
+
"created_at": datetime.utcnow()
|
| 592 |
+
})
|
| 593 |
+
|
| 594 |
+
# Company admin
|
| 595 |
+
users.append({
|
| 596 |
+
"user_id": "user_company_admin",
|
| 597 |
+
"username": "companyadmin",
|
| 598 |
+
"email": "admin@cuatrobeauty.com",
|
| 599 |
+
"password_hash": hash_password("CompanyAdmin@123"),
|
| 600 |
+
"full_name": "Cuatro Beauty Administrator",
|
| 601 |
+
"role_id": "role_company_admin",
|
| 602 |
+
"merchant_id": "company_cuatro_beauty_ltd",
|
| 603 |
+
"permissions": ACCESS_ROLES[1]["permissions"],
|
| 604 |
+
"is_active": True,
|
| 605 |
+
"last_login": None,
|
| 606 |
+
"created_by": "user_super_admin",
|
| 607 |
+
"created_at": datetime.utcnow()
|
| 608 |
+
})
|
| 609 |
+
|
| 610 |
+
# CNF Managers
|
| 611 |
+
cnf_managers = [
|
| 612 |
+
("cnf_north_india_hub", "cnfmanager_north", "north.manager@cuatrobeauty.com", "Rajesh Kumar"),
|
| 613 |
+
("cnf_south_india_hub", "cnfmanager_south", "south.manager@cuatrobeauty.com", "Suresh Reddy"),
|
| 614 |
+
("cnf_west_india_hub", "cnfmanager_west", "west.manager@cuatrobeauty.com", "Amit Sharma")
|
| 615 |
+
]
|
| 616 |
+
|
| 617 |
+
for merchant_id, username, email, full_name in cnf_managers:
|
| 618 |
+
users.append({
|
| 619 |
+
"user_id": generate_user_id(),
|
| 620 |
+
"username": username,
|
| 621 |
+
"email": email,
|
| 622 |
+
"password_hash": hash_password("CNFManager@123"),
|
| 623 |
+
"full_name": full_name,
|
| 624 |
+
"role_id": "role_cnf_manager",
|
| 625 |
+
"merchant_id": merchant_id,
|
| 626 |
+
"permissions": ACCESS_ROLES[2]["permissions"],
|
| 627 |
+
"is_active": True,
|
| 628 |
+
"last_login": None,
|
| 629 |
+
"created_by": "user_company_admin",
|
| 630 |
+
"created_at": datetime.utcnow()
|
| 631 |
+
})
|
| 632 |
+
|
| 633 |
+
# Distributor Managers
|
| 634 |
+
distributor_managers = [
|
| 635 |
+
("dist_delhi_premium", "distmanager_delhi", "delhi.manager@premiumbeauty.com", "Vikram Singh"),
|
| 636 |
+
("dist_punjab_wholesale", "distmanager_punjab", "punjab.manager@wholesalebeauty.com", "Hardeep Singh"),
|
| 637 |
+
("dist_chennai_elite", "distmanager_chennai", "chennai.manager@elitebeauty.com", "Ramesh Kumar"),
|
| 638 |
+
("dist_bangalore_metro", "distmanager_bangalore", "bangalore.manager@metrobeauty.com", "Srinivas Rao"),
|
| 639 |
+
("dist_mumbai_central", "distmanager_mumbai", "mumbai.manager@centralbeauty.com", "Rohit Patil"),
|
| 640 |
+
("dist_pune_premium", "distmanager_pune", "pune.manager@premiumhub.com", "Pradeep Joshi")
|
| 641 |
+
]
|
| 642 |
+
|
| 643 |
+
for merchant_id, username, email, full_name in distributor_managers:
|
| 644 |
+
users.append({
|
| 645 |
+
"user_id": generate_user_id(),
|
| 646 |
+
"username": username,
|
| 647 |
+
"email": email,
|
| 648 |
+
"password_hash": hash_password("DistManager@123"),
|
| 649 |
+
"full_name": full_name,
|
| 650 |
+
"role_id": "role_distributor_manager",
|
| 651 |
+
"merchant_id": merchant_id,
|
| 652 |
+
"permissions": ACCESS_ROLES[3]["permissions"],
|
| 653 |
+
"is_active": True,
|
| 654 |
+
"last_login": None,
|
| 655 |
+
"created_by": "user_company_admin",
|
| 656 |
+
"created_at": datetime.utcnow()
|
| 657 |
+
})
|
| 658 |
+
|
| 659 |
+
# Salon Owners
|
| 660 |
+
salon_owners = [
|
| 661 |
+
("salon_delhi_cp_luxury", "owner_cp_luxury", "owner@luxurysalon.com", "Meera Gupta"),
|
| 662 |
+
("salon_delhi_khan_market", "owner_khan_market", "owner@beautystudio.com", "Priya Sharma"),
|
| 663 |
+
("salon_ludhiana_model_town", "owner_model_town", "owner@beautyparour.com", "Simran Kaur"),
|
| 664 |
+
("salon_chennai_tnagar", "owner_tnagar", "owner@glamourstudio.com", "Lakshmi Devi"),
|
| 665 |
+
("salon_chennai_express_avenue", "owner_express_avenue", "owner@beautylounge.com", "Divya Raj"),
|
| 666 |
+
("salon_bangalore_brigade_road", "owner_brigade_road", "owner@stylestudio.com", "Kavya Nair"),
|
| 667 |
+
("salon_mumbai_linking_road", "owner_linking_road", "owner@fashionsalon.com", "Neha Desai"),
|
| 668 |
+
("salon_mumbai_colaba", "owner_colaba", "owner@beautyhub.com", "Pooja Shah"),
|
| 669 |
+
("salon_pune_koregaon_park", "owner_koregaon_park", "owner@elitesalon.com", "Shweta Kulkarni")
|
| 670 |
+
]
|
| 671 |
+
|
| 672 |
+
for merchant_id, username, email, full_name in salon_owners:
|
| 673 |
+
users.append({
|
| 674 |
+
"user_id": generate_user_id(),
|
| 675 |
+
"username": username,
|
| 676 |
+
"email": email,
|
| 677 |
+
"password_hash": hash_password("SalonOwner@123"),
|
| 678 |
+
"full_name": full_name,
|
| 679 |
+
"role_id": "role_salon_owner",
|
| 680 |
+
"merchant_id": merchant_id,
|
| 681 |
+
"permissions": ACCESS_ROLES[4]["permissions"],
|
| 682 |
+
"is_active": True,
|
| 683 |
+
"last_login": None,
|
| 684 |
+
"created_by": "user_company_admin",
|
| 685 |
+
"created_at": datetime.utcnow()
|
| 686 |
+
})
|
| 687 |
+
|
| 688 |
+
return users
|
| 689 |
+
|
| 690 |
+
# Merchant settings data based on merchant type
|
| 691 |
+
def generate_merchant_settings():
|
| 692 |
+
"""Generate settings for all merchants based on their type."""
|
| 693 |
+
settings = []
|
| 694 |
+
|
| 695 |
+
# Company settings
|
| 696 |
+
settings.append({
|
| 697 |
+
"merchant_id": "company_cuatro_beauty_ltd",
|
| 698 |
+
"auto_allocate_stock": False,
|
| 699 |
+
"credit_limit": None, # No credit limit for parent company
|
| 700 |
+
"payment_terms_days": 0,
|
| 701 |
+
"low_stock_threshold": 0,
|
| 702 |
+
"enable_backorder": False,
|
| 703 |
+
"auto_reorder": False,
|
| 704 |
+
"email_notifications": True,
|
| 705 |
+
"sms_notifications": False,
|
| 706 |
+
"low_stock_alerts": False,
|
| 707 |
+
"order_status_updates": True,
|
| 708 |
+
"operating_hours": {
|
| 709 |
+
"monday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 710 |
+
"tuesday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 711 |
+
"wednesday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 712 |
+
"thursday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 713 |
+
"friday": {"is_open": True, "open_time": "09:00", "close_time": "18:00"},
|
| 714 |
+
"saturday": {"is_open": False},
|
| 715 |
+
"sunday": {"is_open": False}
|
| 716 |
+
},
|
| 717 |
+
"default_markup_percentage": 0.0,
|
| 718 |
+
"allow_discount": False,
|
| 719 |
+
"max_discount_percentage": 0.0,
|
| 720 |
+
"tax_settings": {
|
| 721 |
+
"gst_rate": 18.0,
|
| 722 |
+
"inclusive_pricing": False,
|
| 723 |
+
"tax_exemption": False
|
| 724 |
+
},
|
| 725 |
+
"is_active": True,
|
| 726 |
+
"created_by": "user_super_admin",
|
| 727 |
+
"created_at": datetime.utcnow(),
|
| 728 |
+
"updated_by": None,
|
| 729 |
+
"updated_at": None,
|
| 730 |
+
"custom_fields": {
|
| 731 |
+
"region": "National",
|
| 732 |
+
"category": "parent_company",
|
| 733 |
+
"headquarters": True
|
| 734 |
+
}
|
| 735 |
+
})
|
| 736 |
+
|
| 737 |
+
# CNF settings (high volume, enterprise)
|
| 738 |
+
cnf_merchants = ["cnf_north_india_hub", "cnf_south_india_hub", "cnf_west_india_hub"]
|
| 739 |
+
for merchant_id in cnf_merchants:
|
| 740 |
+
region = merchant_id.split('_')[1].title()
|
| 741 |
+
settings.append({
|
| 742 |
+
"merchant_id": merchant_id,
|
| 743 |
+
"auto_allocate_stock": False,
|
| 744 |
+
"credit_limit": 10000000.0, # 1 Crore
|
| 745 |
+
"payment_terms_days": 90,
|
| 746 |
+
"low_stock_threshold": 500,
|
| 747 |
+
"enable_backorder": True,
|
| 748 |
+
"auto_reorder": True,
|
| 749 |
+
"email_notifications": True,
|
| 750 |
+
"sms_notifications": False,
|
| 751 |
+
"low_stock_alerts": True,
|
| 752 |
+
"order_status_updates": True,
|
| 753 |
+
"operating_hours": {
|
| 754 |
+
"monday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 755 |
+
"tuesday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 756 |
+
"wednesday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 757 |
+
"thursday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 758 |
+
"friday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 759 |
+
"saturday": {"is_open": True, "open_time": "08:00", "close_time": "17:00"},
|
| 760 |
+
"sunday": {"is_open": False}
|
| 761 |
+
},
|
| 762 |
+
"default_markup_percentage": 5.0,
|
| 763 |
+
"allow_discount": True,
|
| 764 |
+
"max_discount_percentage": 2.0,
|
| 765 |
+
"tax_settings": {
|
| 766 |
+
"gst_rate": 18.0,
|
| 767 |
+
"inclusive_pricing": False,
|
| 768 |
+
"tax_exemption": False
|
| 769 |
+
},
|
| 770 |
+
"is_active": True,
|
| 771 |
+
"created_by": "user_company_admin",
|
| 772 |
+
"created_at": datetime.utcnow(),
|
| 773 |
+
"updated_by": None,
|
| 774 |
+
"updated_at": None,
|
| 775 |
+
"custom_fields": {
|
| 776 |
+
"region": f"{region} India",
|
| 777 |
+
"category": "cnf",
|
| 778 |
+
"warehouse_capacity": "100000 sqft",
|
| 779 |
+
"cold_storage": True
|
| 780 |
+
}
|
| 781 |
+
})
|
| 782 |
+
|
| 783 |
+
# Distributor settings (medium volume, wholesale)
|
| 784 |
+
distributor_data = [
|
| 785 |
+
("dist_delhi_premium", "Delhi", 3000000.0),
|
| 786 |
+
("dist_punjab_wholesale", "Punjab", 2500000.0),
|
| 787 |
+
("dist_chennai_elite", "Chennai", 3500000.0),
|
| 788 |
+
("dist_bangalore_metro", "Bangalore", 4000000.0),
|
| 789 |
+
("dist_mumbai_central", "Mumbai", 5000000.0),
|
| 790 |
+
("dist_pune_premium", "Pune", 2000000.0)
|
| 791 |
+
]
|
| 792 |
+
|
| 793 |
+
for merchant_id, region, credit_limit in distributor_data:
|
| 794 |
+
settings.append({
|
| 795 |
+
"merchant_id": merchant_id,
|
| 796 |
+
"auto_allocate_stock": True,
|
| 797 |
+
"credit_limit": credit_limit,
|
| 798 |
+
"payment_terms_days": 60,
|
| 799 |
+
"low_stock_threshold": 100,
|
| 800 |
+
"enable_backorder": True,
|
| 801 |
+
"auto_reorder": True,
|
| 802 |
+
"email_notifications": True,
|
| 803 |
+
"sms_notifications": False,
|
| 804 |
+
"low_stock_alerts": True,
|
| 805 |
+
"order_status_updates": True,
|
| 806 |
+
"operating_hours": {
|
| 807 |
+
"monday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 808 |
+
"tuesday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 809 |
+
"wednesday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 810 |
+
"thursday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 811 |
+
"friday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 812 |
+
"saturday": {"is_open": True, "open_time": "09:00", "close_time": "16:00"},
|
| 813 |
+
"sunday": {"is_open": False}
|
| 814 |
+
},
|
| 815 |
+
"default_markup_percentage": 15.0,
|
| 816 |
+
"allow_discount": True,
|
| 817 |
+
"max_discount_percentage": 7.0,
|
| 818 |
+
"tax_settings": {
|
| 819 |
+
"gst_rate": 18.0,
|
| 820 |
+
"inclusive_pricing": False,
|
| 821 |
+
"tax_exemption": False
|
| 822 |
+
},
|
| 823 |
+
"is_active": True,
|
| 824 |
+
"created_by": "user_company_admin",
|
| 825 |
+
"created_at": datetime.utcnow(),
|
| 826 |
+
"updated_by": None,
|
| 827 |
+
"updated_at": None,
|
| 828 |
+
"custom_fields": {
|
| 829 |
+
"region": region,
|
| 830 |
+
"category": "distributor",
|
| 831 |
+
"warehouse_count": 2
|
| 832 |
+
}
|
| 833 |
+
})
|
| 834 |
+
|
| 835 |
+
# Salon settings (retail, customer-facing)
|
| 836 |
+
salon_data = [
|
| 837 |
+
("salon_delhi_cp_luxury", "Delhi", 500000.0, "premium"),
|
| 838 |
+
("salon_delhi_khan_market", "Delhi", 300000.0, "standard"),
|
| 839 |
+
("salon_ludhiana_model_town", "Punjab", 200000.0, "standard"),
|
| 840 |
+
("salon_chennai_tnagar", "Chennai", 400000.0, "premium"),
|
| 841 |
+
("salon_chennai_express_avenue", "Chennai", 600000.0, "luxury"),
|
| 842 |
+
("salon_bangalore_brigade_road", "Bangalore", 450000.0, "premium"),
|
| 843 |
+
("salon_mumbai_linking_road", "Mumbai", 800000.0, "luxury"),
|
| 844 |
+
("salon_mumbai_colaba", "Mumbai", 350000.0, "standard"),
|
| 845 |
+
("salon_pune_koregaon_park", "Pune", 400000.0, "premium")
|
| 846 |
+
]
|
| 847 |
+
|
| 848 |
+
for merchant_id, region, credit_limit, category in salon_data:
|
| 849 |
+
# Premium/luxury salons have different settings
|
| 850 |
+
is_premium = category in ["premium", "luxury"]
|
| 851 |
+
|
| 852 |
+
settings.append({
|
| 853 |
+
"merchant_id": merchant_id,
|
| 854 |
+
"auto_allocate_stock": True,
|
| 855 |
+
"credit_limit": credit_limit,
|
| 856 |
+
"payment_terms_days": 30 if is_premium else 15,
|
| 857 |
+
"low_stock_threshold": 10 if is_premium else 5,
|
| 858 |
+
"enable_backorder": False,
|
| 859 |
+
"auto_reorder": is_premium,
|
| 860 |
+
"email_notifications": True,
|
| 861 |
+
"sms_notifications": True,
|
| 862 |
+
"low_stock_alerts": True,
|
| 863 |
+
"order_status_updates": True,
|
| 864 |
+
"operating_hours": {
|
| 865 |
+
"monday": {"is_open": True, "open_time": "10:00", "close_time": "20:00"},
|
| 866 |
+
"tuesday": {"is_open": True, "open_time": "10:00", "close_time": "20:00"},
|
| 867 |
+
"wednesday": {"is_open": True, "open_time": "10:00", "close_time": "20:00"},
|
| 868 |
+
"thursday": {"is_open": True, "open_time": "10:00", "close_time": "20:00"},
|
| 869 |
+
"friday": {"is_open": True, "open_time": "10:00", "close_time": "21:00"},
|
| 870 |
+
"saturday": {"is_open": True, "open_time": "09:00", "close_time": "22:00"},
|
| 871 |
+
"sunday": {"is_open": True, "open_time": "11:00", "close_time": "20:00"}
|
| 872 |
+
},
|
| 873 |
+
"default_markup_percentage": 35.0 if is_premium else 25.0,
|
| 874 |
+
"allow_discount": True,
|
| 875 |
+
"max_discount_percentage": 20.0 if is_premium else 15.0,
|
| 876 |
+
"tax_settings": {
|
| 877 |
+
"gst_rate": 18.0,
|
| 878 |
+
"inclusive_pricing": True,
|
| 879 |
+
"tax_exemption": False
|
| 880 |
+
},
|
| 881 |
+
"is_active": True,
|
| 882 |
+
"created_by": "user_company_admin",
|
| 883 |
+
"created_at": datetime.utcnow(),
|
| 884 |
+
"updated_by": None,
|
| 885 |
+
"updated_at": None,
|
| 886 |
+
"custom_fields": {
|
| 887 |
+
"region": region,
|
| 888 |
+
"category": category,
|
| 889 |
+
"loyalty_program": is_premium,
|
| 890 |
+
"online_booking": is_premium
|
| 891 |
+
}
|
| 892 |
+
})
|
| 893 |
+
|
| 894 |
+
return settings
|
| 895 |
+
|
| 896 |
+
async def create_hierarchical_data():
|
| 897 |
+
"""Create complete hierarchical merchant data."""
|
| 898 |
+
client = None
|
| 899 |
+
try:
|
| 900 |
+
print("🚀 Creating hierarchical merchant ecosystem...")
|
| 901 |
+
|
| 902 |
+
# Connect to MongoDB
|
| 903 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 904 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 905 |
+
|
| 906 |
+
# Test connection
|
| 907 |
+
await client.admin.command('ping')
|
| 908 |
+
print(f"✅ Connected to database: {settings.MONGODB_DB_NAME}")
|
| 909 |
+
|
| 910 |
+
# Clear existing data (optional - comment out to preserve existing data)
|
| 911 |
+
print("🧹 Clearing existing hierarchical data...")
|
| 912 |
+
await db[SCM_ACCESS_ROLES_COLLECTION].delete_many({})
|
| 913 |
+
await db[SCM_SYSTEM_USERS_COLLECTION].delete_many({"username": {"$in": ["superadmin", "companyadmin", "cnfmanager_north", "cnfmanager_south", "cnfmanager_west"]}})
|
| 914 |
+
await db[SCM_MERCHANT_SETTINGS_COLLECTION].delete_many({})
|
| 915 |
+
await db[SCM_MERCHANTS_COLLECTION].delete_many({"merchant_code": {"$regex": "^(CUATRO|CNF|DIST|SALON)"}})
|
| 916 |
+
|
| 917 |
+
# 1. Create Access Roles
|
| 918 |
+
print("👮 Creating access roles...")
|
| 919 |
+
await db[SCM_ACCESS_ROLES_COLLECTION].insert_many(ACCESS_ROLES)
|
| 920 |
+
print(f"✅ Created {len(ACCESS_ROLES)} access roles")
|
| 921 |
+
|
| 922 |
+
# 2. Create Parent Company
|
| 923 |
+
print("🏢 Creating parent company...")
|
| 924 |
+
parent_company = MERCHANT_HIERARCHY["parent_company"].copy()
|
| 925 |
+
parent_company["created_at"] = datetime.utcnow()
|
| 926 |
+
await db[SCM_MERCHANTS_COLLECTION].insert_one(parent_company)
|
| 927 |
+
print(f"✅ Created parent company: {parent_company['merchant_name']}")
|
| 928 |
+
|
| 929 |
+
# 3. Create CNFs
|
| 930 |
+
print("🏭 Creating CNFs...")
|
| 931 |
+
cnfs_created = 0
|
| 932 |
+
for cnf_data in MERCHANT_HIERARCHY["cnfs"]:
|
| 933 |
+
cnf = cnf_data.copy()
|
| 934 |
+
cnf["created_at"] = datetime.utcnow()
|
| 935 |
+
await db[SCM_MERCHANTS_COLLECTION].insert_one(cnf)
|
| 936 |
+
cnfs_created += 1
|
| 937 |
+
print(f"✅ Created {cnfs_created} CNFs")
|
| 938 |
+
|
| 939 |
+
# 4. Create Distributors
|
| 940 |
+
print("📦 Creating distributors...")
|
| 941 |
+
distributors_created = 0
|
| 942 |
+
for dist_data in MERCHANT_HIERARCHY["distributors"]:
|
| 943 |
+
distributor = dist_data.copy()
|
| 944 |
+
distributor["created_at"] = datetime.utcnow()
|
| 945 |
+
await db[SCM_MERCHANTS_COLLECTION].insert_one(distributor)
|
| 946 |
+
distributors_created += 1
|
| 947 |
+
print(f"✅ Created {distributors_created} distributors")
|
| 948 |
+
|
| 949 |
+
# 5. Create Salons
|
| 950 |
+
print("💄 Creating salons...")
|
| 951 |
+
salons_created = 0
|
| 952 |
+
for salon_data in MERCHANT_HIERARCHY["salons"]:
|
| 953 |
+
salon = salon_data.copy()
|
| 954 |
+
salon["created_at"] = datetime.utcnow()
|
| 955 |
+
await db[SCM_MERCHANTS_COLLECTION].insert_one(salon)
|
| 956 |
+
salons_created += 1
|
| 957 |
+
print(f"✅ Created {salons_created} salons")
|
| 958 |
+
|
| 959 |
+
# 6. Create System Users
|
| 960 |
+
print("👤 Creating system users...")
|
| 961 |
+
users = generate_system_users()
|
| 962 |
+
await db[SCM_SYSTEM_USERS_COLLECTION].insert_many(users)
|
| 963 |
+
print(f"✅ Created {len(users)} system users")
|
| 964 |
+
|
| 965 |
+
# 7. Create Merchant Settings
|
| 966 |
+
print("⚙️ Creating merchant settings...")
|
| 967 |
+
merchant_settings = generate_merchant_settings()
|
| 968 |
+
await db[SCM_MERCHANT_SETTINGS_COLLECTION].insert_many(merchant_settings)
|
| 969 |
+
print(f"✅ Created {len(merchant_settings)} merchant settings")
|
| 970 |
+
|
| 971 |
+
# Summary
|
| 972 |
+
total_merchants = 1 + cnfs_created + distributors_created + salons_created
|
| 973 |
+
print(f"\n📊 Creation Summary:")
|
| 974 |
+
print(f" • Parent Company: 1")
|
| 975 |
+
print(f" • CNFs: {cnfs_created}")
|
| 976 |
+
print(f" • Distributors: {distributors_created}")
|
| 977 |
+
print(f" • Salons: {salons_created}")
|
| 978 |
+
print(f" • Total Merchants: {total_merchants}")
|
| 979 |
+
print(f" • System Users: {len(users)}")
|
| 980 |
+
print(f" • Access Roles: {len(ACCESS_ROLES)}")
|
| 981 |
+
print(f" • Merchant Settings: {len(merchant_settings)}")
|
| 982 |
+
|
| 983 |
+
print(f"\n🔗 Hierarchy Structure:")
|
| 984 |
+
print(" Cuatro Beauty Ltd")
|
| 985 |
+
print(" ├── North India CNF")
|
| 986 |
+
print(" │ ├── Delhi Premium Distributors")
|
| 987 |
+
print(" │ │ ├── CP Luxury Salon")
|
| 988 |
+
print(" │ │ └── Khan Market Beauty Studio")
|
| 989 |
+
print(" │ └── Punjab Wholesale Hub")
|
| 990 |
+
print(" │ └── Model Town Beauty Parlour")
|
| 991 |
+
print(" ├── South India CNF")
|
| 992 |
+
print(" │ ├── Chennai Elite Distribution")
|
| 993 |
+
print(" │ │ ├── T.Nagar Glamour Studio")
|
| 994 |
+
print(" │ │ └── Express Avenue Beauty Lounge")
|
| 995 |
+
print(" │ └── Bangalore Metro Supplies")
|
| 996 |
+
print(" │ └── Brigade Road Style Studio")
|
| 997 |
+
print(" └── West India CNF")
|
| 998 |
+
print(" ├── Mumbai Central Distribution")
|
| 999 |
+
print(" │ ├── Linking Road Fashion Salon")
|
| 1000 |
+
print(" │ └── Colaba Causeway Beauty Hub")
|
| 1001 |
+
print(" └── Pune Premium Hub")
|
| 1002 |
+
print(" └── Koregaon Park Elite Salon")
|
| 1003 |
+
|
| 1004 |
+
except Exception as e:
|
| 1005 |
+
print(f"❌ Error creating hierarchical data: {e}")
|
| 1006 |
+
raise
|
| 1007 |
+
finally:
|
| 1008 |
+
if client:
|
| 1009 |
+
client.close()
|
| 1010 |
+
|
| 1011 |
+
async def verify_hierarchical_data():
|
| 1012 |
+
"""Verify the created hierarchical data."""
|
| 1013 |
+
client = None
|
| 1014 |
+
try:
|
| 1015 |
+
print("🔍 Verifying hierarchical merchant data...")
|
| 1016 |
+
|
| 1017 |
+
# Connect to MongoDB
|
| 1018 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 1019 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 1020 |
+
|
| 1021 |
+
# Test connection
|
| 1022 |
+
await client.admin.command('ping')
|
| 1023 |
+
|
| 1024 |
+
# Check merchants by type
|
| 1025 |
+
merchants_by_type = {}
|
| 1026 |
+
async for merchant in db[SCM_MERCHANTS_COLLECTION].find({}):
|
| 1027 |
+
merchant_type = merchant.get("merchant_type", "unknown")
|
| 1028 |
+
if merchant_type not in merchants_by_type:
|
| 1029 |
+
merchants_by_type[merchant_type] = []
|
| 1030 |
+
merchants_by_type[merchant_type].append(merchant)
|
| 1031 |
+
|
| 1032 |
+
print("\n📋 Merchants by Type:")
|
| 1033 |
+
for merchant_type, merchants in merchants_by_type.items():
|
| 1034 |
+
print(f" {merchant_type.upper()}: {len(merchants)}")
|
| 1035 |
+
for merchant in merchants:
|
| 1036 |
+
parent = merchant.get("parent_merchant_id", "None")
|
| 1037 |
+
print(f" • {merchant['merchant_name']} (Parent: {parent})")
|
| 1038 |
+
|
| 1039 |
+
# Check users by role
|
| 1040 |
+
users_by_role = {}
|
| 1041 |
+
async for user in db[SCM_SYSTEM_USERS_COLLECTION].find({}):
|
| 1042 |
+
role_id = user.get("role_id", "unknown")
|
| 1043 |
+
if role_id not in users_by_role:
|
| 1044 |
+
users_by_role[role_id] = []
|
| 1045 |
+
users_by_role[role_id].append(user)
|
| 1046 |
+
|
| 1047 |
+
print(f"\n👥 Users by Role:")
|
| 1048 |
+
for role_id, users in users_by_role.items():
|
| 1049 |
+
print(f" {role_id}: {len(users)}")
|
| 1050 |
+
for user in users:
|
| 1051 |
+
print(f" • {user['full_name']} ({user['username']})")
|
| 1052 |
+
|
| 1053 |
+
# Check settings
|
| 1054 |
+
settings_count = await db[SCM_MERCHANT_SETTINGS_COLLECTION].count_documents({})
|
| 1055 |
+
roles_count = await db[SCM_ACCESS_ROLES_COLLECTION].count_documents({})
|
| 1056 |
+
|
| 1057 |
+
print(f"\n📊 Data Verification:")
|
| 1058 |
+
print(f" • Merchant Settings: {settings_count}")
|
| 1059 |
+
print(f" • Access Roles: {roles_count}")
|
| 1060 |
+
|
| 1061 |
+
print(f"\n✅ Hierarchical data verification completed!")
|
| 1062 |
+
|
| 1063 |
+
except Exception as e:
|
| 1064 |
+
print(f"❌ Error verifying data: {e}")
|
| 1065 |
+
raise
|
| 1066 |
+
finally:
|
| 1067 |
+
if client:
|
| 1068 |
+
client.close()
|
| 1069 |
+
|
| 1070 |
+
async def main():
|
| 1071 |
+
"""Main function to handle commands."""
|
| 1072 |
+
if len(sys.argv) < 2:
|
| 1073 |
+
print("Hierarchical Merchant Data Management")
|
| 1074 |
+
print("=" * 45)
|
| 1075 |
+
print()
|
| 1076 |
+
print("Commands:")
|
| 1077 |
+
print(" create Create complete hierarchical merchant data")
|
| 1078 |
+
print(" verify Verify created hierarchical data")
|
| 1079 |
+
print(" help Show this help message")
|
| 1080 |
+
print()
|
| 1081 |
+
print("Examples:")
|
| 1082 |
+
print(" python3 create_hierarchy.py create")
|
| 1083 |
+
print(" python3 create_hierarchy.py verify")
|
| 1084 |
+
return 1
|
| 1085 |
+
|
| 1086 |
+
command = sys.argv[1].lower()
|
| 1087 |
+
|
| 1088 |
+
try:
|
| 1089 |
+
if command == "create":
|
| 1090 |
+
await create_hierarchical_data()
|
| 1091 |
+
print("\n✅ Hierarchical data creation completed!")
|
| 1092 |
+
print("\nNext steps:")
|
| 1093 |
+
print("1. Start the FastAPI server: uvicorn app.main:app --reload")
|
| 1094 |
+
print("2. Visit http://localhost:8000/docs")
|
| 1095 |
+
print("3. Test the API endpoints with the created data")
|
| 1096 |
+
print("4. Login credentials:")
|
| 1097 |
+
print(" • Super Admin: superadmin / SuperAdmin@123")
|
| 1098 |
+
print(" • Company Admin: companyadmin / CompanyAdmin@123")
|
| 1099 |
+
print(" • CNF Manager (North): cnfmanager_north / CNFManager@123")
|
| 1100 |
+
|
| 1101 |
+
elif command == "verify":
|
| 1102 |
+
await verify_hierarchical_data()
|
| 1103 |
+
|
| 1104 |
+
elif command in ["help", "-h", "--help"]:
|
| 1105 |
+
await main() # Show help
|
| 1106 |
+
|
| 1107 |
+
else:
|
| 1108 |
+
print(f"❌ Unknown command: {command}")
|
| 1109 |
+
await main() # Show help
|
| 1110 |
+
return 1
|
| 1111 |
+
|
| 1112 |
+
except Exception as e:
|
| 1113 |
+
print(f"❌ Error: {e}")
|
| 1114 |
+
import traceback
|
| 1115 |
+
traceback.print_exc()
|
| 1116 |
+
return 1
|
| 1117 |
+
|
| 1118 |
+
return 0
|
| 1119 |
+
|
| 1120 |
+
if __name__ == "__main__":
|
| 1121 |
+
exit_code = asyncio.run(main())
|
| 1122 |
+
sys.exit(exit_code)
|
export_sample_data.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Export sample merchant settings data to JSON format.
|
| 3 |
+
"""
|
| 4 |
+
import asyncio
|
| 5 |
+
import json
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
from motor.motor_asyncio import AsyncIOMotorClient
|
| 8 |
+
import sys
|
| 9 |
+
import os
|
| 10 |
+
|
| 11 |
+
# Add the current directory to Python path
|
| 12 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 13 |
+
|
| 14 |
+
from app.core.config import settings
|
| 15 |
+
from app.constants.collections import SCM_MERCHANT_SETTINGS_COLLECTION
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def datetime_serializer(obj):
|
| 19 |
+
"""JSON serializer for datetime objects."""
|
| 20 |
+
if isinstance(obj, datetime):
|
| 21 |
+
return obj.isoformat() + "Z"
|
| 22 |
+
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
async def export_sample_data():
|
| 26 |
+
"""Export sample data to JSON format."""
|
| 27 |
+
client = None
|
| 28 |
+
try:
|
| 29 |
+
print("📤 Exporting sample merchant settings data...")
|
| 30 |
+
|
| 31 |
+
# Connect to MongoDB
|
| 32 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 33 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 34 |
+
|
| 35 |
+
# Test connection
|
| 36 |
+
await client.admin.command('ping')
|
| 37 |
+
|
| 38 |
+
collection = db[SCM_MERCHANT_SETTINGS_COLLECTION]
|
| 39 |
+
|
| 40 |
+
# Fetch all records
|
| 41 |
+
records = []
|
| 42 |
+
async for record in collection.find({}).sort("created_at", 1):
|
| 43 |
+
# Remove MongoDB _id field
|
| 44 |
+
record.pop("_id", None)
|
| 45 |
+
records.append(record)
|
| 46 |
+
|
| 47 |
+
if not records:
|
| 48 |
+
print("❌ No records found to export")
|
| 49 |
+
return
|
| 50 |
+
|
| 51 |
+
# Create export data structure
|
| 52 |
+
export_data = {
|
| 53 |
+
"export_info": {
|
| 54 |
+
"timestamp": datetime.now().isoformat() + "Z",
|
| 55 |
+
"total_records": len(records),
|
| 56 |
+
"collection": SCM_MERCHANT_SETTINGS_COLLECTION,
|
| 57 |
+
"database": settings.MONGODB_DB_NAME
|
| 58 |
+
},
|
| 59 |
+
"sample_records": records
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
# Export to JSON file
|
| 63 |
+
filename = f"merchant_settings_sample_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
| 64 |
+
|
| 65 |
+
with open(filename, 'w', encoding='utf-8') as f:
|
| 66 |
+
json.dump(export_data, f, indent=2, default=datetime_serializer, ensure_ascii=False)
|
| 67 |
+
|
| 68 |
+
print(f"✅ Exported {len(records)} records to: {filename}")
|
| 69 |
+
|
| 70 |
+
# Also print a summary to console
|
| 71 |
+
print("\n📊 Sample Records Summary:")
|
| 72 |
+
print("=" * 60)
|
| 73 |
+
|
| 74 |
+
for i, record in enumerate(records, 1):
|
| 75 |
+
status = "🟢 Active" if record.get("is_active") else "🔴 Inactive"
|
| 76 |
+
merchant_id = record.get("merchant_id", "N/A")
|
| 77 |
+
credit_limit = record.get("credit_limit", 0)
|
| 78 |
+
region = record.get("custom_fields", {}).get("region", "N/A")
|
| 79 |
+
category = record.get("custom_fields", {}).get("category", "N/A")
|
| 80 |
+
|
| 81 |
+
print(f"{i:2d}. {status} | {merchant_id}")
|
| 82 |
+
print(f" Credit: ₹{credit_limit:,.2f} | Region: {region} | Category: {category}")
|
| 83 |
+
|
| 84 |
+
print("\n" + "=" * 60)
|
| 85 |
+
print(f"📁 Full data exported to: {filename}")
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
print(f"❌ Error exporting data: {e}")
|
| 89 |
+
raise
|
| 90 |
+
finally:
|
| 91 |
+
if client:
|
| 92 |
+
client.close()
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
if __name__ == "__main__":
|
| 96 |
+
asyncio.run(export_sample_data())
|
manage_db.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
SCM Database Management Script
|
| 4 |
+
Provides easy commands to initialize and manage the database.
|
| 5 |
+
"""
|
| 6 |
+
import asyncio
|
| 7 |
+
import sys
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
# Add the current directory to Python path
|
| 11 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
from app.db_init.merchant_settings_init import initialize_merchant_settings_database
|
| 15 |
+
from motor.motor_asyncio import AsyncIOMotorClient
|
| 16 |
+
from insightfy_utils.logging import get_logger
|
| 17 |
+
from app.core.config import settings
|
| 18 |
+
except ImportError as e:
|
| 19 |
+
print(f"❌ Import error: {e}")
|
| 20 |
+
print("Make sure you're running this from the project root and dependencies are installed.")
|
| 21 |
+
sys.exit(1)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def print_help():
|
| 25 |
+
"""Print usage information."""
|
| 26 |
+
print("SCM Database Management Script")
|
| 27 |
+
print("=" * 40)
|
| 28 |
+
print()
|
| 29 |
+
print("Commands:")
|
| 30 |
+
print(" init-db Initialize database structure (collections, indexes)")
|
| 31 |
+
print(" verify-db Verify database setup and configuration")
|
| 32 |
+
print(" help Show this help message")
|
| 33 |
+
print()
|
| 34 |
+
print("Examples:")
|
| 35 |
+
print(" python manage_db.py init-db")
|
| 36 |
+
print(" python manage_db.py verify-db")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
async def initialize_database():
|
| 40 |
+
"""Initialize the database structure."""
|
| 41 |
+
client = None
|
| 42 |
+
try:
|
| 43 |
+
logger = get_logger(__name__)
|
| 44 |
+
logger.info("Starting database initialization")
|
| 45 |
+
|
| 46 |
+
# Connect to MongoDB
|
| 47 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 48 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 49 |
+
|
| 50 |
+
# Test connection
|
| 51 |
+
await client.admin.command('ping')
|
| 52 |
+
print(f"✅ Connected to database: {settings.MONGODB_DB_NAME}")
|
| 53 |
+
|
| 54 |
+
# Initialize merchant settings
|
| 55 |
+
await initialize_merchant_settings_database(db)
|
| 56 |
+
|
| 57 |
+
print("✅ Database initialization completed successfully")
|
| 58 |
+
|
| 59 |
+
except Exception as e:
|
| 60 |
+
print(f"❌ Database initialization failed: {e}")
|
| 61 |
+
raise
|
| 62 |
+
finally:
|
| 63 |
+
if client:
|
| 64 |
+
client.close()
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
async def verify_database():
|
| 68 |
+
"""Verify database setup."""
|
| 69 |
+
client = None
|
| 70 |
+
try:
|
| 71 |
+
logger = get_logger(__name__)
|
| 72 |
+
|
| 73 |
+
# Connect to MongoDB
|
| 74 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 75 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 76 |
+
|
| 77 |
+
# Test connection
|
| 78 |
+
await client.admin.command('ping')
|
| 79 |
+
print(f"✅ Connected to database: {settings.MONGODB_DB_NAME}")
|
| 80 |
+
|
| 81 |
+
# List collections
|
| 82 |
+
collections = await db.list_collection_names()
|
| 83 |
+
print(f"📁 Collections found: {len(collections)}")
|
| 84 |
+
for collection in collections:
|
| 85 |
+
print(f" - {collection}")
|
| 86 |
+
|
| 87 |
+
# Check merchant settings collection specifically
|
| 88 |
+
from app.constants.collections import SCM_MERCHANT_SETTINGS_COLLECTION
|
| 89 |
+
|
| 90 |
+
if SCM_MERCHANT_SETTINGS_COLLECTION in collections:
|
| 91 |
+
collection = db[SCM_MERCHANT_SETTINGS_COLLECTION]
|
| 92 |
+
indexes = await collection.index_information()
|
| 93 |
+
print(f"✅ {SCM_MERCHANT_SETTINGS_COLLECTION} has {len(indexes)} indexes:")
|
| 94 |
+
for index_name in indexes.keys():
|
| 95 |
+
print(f" - {index_name}")
|
| 96 |
+
else:
|
| 97 |
+
print(f"❌ {SCM_MERCHANT_SETTINGS_COLLECTION} collection not found")
|
| 98 |
+
|
| 99 |
+
except Exception as e:
|
| 100 |
+
print(f"❌ Database verification failed: {e}")
|
| 101 |
+
raise
|
| 102 |
+
finally:
|
| 103 |
+
if client:
|
| 104 |
+
client.close()
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
async def main():
|
| 108 |
+
"""Main function to handle commands."""
|
| 109 |
+
if len(sys.argv) < 2:
|
| 110 |
+
print_help()
|
| 111 |
+
return 1
|
| 112 |
+
|
| 113 |
+
command = sys.argv[1].lower()
|
| 114 |
+
|
| 115 |
+
try:
|
| 116 |
+
if command == "init-db":
|
| 117 |
+
print("🚀 Initializing SCM database structure...")
|
| 118 |
+
print("This will create collections, indexes, and validation rules.")
|
| 119 |
+
print()
|
| 120 |
+
|
| 121 |
+
await initialize_database()
|
| 122 |
+
|
| 123 |
+
print()
|
| 124 |
+
print("Next steps:")
|
| 125 |
+
print("1. Start the FastAPI server: uvicorn app.main:app --reload")
|
| 126 |
+
print("2. Visit http://localhost:8000/docs to test the API")
|
| 127 |
+
print("3. Use the merchant settings endpoints to create configurations")
|
| 128 |
+
|
| 129 |
+
elif command == "verify-db":
|
| 130 |
+
print("🔍 Verifying SCM database setup...")
|
| 131 |
+
print()
|
| 132 |
+
|
| 133 |
+
await verify_database()
|
| 134 |
+
|
| 135 |
+
elif command in ["help", "-h", "--help"]:
|
| 136 |
+
print_help()
|
| 137 |
+
|
| 138 |
+
else:
|
| 139 |
+
print(f"❌ Unknown command: {command}")
|
| 140 |
+
print()
|
| 141 |
+
print_help()
|
| 142 |
+
return 1
|
| 143 |
+
|
| 144 |
+
except Exception as e:
|
| 145 |
+
print(f"❌ Error: {e}")
|
| 146 |
+
import traceback
|
| 147 |
+
traceback.print_exc()
|
| 148 |
+
return 1
|
| 149 |
+
|
| 150 |
+
return 0
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
if __name__ == "__main__":
|
| 154 |
+
exit_code = asyncio.run(main())
|
| 155 |
+
sys.exit(exit_code)
|
sample_records.py
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Sample data insertion script for merchant settings.
|
| 3 |
+
Creates realistic sample records for testing and demonstration.
|
| 4 |
+
"""
|
| 5 |
+
import asyncio
|
| 6 |
+
from datetime import datetime, timedelta
|
| 7 |
+
from motor.motor_asyncio import AsyncIOMotorClient
|
| 8 |
+
from insightfy_utils.logging import get_logger
|
| 9 |
+
import sys
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
# Add the current directory to Python path
|
| 13 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 14 |
+
|
| 15 |
+
from app.core.config import settings
|
| 16 |
+
from app.constants.collections import SCM_MERCHANT_SETTINGS_COLLECTION, SCM_MERCHANTS_COLLECTION
|
| 17 |
+
|
| 18 |
+
logger = get_logger(__name__)
|
| 19 |
+
|
| 20 |
+
# Sample merchant settings data
|
| 21 |
+
SAMPLE_SETTINGS = [
|
| 22 |
+
{
|
| 23 |
+
"merchant_id": "mch_salon_mumbai_001",
|
| 24 |
+
"auto_allocate_stock": True,
|
| 25 |
+
"credit_limit": 250000.0,
|
| 26 |
+
"payment_terms_days": 15,
|
| 27 |
+
"low_stock_threshold": 5,
|
| 28 |
+
"enable_backorder": False,
|
| 29 |
+
"auto_reorder": True,
|
| 30 |
+
"email_notifications": True,
|
| 31 |
+
"sms_notifications": True,
|
| 32 |
+
"low_stock_alerts": True,
|
| 33 |
+
"order_status_updates": True,
|
| 34 |
+
"operating_hours": {
|
| 35 |
+
"monday": {"is_open": True, "open_time": "10:00", "close_time": "20:00"},
|
| 36 |
+
"tuesday": {"is_open": True, "open_time": "10:00", "close_time": "20:00"},
|
| 37 |
+
"wednesday": {"is_open": True, "open_time": "10:00", "close_time": "20:00"},
|
| 38 |
+
"thursday": {"is_open": True, "open_time": "10:00", "close_time": "20:00"},
|
| 39 |
+
"friday": {"is_open": True, "open_time": "10:00", "close_time": "21:00"},
|
| 40 |
+
"saturday": {"is_open": True, "open_time": "09:00", "close_time": "22:00"},
|
| 41 |
+
"sunday": {"is_open": True, "open_time": "11:00", "close_time": "20:00"}
|
| 42 |
+
},
|
| 43 |
+
"default_markup_percentage": 30.0,
|
| 44 |
+
"allow_discount": True,
|
| 45 |
+
"max_discount_percentage": 15.0,
|
| 46 |
+
"tax_settings": {
|
| 47 |
+
"gst_rate": 18.0,
|
| 48 |
+
"inclusive_pricing": True,
|
| 49 |
+
"tax_exemption": False
|
| 50 |
+
},
|
| 51 |
+
"is_active": True,
|
| 52 |
+
"created_by": "admin_001",
|
| 53 |
+
"created_at": datetime.utcnow() - timedelta(days=30),
|
| 54 |
+
"updated_by": "admin_001",
|
| 55 |
+
"updated_at": datetime.utcnow() - timedelta(days=5),
|
| 56 |
+
"custom_fields": {
|
| 57 |
+
"region": "Mumbai",
|
| 58 |
+
"category": "premium",
|
| 59 |
+
"loyalty_program": True,
|
| 60 |
+
"online_booking": True
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"merchant_id": "mch_distributor_delhi_001",
|
| 65 |
+
"auto_allocate_stock": False,
|
| 66 |
+
"credit_limit": 2000000.0,
|
| 67 |
+
"payment_terms_days": 45,
|
| 68 |
+
"low_stock_threshold": 50,
|
| 69 |
+
"enable_backorder": True,
|
| 70 |
+
"auto_reorder": False,
|
| 71 |
+
"email_notifications": True,
|
| 72 |
+
"sms_notifications": False,
|
| 73 |
+
"low_stock_alerts": True,
|
| 74 |
+
"order_status_updates": True,
|
| 75 |
+
"operating_hours": {
|
| 76 |
+
"monday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 77 |
+
"tuesday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 78 |
+
"wednesday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 79 |
+
"thursday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 80 |
+
"friday": {"is_open": True, "open_time": "08:00", "close_time": "18:00"},
|
| 81 |
+
"saturday": {"is_open": True, "open_time": "09:00", "close_time": "16:00"},
|
| 82 |
+
"sunday": {"is_open": False}
|
| 83 |
+
},
|
| 84 |
+
"default_markup_percentage": 12.0,
|
| 85 |
+
"allow_discount": True,
|
| 86 |
+
"max_discount_percentage": 5.0,
|
| 87 |
+
"tax_settings": {
|
| 88 |
+
"gst_rate": 18.0,
|
| 89 |
+
"inclusive_pricing": False,
|
| 90 |
+
"tax_exemption": False
|
| 91 |
+
},
|
| 92 |
+
"is_active": True,
|
| 93 |
+
"created_by": "system_admin",
|
| 94 |
+
"created_at": datetime.utcnow() - timedelta(days=60),
|
| 95 |
+
"updated_by": "manager_002",
|
| 96 |
+
"updated_at": datetime.utcnow() - timedelta(days=10),
|
| 97 |
+
"custom_fields": {
|
| 98 |
+
"region": "North India",
|
| 99 |
+
"category": "wholesale",
|
| 100 |
+
"warehouse_count": 3,
|
| 101 |
+
"delivery_zones": ["Delhi", "Gurgaon", "Noida", "Faridabad"]
|
| 102 |
+
}
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"merchant_id": "mch_salon_bangalore_002",
|
| 106 |
+
"auto_allocate_stock": True,
|
| 107 |
+
"credit_limit": 150000.0,
|
| 108 |
+
"payment_terms_days": 30,
|
| 109 |
+
"low_stock_threshold": 8,
|
| 110 |
+
"enable_backorder": False,
|
| 111 |
+
"auto_reorder": True,
|
| 112 |
+
"email_notifications": True,
|
| 113 |
+
"sms_notifications": True,
|
| 114 |
+
"low_stock_alerts": True,
|
| 115 |
+
"order_status_updates": True,
|
| 116 |
+
"operating_hours": {
|
| 117 |
+
"monday": {"is_open": True, "open_time": "09:30", "close_time": "19:30"},
|
| 118 |
+
"tuesday": {"is_open": True, "open_time": "09:30", "close_time": "19:30"},
|
| 119 |
+
"wednesday": {"is_open": True, "open_time": "09:30", "close_time": "19:30"},
|
| 120 |
+
"thursday": {"is_open": True, "open_time": "09:30", "close_time": "19:30"},
|
| 121 |
+
"friday": {"is_open": True, "open_time": "09:30", "close_time": "20:00"},
|
| 122 |
+
"saturday": {"is_open": True, "open_time": "09:00", "close_time": "21:00"},
|
| 123 |
+
"sunday": {"is_open": True, "open_time": "10:00", "close_time": "19:00"}
|
| 124 |
+
},
|
| 125 |
+
"default_markup_percentage": 25.0,
|
| 126 |
+
"allow_discount": True,
|
| 127 |
+
"max_discount_percentage": 12.0,
|
| 128 |
+
"tax_settings": {
|
| 129 |
+
"gst_rate": 18.0,
|
| 130 |
+
"inclusive_pricing": True,
|
| 131 |
+
"tax_exemption": False
|
| 132 |
+
},
|
| 133 |
+
"is_active": True,
|
| 134 |
+
"created_by": "regional_manager",
|
| 135 |
+
"created_at": datetime.utcnow() - timedelta(days=45),
|
| 136 |
+
"updated_by": "store_manager_003",
|
| 137 |
+
"updated_at": datetime.utcnow() - timedelta(days=2),
|
| 138 |
+
"custom_fields": {
|
| 139 |
+
"region": "Bangalore",
|
| 140 |
+
"category": "standard",
|
| 141 |
+
"loyalty_program": False,
|
| 142 |
+
"online_booking": True,
|
| 143 |
+
"staff_count": 8
|
| 144 |
+
}
|
| 145 |
+
},
|
| 146 |
+
{
|
| 147 |
+
"merchant_id": "mch_cnf_chennai_001",
|
| 148 |
+
"auto_allocate_stock": False,
|
| 149 |
+
"credit_limit": 5000000.0,
|
| 150 |
+
"payment_terms_days": 60,
|
| 151 |
+
"low_stock_threshold": 100,
|
| 152 |
+
"enable_backorder": True,
|
| 153 |
+
"auto_reorder": True,
|
| 154 |
+
"email_notifications": True,
|
| 155 |
+
"sms_notifications": False,
|
| 156 |
+
"low_stock_alerts": True,
|
| 157 |
+
"order_status_updates": True,
|
| 158 |
+
"operating_hours": {
|
| 159 |
+
"monday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 160 |
+
"tuesday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 161 |
+
"wednesday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 162 |
+
"thursday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 163 |
+
"friday": {"is_open": True, "open_time": "07:00", "close_time": "19:00"},
|
| 164 |
+
"saturday": {"is_open": True, "open_time": "08:00", "close_time": "17:00"},
|
| 165 |
+
"sunday": {"is_open": False}
|
| 166 |
+
},
|
| 167 |
+
"default_markup_percentage": 8.0,
|
| 168 |
+
"allow_discount": True,
|
| 169 |
+
"max_discount_percentage": 3.0,
|
| 170 |
+
"tax_settings": {
|
| 171 |
+
"gst_rate": 18.0,
|
| 172 |
+
"inclusive_pricing": False,
|
| 173 |
+
"tax_exemption": False
|
| 174 |
+
},
|
| 175 |
+
"is_active": True,
|
| 176 |
+
"created_by": "head_office",
|
| 177 |
+
"created_at": datetime.utcnow() - timedelta(days=90),
|
| 178 |
+
"updated_by": "operations_manager",
|
| 179 |
+
"updated_at": datetime.utcnow() - timedelta(days=7),
|
| 180 |
+
"custom_fields": {
|
| 181 |
+
"region": "South India",
|
| 182 |
+
"category": "cnf",
|
| 183 |
+
"warehouse_capacity": "50000 sqft",
|
| 184 |
+
"distribution_network": ["Chennai", "Coimbatore", "Madurai", "Trichy"],
|
| 185 |
+
"cold_storage": True
|
| 186 |
+
}
|
| 187 |
+
},
|
| 188 |
+
{
|
| 189 |
+
"merchant_id": "mch_salon_pune_003",
|
| 190 |
+
"auto_allocate_stock": True,
|
| 191 |
+
"credit_limit": 100000.0,
|
| 192 |
+
"payment_terms_days": 20,
|
| 193 |
+
"low_stock_threshold": 6,
|
| 194 |
+
"enable_backorder": False,
|
| 195 |
+
"auto_reorder": False,
|
| 196 |
+
"email_notifications": True,
|
| 197 |
+
"sms_notifications": False,
|
| 198 |
+
"low_stock_alerts": True,
|
| 199 |
+
"order_status_updates": False,
|
| 200 |
+
"operating_hours": {
|
| 201 |
+
"monday": {"is_open": True, "open_time": "10:00", "close_time": "19:00"},
|
| 202 |
+
"tuesday": {"is_open": True, "open_time": "10:00", "close_time": "19:00"},
|
| 203 |
+
"wednesday": {"is_open": True, "open_time": "10:00", "close_time": "19:00"},
|
| 204 |
+
"thursday": {"is_open": True, "open_time": "10:00", "close_time": "19:00"},
|
| 205 |
+
"friday": {"is_open": True, "open_time": "10:00", "close_time": "20:00"},
|
| 206 |
+
"saturday": {"is_open": True, "open_time": "09:30", "close_time": "20:30"},
|
| 207 |
+
"sunday": {"is_open": False}
|
| 208 |
+
},
|
| 209 |
+
"default_markup_percentage": 22.0,
|
| 210 |
+
"allow_discount": False,
|
| 211 |
+
"max_discount_percentage": 0.0,
|
| 212 |
+
"tax_settings": {
|
| 213 |
+
"gst_rate": 18.0,
|
| 214 |
+
"inclusive_pricing": True,
|
| 215 |
+
"tax_exemption": False
|
| 216 |
+
},
|
| 217 |
+
"is_active": False, # Inactive salon for testing
|
| 218 |
+
"created_by": "franchise_manager",
|
| 219 |
+
"created_at": datetime.utcnow() - timedelta(days=20),
|
| 220 |
+
"updated_by": "franchise_manager",
|
| 221 |
+
"updated_at": datetime.utcnow() - timedelta(days=1),
|
| 222 |
+
"custom_fields": {
|
| 223 |
+
"region": "Pune",
|
| 224 |
+
"category": "budget",
|
| 225 |
+
"loyalty_program": False,
|
| 226 |
+
"online_booking": False,
|
| 227 |
+
"closure_reason": "Temporarily closed for renovation"
|
| 228 |
+
}
|
| 229 |
+
}
|
| 230 |
+
]
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
async def create_sample_merchants():
|
| 234 |
+
"""Create sample merchants if they don't exist."""
|
| 235 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 236 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 237 |
+
|
| 238 |
+
sample_merchants = [
|
| 239 |
+
{
|
| 240 |
+
"merchant_id": "mch_salon_mumbai_001",
|
| 241 |
+
"merchant_type": "salon",
|
| 242 |
+
"merchant_code": "SAL-MUM-001",
|
| 243 |
+
"merchant_name": "Glamour Studio Mumbai",
|
| 244 |
+
"status": "active",
|
| 245 |
+
"created_by": "admin_001",
|
| 246 |
+
"created_at": datetime.utcnow() - timedelta(days=30)
|
| 247 |
+
},
|
| 248 |
+
{
|
| 249 |
+
"merchant_id": "mch_distributor_delhi_001",
|
| 250 |
+
"merchant_type": "distributor",
|
| 251 |
+
"merchant_code": "DIST-DEL-001",
|
| 252 |
+
"merchant_name": "Beauty Products Distributor Delhi",
|
| 253 |
+
"status": "active",
|
| 254 |
+
"created_by": "system_admin",
|
| 255 |
+
"created_at": datetime.utcnow() - timedelta(days=60)
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"merchant_id": "mch_salon_bangalore_002",
|
| 259 |
+
"merchant_type": "salon",
|
| 260 |
+
"merchant_code": "SAL-BLR-002",
|
| 261 |
+
"merchant_name": "Trend Salon Bangalore",
|
| 262 |
+
"status": "active",
|
| 263 |
+
"created_by": "regional_manager",
|
| 264 |
+
"created_at": datetime.utcnow() - timedelta(days=45)
|
| 265 |
+
},
|
| 266 |
+
{
|
| 267 |
+
"merchant_id": "mch_cnf_chennai_001",
|
| 268 |
+
"merchant_type": "cnf",
|
| 269 |
+
"merchant_code": "CNF-CHN-001",
|
| 270 |
+
"merchant_name": "South India Beauty CNF",
|
| 271 |
+
"status": "active",
|
| 272 |
+
"created_by": "head_office",
|
| 273 |
+
"created_at": datetime.utcnow() - timedelta(days=90)
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
"merchant_id": "mch_salon_pune_003",
|
| 277 |
+
"merchant_type": "salon",
|
| 278 |
+
"merchant_code": "SAL-PUN-003",
|
| 279 |
+
"merchant_name": "Style Zone Pune",
|
| 280 |
+
"status": "active",
|
| 281 |
+
"created_by": "franchise_manager",
|
| 282 |
+
"created_at": datetime.utcnow() - timedelta(days=20)
|
| 283 |
+
}
|
| 284 |
+
]
|
| 285 |
+
|
| 286 |
+
merchants_collection = db[SCM_MERCHANTS_COLLECTION]
|
| 287 |
+
|
| 288 |
+
for merchant in sample_merchants:
|
| 289 |
+
existing = await merchants_collection.find_one({"merchant_id": merchant["merchant_id"]})
|
| 290 |
+
if not existing:
|
| 291 |
+
await merchants_collection.insert_one(merchant)
|
| 292 |
+
print(f"✅ Created sample merchant: {merchant['merchant_name']}")
|
| 293 |
+
else:
|
| 294 |
+
print(f"⚠️ Merchant already exists: {merchant['merchant_name']}")
|
| 295 |
+
|
| 296 |
+
client.close()
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
async def insert_sample_settings():
|
| 300 |
+
"""Insert sample merchant settings records."""
|
| 301 |
+
client = None
|
| 302 |
+
try:
|
| 303 |
+
print("🚀 Inserting sample merchant settings records...")
|
| 304 |
+
|
| 305 |
+
# Connect to MongoDB
|
| 306 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 307 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 308 |
+
|
| 309 |
+
# Test connection
|
| 310 |
+
await client.admin.command('ping')
|
| 311 |
+
print(f"✅ Connected to database: {settings.MONGODB_DB_NAME}")
|
| 312 |
+
|
| 313 |
+
# Create sample merchants first
|
| 314 |
+
await create_sample_merchants()
|
| 315 |
+
|
| 316 |
+
# Insert sample settings
|
| 317 |
+
collection = db[SCM_MERCHANT_SETTINGS_COLLECTION]
|
| 318 |
+
|
| 319 |
+
inserted_count = 0
|
| 320 |
+
updated_count = 0
|
| 321 |
+
|
| 322 |
+
for settings_data in SAMPLE_SETTINGS:
|
| 323 |
+
# Check if settings already exist for this merchant
|
| 324 |
+
existing = await collection.find_one({"merchant_id": settings_data["merchant_id"]})
|
| 325 |
+
|
| 326 |
+
if existing:
|
| 327 |
+
# Update existing record
|
| 328 |
+
result = await collection.replace_one(
|
| 329 |
+
{"merchant_id": settings_data["merchant_id"]},
|
| 330 |
+
settings_data
|
| 331 |
+
)
|
| 332 |
+
if result.modified_count > 0:
|
| 333 |
+
updated_count += 1
|
| 334 |
+
print(f"🔄 Updated settings for: {settings_data['merchant_id']}")
|
| 335 |
+
else:
|
| 336 |
+
# Insert new record
|
| 337 |
+
await collection.insert_one(settings_data.copy())
|
| 338 |
+
inserted_count += 1
|
| 339 |
+
print(f"✅ Inserted settings for: {settings_data['merchant_id']}")
|
| 340 |
+
|
| 341 |
+
print(f"\n📊 Sample Data Summary:")
|
| 342 |
+
print(f" • Inserted: {inserted_count} new records")
|
| 343 |
+
print(f" • Updated: {updated_count} existing records")
|
| 344 |
+
print(f" • Total: {len(SAMPLE_SETTINGS)} sample records")
|
| 345 |
+
|
| 346 |
+
# Show collection stats
|
| 347 |
+
total_count = await collection.count_documents({})
|
| 348 |
+
active_count = await collection.count_documents({"is_active": True})
|
| 349 |
+
inactive_count = await collection.count_documents({"is_active": False})
|
| 350 |
+
|
| 351 |
+
print(f"\n📈 Collection Statistics:")
|
| 352 |
+
print(f" • Total settings records: {total_count}")
|
| 353 |
+
print(f" • Active settings: {active_count}")
|
| 354 |
+
print(f" • Inactive settings: {inactive_count}")
|
| 355 |
+
|
| 356 |
+
except Exception as e:
|
| 357 |
+
print(f"❌ Error inserting sample data: {e}")
|
| 358 |
+
raise
|
| 359 |
+
finally:
|
| 360 |
+
if client:
|
| 361 |
+
client.close()
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
async def view_sample_records():
|
| 365 |
+
"""View the inserted sample records."""
|
| 366 |
+
client = None
|
| 367 |
+
try:
|
| 368 |
+
print("🔍 Viewing sample merchant settings records...")
|
| 369 |
+
|
| 370 |
+
# Connect to MongoDB
|
| 371 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 372 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 373 |
+
|
| 374 |
+
# Test connection
|
| 375 |
+
await client.admin.command('ping')
|
| 376 |
+
|
| 377 |
+
collection = db[SCM_MERCHANT_SETTINGS_COLLECTION]
|
| 378 |
+
|
| 379 |
+
# Fetch all records
|
| 380 |
+
records = []
|
| 381 |
+
async for record in collection.find({}).sort("created_at", 1):
|
| 382 |
+
records.append(record)
|
| 383 |
+
|
| 384 |
+
if not records:
|
| 385 |
+
print("❌ No records found. Run 'insert' command first.")
|
| 386 |
+
return
|
| 387 |
+
|
| 388 |
+
print(f"\n📋 Found {len(records)} merchant settings records:")
|
| 389 |
+
print("=" * 80)
|
| 390 |
+
|
| 391 |
+
for i, record in enumerate(records, 1):
|
| 392 |
+
status_icon = "🟢" if record.get("is_active") else "🔴"
|
| 393 |
+
print(f"\n{i}. {status_icon} {record['merchant_id']}")
|
| 394 |
+
print(f" Credit Limit: ₹{record.get('credit_limit', 0):,.2f}")
|
| 395 |
+
print(f" Payment Terms: {record.get('payment_terms_days', 0)} days")
|
| 396 |
+
print(f" Stock Threshold: {record.get('low_stock_threshold', 0)} units")
|
| 397 |
+
print(f" Auto Allocate: {'Yes' if record.get('auto_allocate_stock') else 'No'}")
|
| 398 |
+
print(f" Email Alerts: {'Yes' if record.get('email_notifications') else 'No'}")
|
| 399 |
+
print(f" SMS Alerts: {'Yes' if record.get('sms_notifications') else 'No'}")
|
| 400 |
+
|
| 401 |
+
# Operating hours summary
|
| 402 |
+
operating_hours = record.get('operating_hours', {})
|
| 403 |
+
if operating_hours:
|
| 404 |
+
open_days = sum(1 for day_info in operating_hours.values()
|
| 405 |
+
if isinstance(day_info, dict) and day_info.get('is_open'))
|
| 406 |
+
print(f" Operating Days: {open_days}/7 days")
|
| 407 |
+
|
| 408 |
+
# Tax settings
|
| 409 |
+
tax_settings = record.get('tax_settings', {})
|
| 410 |
+
if tax_settings:
|
| 411 |
+
gst_rate = tax_settings.get('gst_rate', 0)
|
| 412 |
+
print(f" GST Rate: {gst_rate}%")
|
| 413 |
+
|
| 414 |
+
print(f" Created: {record.get('created_at', 'N/A')}")
|
| 415 |
+
print(f" Updated: {record.get('updated_at', 'N/A')}")
|
| 416 |
+
|
| 417 |
+
# Custom fields
|
| 418 |
+
custom_fields = record.get('custom_fields', {})
|
| 419 |
+
if custom_fields:
|
| 420 |
+
region = custom_fields.get('region', 'N/A')
|
| 421 |
+
category = custom_fields.get('category', 'N/A')
|
| 422 |
+
print(f" Region: {region} | Category: {category}")
|
| 423 |
+
|
| 424 |
+
print("\n" + "=" * 80)
|
| 425 |
+
|
| 426 |
+
except Exception as e:
|
| 427 |
+
print(f"❌ Error viewing records: {e}")
|
| 428 |
+
raise
|
| 429 |
+
finally:
|
| 430 |
+
if client:
|
| 431 |
+
client.close()
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
async def clear_sample_data():
|
| 435 |
+
"""Clear all sample merchant settings data."""
|
| 436 |
+
client = None
|
| 437 |
+
try:
|
| 438 |
+
print("🗑️ Clearing sample merchant settings data...")
|
| 439 |
+
|
| 440 |
+
# Connect to MongoDB
|
| 441 |
+
client = AsyncIOMotorClient(settings.MONGODB_URI)
|
| 442 |
+
db = client[settings.MONGODB_DB_NAME]
|
| 443 |
+
|
| 444 |
+
# Test connection
|
| 445 |
+
await client.admin.command('ping')
|
| 446 |
+
|
| 447 |
+
collection = db[SCM_MERCHANT_SETTINGS_COLLECTION]
|
| 448 |
+
|
| 449 |
+
# Count before deletion
|
| 450 |
+
before_count = await collection.count_documents({})
|
| 451 |
+
|
| 452 |
+
# Delete all records
|
| 453 |
+
result = await collection.delete_many({})
|
| 454 |
+
|
| 455 |
+
print(f"✅ Cleared {result.deleted_count} records (was {before_count})")
|
| 456 |
+
|
| 457 |
+
except Exception as e:
|
| 458 |
+
print(f"❌ Error clearing data: {e}")
|
| 459 |
+
raise
|
| 460 |
+
finally:
|
| 461 |
+
if client:
|
| 462 |
+
client.close()
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
async def main():
|
| 466 |
+
"""Main function to handle commands."""
|
| 467 |
+
if len(sys.argv) < 2:
|
| 468 |
+
print("Sample Merchant Settings Data Management")
|
| 469 |
+
print("=" * 45)
|
| 470 |
+
print()
|
| 471 |
+
print("Commands:")
|
| 472 |
+
print(" insert Insert sample merchant settings records")
|
| 473 |
+
print(" view View existing sample records")
|
| 474 |
+
print(" clear Clear all sample data")
|
| 475 |
+
print(" help Show this help message")
|
| 476 |
+
print()
|
| 477 |
+
print("Examples:")
|
| 478 |
+
print(" python3 sample_records.py insert")
|
| 479 |
+
print(" python3 sample_records.py view")
|
| 480 |
+
return 1
|
| 481 |
+
|
| 482 |
+
command = sys.argv[1].lower()
|
| 483 |
+
|
| 484 |
+
try:
|
| 485 |
+
if command == "insert":
|
| 486 |
+
await insert_sample_settings()
|
| 487 |
+
print("\n✅ Sample data insertion completed!")
|
| 488 |
+
print("\nNext steps:")
|
| 489 |
+
print("1. Start the FastAPI server: uvicorn app.main:app --reload")
|
| 490 |
+
print("2. Visit http://localhost:8000/docs")
|
| 491 |
+
print("3. Test the merchant settings GET endpoints")
|
| 492 |
+
|
| 493 |
+
elif command == "view":
|
| 494 |
+
await view_sample_records()
|
| 495 |
+
|
| 496 |
+
elif command == "clear":
|
| 497 |
+
await clear_sample_data()
|
| 498 |
+
|
| 499 |
+
elif command in ["help", "-h", "--help"]:
|
| 500 |
+
await main() # Show help
|
| 501 |
+
|
| 502 |
+
else:
|
| 503 |
+
print(f"❌ Unknown command: {command}")
|
| 504 |
+
await main() # Show help
|
| 505 |
+
return 1
|
| 506 |
+
|
| 507 |
+
except Exception as e:
|
| 508 |
+
print(f"❌ Error: {e}")
|
| 509 |
+
import traceback
|
| 510 |
+
traceback.print_exc()
|
| 511 |
+
return 1
|
| 512 |
+
|
| 513 |
+
return 0
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
if __name__ == "__main__":
|
| 517 |
+
exit_code = asyncio.run(main())
|
| 518 |
+
sys.exit(exit_code)
|