Mix calculator

This commit is contained in:
2026-04-29 23:05:27 +12:00
parent 3f3b1d0f25
commit 5cb95266d8
28 changed files with 2943 additions and 46 deletions
+57 -4
View File
@@ -17,6 +17,7 @@ MODULE_CATALOG = (
("dashboard", "Dashboard", "workspace", "Top-level operational dashboard"),
("raw_materials", "Raw Materials", "costing", "Maintain live material costs and versions"),
("mix_master", "Mix Master", "costing", "Create and maintain mix worksheets"),
("mix_calculator", "Mix Calculator", "production", "Create and review client-specific mix calculation sessions"),
("products", "Products", "pricing", "Review finished product pricing"),
("scenarios", "Scenarios", "planning", "Run scenario overrides and comparisons"),
("powerbi_export", "Power BI Export", "reporting", "Expose client access data to BI consumers"),
@@ -43,6 +44,7 @@ def client_access_query() -> Select[tuple[ClientAccount]]:
def list_client_accounts(db: Session) -> list[ClientAccount]:
ensure_client_user_module_permissions(db)
ensure_client_feature_access(db)
return db.scalars(client_access_query()).all()
@@ -50,9 +52,19 @@ def get_client_user_by_email(db: Session, *, email: str, tenant_id: str | None =
statement = select(ClientUser).where(ClientUser.email == email)
if tenant_id:
statement = statement.where(ClientUser.tenant_id == tenant_id)
return db.scalar(
user = db.scalar(
statement.options(selectinload(ClientUser.module_permissions)).order_by(ClientUser.id.desc())
)
if user is None:
return None
if ensure_user_module_permissions(db, user):
db.commit()
return db.scalar(
statement.options(selectinload(ClientUser.module_permissions)).order_by(ClientUser.id.desc())
)
return user
def module_access_map(user: ClientUser) -> dict[str, str]:
@@ -66,13 +78,15 @@ def has_access_level(access_level: str | None, minimum_level: str) -> bool:
def default_access_level_for_role(role: str, module_key: str) -> str:
normalized = role.strip().lower()
if normalized == "superadmin":
return "manage" if module_key == "client_access" else "edit"
return "manage" if module_key in {"client_access", "mix_calculator"} else "edit"
if normalized == "admin":
if module_key == "mix_calculator":
return "manage"
return "edit" if module_key != "client_access" else "none"
if normalized == "operator":
return "edit" if module_key in {"dashboard", "raw_materials", "mix_master", "products", "scenarios"} else "none"
return "edit" if module_key in {"dashboard", "raw_materials", "mix_master", "mix_calculator", "products", "scenarios"} else "none"
if normalized == "viewer":
return "view" if module_key in {"dashboard", "products", "powerbi_export"} else "none"
return "view" if module_key in {"dashboard", "mix_calculator", "products", "powerbi_export"} else "none"
return "none"
@@ -106,6 +120,45 @@ def ensure_client_user_module_permissions(db: Session) -> None:
db.commit()
def ensure_client_feature_access(db: Session) -> None:
clients = db.scalars(
select(ClientAccount).options(
selectinload(ClientAccount.users).selectinload(ClientUser.module_permissions),
selectinload(ClientAccount.features),
)
).all()
changed = False
for client in clients:
existing_feature_keys = {feature.feature_key for feature in client.features}
permission_levels: dict[str, str] = {}
for user in client.users:
for permission in user.module_permissions:
current_level = permission_levels.get(permission.module_key, "none")
if ACCESS_LEVEL_ORDER.get(permission.access_level, 0) > ACCESS_LEVEL_ORDER.get(current_level, 0):
permission_levels[permission.module_key] = permission.access_level
for feature_key, feature_name, feature_group, description in MODULE_CATALOG:
if feature_key in existing_feature_keys:
continue
db.add(
ClientFeatureAccess(
tenant_id=client.tenant_id,
client_account_id=client.id,
feature_key=feature_key,
feature_name=feature_name,
feature_group=feature_group,
description=description,
enabled=has_access_level(permission_levels.get(feature_key), "view"),
)
)
changed = True
if changed:
db.commit()
def serialize_client_user(user: ClientUser) -> dict:
return {
"id": user.id,