MukeshKapoor25 commited on
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 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)