"""Idempotent seed data for the B2B ordering portal. Creates a small demo catalogue, a demo ordering customer + buyer user, a price list, and a customer-specific price so the acceptance-criteria flow works out of the box. Safe to run on every startup — it no-ops once seeded. """ from __future__ import annotations import logging from sqlalchemy import select from sqlalchemy.orm import Session from app.core.config import settings from app.models.client_access import ClientAccount, ClientFeatureAccess, ClientUser from app.models.ordering import ( CatalogueProduct, CustomerPriceAssignment, CustomerProductPrice, NotificationSetting, PriceList, PriceListItem, ProductCategory, ) from app.services.client_access_service import ( MODULE_INDEX, ensure_user_module_permissions, ) logger = logging.getLogger("data_entry_app.seed") ORDERING_TENANT = settings.client_tenant_id _CATEGORIES = [ ("grains", "Grains", 10), ("premixed", "Premixed Products", 20), ("bags", "Bags", 30), ("bulk_loads", "Bulk Loads", 40), ("custom_blends", "Custom Blends", 50), ("services", "Services & Delivery", 60), ] _PRODUCTS = [ # (name, sku, category, uom, unit_size, moq, base_price, stock, requires_quote) ("Cracked Maize", "GRN-MAIZE-20", "grains", "20kg bag", "20kg", 1, 24.50, "in_stock", False), ("Whole Barley", "GRN-BARLEY-20", "grains", "20kg bag", "20kg", 1, 21.00, "in_stock", False), ("Layer Premix", "PMX-LAYER-20", "premixed", "20kg bag", "20kg", 1, 32.75, "in_stock", False), ("Calf Starter Premix", "PMX-CALF-20", "premixed", "20kg bag", "20kg", 5, 38.40, "low_stock", False), ("Bulka Bag (1T)", "BAG-BULKA-1T", "bags", "each", "1 tonne", 1, 9.50, "in_stock", False), ("Bulk Maize Load", "BLK-MAIZE-T", "bulk_loads", "tonne", "per tonne", 1, 685.00, "made_to_order", False), ("Custom Horse Blend", "CST-HORSE-20", "custom_blends", "20kg bag", "20kg", 10, None, "made_to_order", True), ("Delivery (per pallet)", "SVC-DELIVERY", "services", "pallet", "1 pallet", 1, 45.00, "in_stock", False), ] def seed_ordering(db: Session) -> dict[str, int]: created = {"categories": 0, "products": 0, "customer": 0, "pricing": 0} # Categories existing_categories = { c.slug for c in db.scalars(select(ProductCategory).where(ProductCategory.tenant_id == ORDERING_TENANT)).all() } for slug, name, sort_order in _CATEGORIES: if slug in existing_categories: continue db.add(ProductCategory(tenant_id=ORDERING_TENANT, slug=slug, name=name, sort_order=sort_order)) created["categories"] += 1 # Catalogue products existing_skus = { p.sku for p in db.scalars(select(CatalogueProduct).where(CatalogueProduct.tenant_id == ORDERING_TENANT)).all() } product_by_sku: dict[str, CatalogueProduct] = {} for name, sku, category, uom, unit_size, moq, base_price, stock, requires_quote in _PRODUCTS: if sku in existing_skus: continue product = CatalogueProduct( tenant_id=ORDERING_TENANT, name=name, sku=sku, category=category, unit_of_measure=uom, unit_size=unit_size, min_order_quantity=moq, base_price=base_price, stock_status=stock, requires_quote=requires_quote, ) db.add(product) product_by_sku[sku] = product created["products"] += 1 db.flush() # Notification settings row if db.scalar(select(NotificationSetting).where(NotificationSetting.tenant_id == ORDERING_TENANT)) is None: db.add( NotificationSetting( tenant_id=ORDERING_TENANT, internal_recipients=settings.admin_email, send_customer_confirmation=True, require_po_number=False, from_email=settings.admin_email, ) ) # Demo ordering customer + buyer user demo = db.scalar(select(ClientAccount).where(ClientAccount.client_code == "RIVERSIDE")) if demo is None: demo = ClientAccount( tenant_id="riverside-stockfeeds", name="Riverside Stockfeeds", client_code="RIVERSIDE", status="active", notes="Demo B2B ordering customer", ) db.add(demo) db.flush() created["customer"] += 1 info = MODULE_INDEX["ordering"] db.add( ClientFeatureAccess( tenant_id=demo.tenant_id, client_account_id=demo.id, feature_key="ordering", feature_name=info["module_name"], feature_group=info["module_group"], description=info["description"], enabled=True, ) ) buyer = ClientUser( tenant_id=demo.tenant_id, client_account_id=demo.id, full_name="Riverside Buyer", email="buyer@riverside.example", role="buyer", status="active", is_new_user=False, ) db.add(buyer) db.flush() ensure_user_module_permissions(db, buyer) # A customer-specific contract price + a small discount on the rest. maize = db.scalar( select(CatalogueProduct).where( CatalogueProduct.tenant_id == ORDERING_TENANT, CatalogueProduct.sku == "GRN-MAIZE-20" ) ) if maize is not None: db.add( CustomerProductPrice( tenant_id=ORDERING_TENANT, client_account_id=demo.id, product_id=maize.id, unit_price=22.00, rule_type="contract", contract_reference="2026 supply agreement", ) ) db.add( CustomerPriceAssignment( tenant_id=ORDERING_TENANT, client_account_id=demo.id, price_list_id=None, discount_percent=5.0, ) ) created["pricing"] += 1 return created