2026-04-25 22:51:36 +12:00
|
|
|
from datetime import date, datetime
|
2026-04-25 20:43:37 +12:00
|
|
|
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
|
|
|
|
from app.db.session import Base, SessionLocal, engine
|
|
|
|
|
from app.models.assumption import FreightCostRule, PackagingCostRule, ProcessCostRule
|
2026-04-29 01:21:16 +12:00
|
|
|
from app.models.client_access import ClientAccessAuditEvent, ClientAccount, ClientFeatureAccess, ClientUser, ClientUserModulePermission
|
2026-04-25 20:43:37 +12:00
|
|
|
from app.models.mix import Mix, MixIngredient
|
|
|
|
|
from app.models.product import Product
|
|
|
|
|
from app.models.raw_material import RawMaterial, RawMaterialPriceVersion
|
2026-04-29 01:21:16 +12:00
|
|
|
from app.services.client_access_service import MODULE_CATALOG, default_access_level_for_role
|
2026-04-25 22:51:36 +12:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def seed_client_access(db):
|
|
|
|
|
existing = db.scalar(select(ClientAccount.id))
|
|
|
|
|
if existing is not None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
specialty = ClientAccount(
|
|
|
|
|
tenant_id="hunter-premium-produce",
|
|
|
|
|
name="Hunter Premium Produce",
|
|
|
|
|
client_code="HPP",
|
|
|
|
|
status="active",
|
|
|
|
|
powerbi_workspace="hunter-premium-produce-prod",
|
|
|
|
|
notes="Primary production client for the Lean 101 admin and access workflows",
|
|
|
|
|
)
|
|
|
|
|
loft = ClientAccount(
|
|
|
|
|
tenant_id="loft-grains",
|
|
|
|
|
name="Loft Grains",
|
|
|
|
|
client_code="LOFT",
|
|
|
|
|
status="onboarding",
|
|
|
|
|
powerbi_workspace="farm-ops-sandbox",
|
|
|
|
|
notes="Onboarding workspace used to test staged user enablement",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
db.add_all([specialty, loft])
|
|
|
|
|
db.flush()
|
|
|
|
|
|
|
|
|
|
specialty.users.extend(
|
|
|
|
|
[
|
|
|
|
|
ClientUser(
|
|
|
|
|
tenant_id=specialty.tenant_id,
|
|
|
|
|
full_name="Amelia Hart",
|
|
|
|
|
email="operator@example.com",
|
2026-04-29 01:21:16 +12:00
|
|
|
role="superadmin",
|
2026-04-25 22:51:36 +12:00
|
|
|
status="active",
|
|
|
|
|
is_new_user=False,
|
|
|
|
|
last_login_at=datetime(2026, 4, 24, 11, 30),
|
|
|
|
|
),
|
|
|
|
|
ClientUser(
|
|
|
|
|
tenant_id=specialty.tenant_id,
|
|
|
|
|
full_name="Ethan Cole",
|
|
|
|
|
email="ethan.cole@hunterpremiumproduce.example",
|
|
|
|
|
role="operator",
|
|
|
|
|
status="invited",
|
|
|
|
|
is_new_user=True,
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
loft.users.extend(
|
|
|
|
|
[
|
|
|
|
|
ClientUser(
|
|
|
|
|
tenant_id=loft.tenant_id,
|
|
|
|
|
full_name="Ruby Singh",
|
|
|
|
|
email="ruby.singh@loftgrains.example",
|
|
|
|
|
role="viewer",
|
|
|
|
|
status="active",
|
|
|
|
|
is_new_user=False,
|
|
|
|
|
last_login_at=datetime(2026, 4, 22, 9, 10),
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
enabled_feature_map = {
|
2026-04-29 23:05:27 +12:00
|
|
|
"hunter-premium-produce": {"dashboard", "raw_materials", "mix_master", "mix_calculator", "products", "scenarios", "powerbi_export", "client_access"},
|
|
|
|
|
"loft-grains": {"dashboard", "mix_calculator", "products", "powerbi_export"},
|
2026-04-25 22:51:36 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for client in (specialty, loft):
|
|
|
|
|
enabled_keys = enabled_feature_map[client.tenant_id]
|
2026-04-29 01:21:16 +12:00
|
|
|
for feature_key, feature_name, feature_group, description in MODULE_CATALOG:
|
2026-04-25 22:51:36 +12:00
|
|
|
client.features.append(
|
|
|
|
|
ClientFeatureAccess(
|
|
|
|
|
tenant_id=client.tenant_id,
|
|
|
|
|
feature_key=feature_key,
|
|
|
|
|
feature_name=feature_name,
|
|
|
|
|
feature_group=feature_group,
|
|
|
|
|
description=description,
|
|
|
|
|
enabled=feature_key in enabled_keys,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-29 01:21:16 +12:00
|
|
|
for user in client.users:
|
|
|
|
|
for module_key, _, _, _ in MODULE_CATALOG:
|
|
|
|
|
user.module_permissions.append(
|
|
|
|
|
ClientUserModulePermission(
|
|
|
|
|
tenant_id=client.tenant_id,
|
|
|
|
|
client_account_id=client.id,
|
|
|
|
|
module_key=module_key,
|
|
|
|
|
access_level=default_access_level_for_role(user.role, module_key),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
specialty.audit_events.append(
|
|
|
|
|
ClientAccessAuditEvent(
|
|
|
|
|
tenant_id=specialty.tenant_id,
|
|
|
|
|
actor_type="seed",
|
|
|
|
|
actor_name="Lean 101 Seeder",
|
|
|
|
|
actor_email="system@lean101.local",
|
|
|
|
|
actor_role="system",
|
|
|
|
|
action="client_access.seeded",
|
|
|
|
|
target_type="client_account",
|
|
|
|
|
target_id=specialty.id,
|
|
|
|
|
module_key="client_access",
|
|
|
|
|
summary="Initial client access controls, module permissions, and feature flags were seeded.",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2026-04-25 22:51:36 +12:00
|
|
|
|
|
|
|
|
def seed_costing_workspace(db):
|
|
|
|
|
existing = db.scalar(select(RawMaterial.id))
|
|
|
|
|
if existing is not None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
tenant_id = "hunter-premium-produce"
|
|
|
|
|
|
|
|
|
|
maize = RawMaterial(tenant_id=tenant_id, name="Maize", supplier="Example Supplier", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
|
|
|
|
barley = RawMaterial(tenant_id=tenant_id, name="Barley", supplier="Example Supplier", unit_of_measure="tonne", kg_per_unit=1000, status="active")
|
|
|
|
|
acid_buf = RawMaterial(tenant_id=tenant_id, name="Acid Buf", supplier="Example Supplier", unit_of_measure="bag", kg_per_unit=25, status="active")
|
|
|
|
|
|
|
|
|
|
maize.price_versions.append(RawMaterialPriceVersion(tenant_id=tenant_id, market_value=520, waste_percentage=0.02, effective_date=date(2026, 4, 1)))
|
|
|
|
|
barley.price_versions.append(RawMaterialPriceVersion(tenant_id=tenant_id, market_value=470, waste_percentage=0.015, effective_date=date(2026, 4, 1)))
|
|
|
|
|
acid_buf.price_versions.append(RawMaterialPriceVersion(tenant_id=tenant_id, market_value=39, waste_percentage=0.0, effective_date=date(2026, 4, 1)))
|
|
|
|
|
|
|
|
|
|
db.add_all([maize, barley, acid_buf])
|
|
|
|
|
db.flush()
|
|
|
|
|
|
|
|
|
|
db.add_all(
|
|
|
|
|
[
|
|
|
|
|
ProcessCostRule(tenant_id=tenant_id, process_name="standard_bagging", grading_cost=0.055, bagging_cost=0.04, cracking_cost=0.0),
|
|
|
|
|
ProcessCostRule(tenant_id=tenant_id, process_name="bulk_loadout", grading_cost=0.03, bagging_cost=0.0, cracking_cost=0.0),
|
|
|
|
|
PackagingCostRule(tenant_id=tenant_id, sale_type="standard", unit_of_measure="20kg bag", own_bag=False, bag_cost=0.63),
|
|
|
|
|
PackagingCostRule(tenant_id=tenant_id, sale_type="bulka", unit_of_measure="550kg bulka", own_bag=False, bag_cost=7.5),
|
|
|
|
|
FreightCostRule(tenant_id=tenant_id, sale_type="standard", unit_of_measure="20kg bag", cost_per_unit=1.45),
|
|
|
|
|
FreightCostRule(tenant_id=tenant_id, sale_type="bulka", unit_of_measure="550kg bulka", cost_per_unit=18.0),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
db.flush()
|
|
|
|
|
|
|
|
|
|
mix = Mix(tenant_id=tenant_id, client_name="Hunter Premium Produce", name="Hunter Orchard Blend", status="active", version=1, notes="Seed recipe for MVP")
|
|
|
|
|
db.add(mix)
|
|
|
|
|
db.flush()
|
|
|
|
|
|
|
|
|
|
db.add_all(
|
|
|
|
|
[
|
|
|
|
|
MixIngredient(tenant_id=tenant_id, mix_id=mix.id, raw_material_id=maize.id, quantity_kg=180),
|
|
|
|
|
MixIngredient(tenant_id=tenant_id, mix_id=mix.id, raw_material_id=barley.id, quantity_kg=95),
|
|
|
|
|
MixIngredient(tenant_id=tenant_id, mix_id=mix.id, raw_material_id=acid_buf.id, quantity_kg=5),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
db.flush()
|
|
|
|
|
|
|
|
|
|
db.add(
|
|
|
|
|
Product(
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
client_name="Hunter Premium Produce",
|
|
|
|
|
item_id="SKU-001",
|
|
|
|
|
name="Hunter Orchard Blend 20kg",
|
|
|
|
|
mix_id=mix.id,
|
|
|
|
|
sale_type="standard",
|
|
|
|
|
own_bag=False,
|
|
|
|
|
unit_of_measure="20kg bag",
|
|
|
|
|
items_per_pallet=50,
|
|
|
|
|
bagging_process="standard_bagging",
|
|
|
|
|
distributor_margin=0.225,
|
|
|
|
|
wholesale_margin=0.18,
|
|
|
|
|
notes="Reference product for formula parity work",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-04-25 20:43:37 +12:00
|
|
|
def seed_if_empty():
|
|
|
|
|
Base.metadata.create_all(bind=engine)
|
|
|
|
|
with SessionLocal() as db:
|
2026-04-25 22:51:36 +12:00
|
|
|
seed_costing_workspace(db)
|
|
|
|
|
seed_client_access(db)
|
2026-04-25 20:43:37 +12:00
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
seed_if_empty()
|