Spaces:
Running
Running
| """ | |
| Seed data script for POS Microservice. | |
| Populates MongoDB with sample catalogue services, staff, and customers. | |
| Usage: | |
| python scripts/seed_data.py | |
| Or with custom merchant_id: | |
| python scripts/seed_data.py --merchant-id <uuid> | |
| """ | |
| import asyncio | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| from uuid import uuid4 | |
| from datetime import datetime | |
| # Add parent directory to path to import app modules | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from app.nosql import connect_to_mongo, close_mongo_connection, get_database | |
| from app.core.config import settings | |
| # Use standard UUID for consistency across services | |
| MERCHANT_ID = "01234567-89ab-cdef-0123-456789abcdef" # Cuatro Beauty Ltd UUID | |
| # Sample Services | |
| SAMPLE_SERVICES = [ | |
| { | |
| "name": "Hair Cut β Women", | |
| "code": "HC-WOMEN-45", | |
| "category_id": "hair", | |
| "category_name": "Hair", | |
| "description": "Includes wash, cut and blow-dry", | |
| "duration_mins": 45, | |
| "price": 700, | |
| "gst_rate": 18, | |
| }, | |
| { | |
| "name": "Hair Cut β Men", | |
| "code": "HC-MEN-30", | |
| "category_id": "hair", | |
| "category_name": "Hair", | |
| "description": "Standard men's haircut", | |
| "duration_mins": 30, | |
| "price": 400, | |
| "gst_rate": 18, | |
| }, | |
| { | |
| "name": "Hair Color β Full", | |
| "code": "COL-FULL-120", | |
| "category_id": "hair", | |
| "category_name": "Hair", | |
| "description": "Complete hair coloring with premium products", | |
| "duration_mins": 120, | |
| "price": 3500, | |
| "gst_rate": 18, | |
| }, | |
| { | |
| "name": "Hair Spa Treatment", | |
| "code": "SPA-HAIR-60", | |
| "category_id": "spa", | |
| "category_name": "Spa", | |
| "description": "Deep conditioning and scalp treatment", | |
| "duration_mins": 60, | |
| "price": 1500, | |
| "gst_rate": 18, | |
| }, | |
| { | |
| "name": "Facial β Classic", | |
| "code": "FACE-CLS-45", | |
| "category_id": "facial", | |
| "category_name": "Facial", | |
| "description": "Deep cleansing facial with mask", | |
| "duration_mins": 45, | |
| "price": 1200, | |
| "gst_rate": 18, | |
| }, | |
| { | |
| "name": "Manicure", | |
| "code": "MANI-30", | |
| "category_id": "nails", | |
| "category_name": "Nails", | |
| "description": "Classic manicure with nail polish", | |
| "duration_mins": 30, | |
| "price": 500, | |
| "gst_rate": 18, | |
| }, | |
| { | |
| "name": "Pedicure", | |
| "code": "PEDI-45", | |
| "category_id": "nails", | |
| "category_name": "Nails", | |
| "description": "Classic pedicure with foot massage", | |
| "duration_mins": 45, | |
| "price": 700, | |
| "gst_rate": 18, | |
| }, | |
| { | |
| "name": "Blow Dry & Styling", | |
| "code": "STYLE-30", | |
| "category_id": "hair", | |
| "category_name": "Hair", | |
| "description": "Professional blow dry and styling", | |
| "duration_mins": 30, | |
| "price": 600, | |
| "gst_rate": 18, | |
| }, | |
| { | |
| "name": "Makeup β Party", | |
| "code": "MKP-PARTY-60", | |
| "category_id": "makeup", | |
| "category_name": "Makeup", | |
| "description": "Party makeup with false lashes", | |
| "duration_mins": 60, | |
| "price": 2500, | |
| "gst_rate": 18, | |
| }, | |
| { | |
| "name": "Waxing β Full Arms", | |
| "code": "WAX-ARMS-20", | |
| "category_id": "waxing", | |
| "category_name": "Waxing", | |
| "description": "Full arms waxing", | |
| "duration_mins": 20, | |
| "price": 400, | |
| "gst_rate": 18, | |
| }, | |
| ] | |
| # Sample Staff | |
| SAMPLE_STAFF = [ | |
| { | |
| "name": "Priya Sharma", | |
| "role": "Senior Stylist", | |
| "staff_code": "STF001", | |
| "phone": "+91-9876543210", | |
| "email": "priya.sharma@retail.com", | |
| "skills": ["Haircut", "Color", "Styling", "Hair Spa"], | |
| "working_hours": [ | |
| {"day": "Mon", "from": "10:00", "to": "19:00"}, | |
| {"day": "Tue", "from": "10:00", "to": "19:00"}, | |
| {"day": "Wed", "from": "10:00", "to": "19:00"}, | |
| {"day": "Thu", "from": "10:00", "to": "19:00"}, | |
| {"day": "Fri", "from": "10:00", "to": "19:00"}, | |
| {"day": "Sat", "from": "11:00", "to": "18:00"}, | |
| ], | |
| }, | |
| { | |
| "name": "Rajesh Kumar", | |
| "role": "Stylist", | |
| "staff_code": "STF002", | |
| "phone": "+91-9876543211", | |
| "email": "rajesh.kumar@retail.com", | |
| "skills": ["Haircut", "Styling", "Beard Trim"], | |
| "working_hours": [ | |
| {"day": "Mon", "from": "10:00", "to": "19:00"}, | |
| {"day": "Tue", "from": "10:00", "to": "19:00"}, | |
| {"day": "Wed", "from": "10:00", "to": "19:00"}, | |
| {"day": "Thu", "from": "10:00", "to": "19:00"}, | |
| {"day": "Fri", "from": "10:00", "to": "19:00"}, | |
| ], | |
| }, | |
| { | |
| "name": "Anjali Verma", | |
| "role": "Makeup Artist", | |
| "staff_code": "STF003", | |
| "phone": "+91-9876543212", | |
| "email": "anjali.verma@retail.com", | |
| "skills": ["Makeup", "Facial", "Bridal Makeup"], | |
| "working_hours": [ | |
| {"day": "Tue", "from": "11:00", "to": "20:00"}, | |
| {"day": "Wed", "from": "11:00", "to": "20:00"}, | |
| {"day": "Thu", "from": "11:00", "to": "20:00"}, | |
| {"day": "Fri", "from": "11:00", "to": "20:00"}, | |
| {"day": "Sat", "from": "10:00", "to": "19:00"}, | |
| {"day": "Sun", "from": "10:00", "to": "17:00"}, | |
| ], | |
| }, | |
| { | |
| "name": "Meera Patel", | |
| "role": "Nail Technician", | |
| "staff_code": "STF004", | |
| "phone": "+91-9876543213", | |
| "email": "meera.patel@retail.com", | |
| "skills": ["Manicure", "Pedicure", "Nail Art"], | |
| "working_hours": [ | |
| {"day": "Mon", "from": "10:00", "to": "19:00"}, | |
| {"day": "Tue", "from": "10:00", "to": "19:00"}, | |
| {"day": "Wed", "from": "10:00", "to": "19:00"}, | |
| {"day": "Thu", "from": "10:00", "to": "19:00"}, | |
| {"day": "Fri", "from": "10:00", "to": "19:00"}, | |
| {"day": "Sat", "from": "11:00", "to": "18:00"}, | |
| ], | |
| }, | |
| ] | |
| # Sample Customers | |
| SAMPLE_CUSTOMERS = [ | |
| { | |
| "name": "Neha Kapoor", | |
| "phone": "+91-9988776655", | |
| "email": "neha.kapoor@example.com", | |
| "notes": "Regular customer, prefers morning appointments", | |
| }, | |
| { | |
| "name": "Arjun Reddy", | |
| "phone": "+91-9988776656", | |
| "email": "arjun.reddy@example.com", | |
| "notes": "Walk-in customer", | |
| }, | |
| { | |
| "name": "Kavya Nair", | |
| "phone": "+91-9988776657", | |
| "email": "kavya.nair@example.com", | |
| "notes": "Prefers specific stylist - Priya", | |
| }, | |
| ] | |
| async def seed_services(merchant_id: str): | |
| """Seed catalogue services.""" | |
| db = get_database() | |
| if db is None: | |
| print("β MongoDB not connected. Cannot seed data.") | |
| return False | |
| collection = db["catalogue_services"] | |
| print("\nπ Seeding Catalogue Services...") | |
| created_count = 0 | |
| skipped_count = 0 | |
| for service in SAMPLE_SERVICES: | |
| # Check if service code already exists | |
| existing = await collection.find_one({ | |
| "merchant_id": merchant_id, | |
| "code": service["code"] | |
| }) | |
| if existing: | |
| print(f" βοΈ Skipped: {service['name']} (code: {service['code']}) - already exists") | |
| skipped_count += 1 | |
| continue | |
| service_id = str(uuid4()) | |
| now = datetime.utcnow() | |
| doc = { | |
| "_id": service_id, | |
| "merchant_id": merchant_id, | |
| "name": service["name"], | |
| "code": service["code"], | |
| "category": { | |
| "id": service["category_id"], | |
| "name": service["category_name"] | |
| }, | |
| "description": service["description"], | |
| "duration_mins": service["duration_mins"], | |
| "pricing": { | |
| "price": service["price"], | |
| "currency": "INR", | |
| "gst_rate": service["gst_rate"] | |
| }, | |
| "status": "active", | |
| "sort_order": created_count, | |
| "created_at": now, | |
| "updated_at": now | |
| } | |
| await collection.insert_one(doc) | |
| print(f" β Created: {service['name']} (βΉ{service['price']}, {service['duration_mins']} mins)") | |
| created_count += 1 | |
| print(f"\n π Services: {created_count} created, {skipped_count} skipped") | |
| return True | |
| async def seed_staff(merchant_id: str): | |
| """Seed staff members.""" | |
| db = get_database() | |
| if db is None: | |
| print("β MongoDB not connected. Cannot seed data.") | |
| return False | |
| collection = db["pos_staff"] | |
| print("\nπ₯ Seeding Staff...") | |
| created_count = 0 | |
| skipped_count = 0 | |
| for staff in SAMPLE_STAFF: | |
| # Check if staff code already exists | |
| existing = await collection.find_one({ | |
| "merchant_id": merchant_id, | |
| "staff_code": staff["staff_code"] | |
| }) | |
| if existing: | |
| print(f" βοΈ Skipped: {staff['name']} ({staff['staff_code']}) - already exists") | |
| skipped_count += 1 | |
| continue | |
| staff_id = f"staff_{uuid4().hex[:16]}" | |
| now = datetime.utcnow() | |
| doc = { | |
| "staff_id": staff_id, | |
| "merchant_id": merchant_id, | |
| "name": staff["name"], | |
| "role": staff["role"], | |
| "staff_code": staff["staff_code"], | |
| "contact": { | |
| "phone": staff["phone"], | |
| "email": staff["email"] | |
| }, | |
| "skills": staff["skills"], | |
| "status": "active", | |
| "working_hours": staff["working_hours"], | |
| "created_at": now, | |
| "updated_at": now | |
| } | |
| await collection.insert_one(doc) | |
| print(f" β Created: {staff['name']} - {staff['role']}") | |
| created_count += 1 | |
| print(f"\n π Staff: {created_count} created, {skipped_count} skipped") | |
| return True | |
| async def seed_customers(merchant_id: str): | |
| """Seed sample customers.""" | |
| db = get_database() | |
| if db is None: | |
| print("β MongoDB not connected. Cannot seed data.") | |
| return False | |
| collection = db["pos_customers"] | |
| print("\nπ€ Seeding Customers...") | |
| created_count = 0 | |
| skipped_count = 0 | |
| for customer in SAMPLE_CUSTOMERS: | |
| # Check if customer phone already exists | |
| existing = await collection.find_one({ | |
| "merchant_id": merchant_id, | |
| "phone": customer["phone"] | |
| }) | |
| if existing: | |
| print(f" βοΈ Skipped: {customer['name']} ({customer['phone']}) - already exists") | |
| skipped_count += 1 | |
| continue | |
| customer_id = str(uuid4()) | |
| now = datetime.utcnow() | |
| doc = { | |
| "customer_id": customer_id, | |
| "merchant_id": merchant_id, | |
| "name": customer["name"], | |
| "phone": customer["phone"], | |
| "email": customer["email"], | |
| "notes": customer["notes"], | |
| "created_at": now, | |
| "updated_at": now | |
| } | |
| await collection.insert_one(doc) | |
| print(f" β Created: {customer['name']} ({customer['phone']})") | |
| created_count += 1 | |
| print(f"\n π Customers: {created_count} created, {skipped_count} skipped") | |
| return True | |
| async def main(merchant_id: str): | |
| """Main seed function.""" | |
| print("="*60) | |
| print("π± POS Microservice - Seed Data Script") | |
| print("="*60) | |
| print(f"\nπ Merchant ID: {merchant_id}") | |
| print(f"π MongoDB URI: {settings.MONGODB_URI}") | |
| print(f"π Database: {settings.MONGODB_DB_NAME}") | |
| # Connect to MongoDB | |
| try: | |
| await connect_to_mongo() | |
| except Exception as e: | |
| print(f"\nβ Failed to connect to MongoDB: {e}") | |
| print("\nπ‘ Make sure MongoDB is running:") | |
| print(" docker run -d --name mongo -p 27017:27017 mongo:6") | |
| return | |
| # Seed data | |
| try: | |
| success = True | |
| success = await seed_services(merchant_id) and success | |
| success = await seed_staff(merchant_id) and success | |
| success = await seed_customers(merchant_id) and success | |
| if success: | |
| print("\n" + "="*60) | |
| print("β Seed data completed successfully!") | |
| print("="*60) | |
| print("\nπ‘ You can now:") | |
| print(" 1. Start the API: uvicorn app.main:app --reload --port 8282") | |
| print(f" 2. List services: GET /api/v1/pos/catalogue/services?merchant_id={merchant_id}") | |
| print(f" 3. Create appointments with the seeded services and staff") | |
| else: | |
| print("\nβ οΈ Seed data completed with some issues") | |
| except Exception as e: | |
| print(f"\nβ Error during seeding: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| finally: | |
| await close_mongo_connection() | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Seed POS microservice data") | |
| parser.add_argument( | |
| "--merchant-id", | |
| type=str, | |
| default=MERCHANT_ID, | |
| help=f"Merchant UUID (default: {MERCHANT_ID})" | |
| ) | |
| args = parser.parse_args() | |
| asyncio.run(main(args.merchant_id)) | |