v1.3 - client and admin scaffolding
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Select, select
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
from app.models.client_access import ClientAccount, ClientFeatureAccess, ClientUser
|
||||
|
||||
|
||||
def client_access_query() -> Select[tuple[ClientAccount]]:
|
||||
return (
|
||||
select(ClientAccount)
|
||||
.options(selectinload(ClientAccount.users), selectinload(ClientAccount.features))
|
||||
.order_by(ClientAccount.name)
|
||||
)
|
||||
|
||||
|
||||
def list_client_accounts(db: Session) -> list[ClientAccount]:
|
||||
return db.scalars(client_access_query()).all()
|
||||
|
||||
|
||||
def serialize_client_user(user: ClientUser) -> dict:
|
||||
return {
|
||||
"id": user.id,
|
||||
"client_account_id": user.client_account_id,
|
||||
"full_name": user.full_name,
|
||||
"email": user.email,
|
||||
"role": user.role,
|
||||
"status": user.status,
|
||||
"is_new_user": user.is_new_user,
|
||||
"last_login_at": user.last_login_at,
|
||||
"created_at": user.created_at,
|
||||
}
|
||||
|
||||
|
||||
def serialize_client_feature(feature: ClientFeatureAccess) -> dict:
|
||||
return {
|
||||
"id": feature.id,
|
||||
"client_account_id": feature.client_account_id,
|
||||
"feature_key": feature.feature_key,
|
||||
"feature_name": feature.feature_name,
|
||||
"feature_group": feature.feature_group,
|
||||
"description": feature.description,
|
||||
"enabled": feature.enabled,
|
||||
"updated_at": feature.updated_at,
|
||||
"created_at": feature.created_at,
|
||||
}
|
||||
|
||||
|
||||
def serialize_client_account(client: ClientAccount) -> dict:
|
||||
users = [serialize_client_user(user) for user in client.users]
|
||||
features = [serialize_client_feature(feature) for feature in client.features]
|
||||
active_users = sum(1 for user in users if user["status"] == "active")
|
||||
new_users = sum(1 for user in users if user["is_new_user"])
|
||||
enabled_features = sum(1 for feature in features if feature["enabled"])
|
||||
|
||||
return {
|
||||
"id": client.id,
|
||||
"tenant_id": client.tenant_id,
|
||||
"name": client.name,
|
||||
"client_code": client.client_code,
|
||||
"status": client.status,
|
||||
"powerbi_workspace": client.powerbi_workspace,
|
||||
"notes": client.notes,
|
||||
"created_at": client.created_at,
|
||||
"users": users,
|
||||
"features": features,
|
||||
"active_user_count": active_users,
|
||||
"new_user_count": new_users,
|
||||
"enabled_feature_count": enabled_features,
|
||||
"total_feature_count": len(features),
|
||||
}
|
||||
|
||||
|
||||
def build_client_access_export(clients: list[ClientAccount]) -> dict:
|
||||
serialized_clients = [serialize_client_account(client) for client in clients]
|
||||
client_rows = []
|
||||
user_rows = []
|
||||
feature_rows = []
|
||||
|
||||
for client in serialized_clients:
|
||||
client_rows.append(
|
||||
{
|
||||
"client_id": client["id"],
|
||||
"tenant_id": client["tenant_id"],
|
||||
"client_name": client["name"],
|
||||
"client_code": client["client_code"],
|
||||
"client_status": client["status"],
|
||||
"powerbi_workspace": client["powerbi_workspace"],
|
||||
"active_user_count": client["active_user_count"],
|
||||
"new_user_count": client["new_user_count"],
|
||||
"enabled_feature_count": client["enabled_feature_count"],
|
||||
"total_feature_count": client["total_feature_count"],
|
||||
}
|
||||
)
|
||||
|
||||
for user in client["users"]:
|
||||
user_rows.append(
|
||||
{
|
||||
"client_id": client["id"],
|
||||
"client_name": client["name"],
|
||||
"user_id": user["id"],
|
||||
"full_name": user["full_name"],
|
||||
"email": user["email"],
|
||||
"role": user["role"],
|
||||
"status": user["status"],
|
||||
"is_new_user": user["is_new_user"],
|
||||
"last_login_at": user["last_login_at"],
|
||||
"created_at": user["created_at"],
|
||||
}
|
||||
)
|
||||
|
||||
for feature in client["features"]:
|
||||
feature_rows.append(
|
||||
{
|
||||
"client_id": client["id"],
|
||||
"client_name": client["name"],
|
||||
"feature_id": feature["id"],
|
||||
"feature_key": feature["feature_key"],
|
||||
"feature_name": feature["feature_name"],
|
||||
"feature_group": feature["feature_group"],
|
||||
"enabled": feature["enabled"],
|
||||
"updated_at": feature["updated_at"],
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"generated_at": datetime.utcnow(),
|
||||
"client_rows": client_rows,
|
||||
"user_rows": user_rows,
|
||||
"feature_rows": feature_rows,
|
||||
"clients": serialized_clients,
|
||||
}
|
||||
@@ -123,7 +123,11 @@ def _get_process_costs(db: Session, process_name: str | None, overrides: dict) -
|
||||
if not process_name:
|
||||
return 0.0, 0.0, 0.0, ["Missing bagging process"]
|
||||
|
||||
rule = db.scalar(select(ProcessCostRule).where(ProcessCostRule.process_name == process_name))
|
||||
tenant_id = overrides.get("tenant_id")
|
||||
query = select(ProcessCostRule).where(ProcessCostRule.process_name == process_name)
|
||||
if tenant_id:
|
||||
query = query.where(ProcessCostRule.tenant_id == tenant_id)
|
||||
rule = db.scalar(query)
|
||||
if rule is None:
|
||||
return 0.0, 0.0, 0.0, [f"Process rule not found for {process_name}"]
|
||||
|
||||
@@ -138,13 +142,14 @@ def _get_packaging_cost(db: Session, product: Product, overrides: dict) -> tuple
|
||||
if product.own_bag:
|
||||
return 0.0, []
|
||||
|
||||
rule = db.scalar(
|
||||
select(PackagingCostRule).where(
|
||||
PackagingCostRule.sale_type == product.sale_type,
|
||||
PackagingCostRule.unit_of_measure == product.unit_of_measure,
|
||||
PackagingCostRule.own_bag == product.own_bag,
|
||||
)
|
||||
query = select(PackagingCostRule).where(
|
||||
PackagingCostRule.sale_type == product.sale_type,
|
||||
PackagingCostRule.unit_of_measure == product.unit_of_measure,
|
||||
PackagingCostRule.own_bag == product.own_bag,
|
||||
)
|
||||
if product.tenant_id:
|
||||
query = query.where(PackagingCostRule.tenant_id == product.tenant_id)
|
||||
rule = db.scalar(query)
|
||||
if rule is None:
|
||||
return 0.0, ["Packaging rule not found"]
|
||||
|
||||
@@ -152,12 +157,13 @@ def _get_packaging_cost(db: Session, product: Product, overrides: dict) -> tuple
|
||||
|
||||
|
||||
def _get_freight_cost(db: Session, product: Product, overrides: dict) -> tuple[float, list[str]]:
|
||||
rule = db.scalar(
|
||||
select(FreightCostRule).where(
|
||||
FreightCostRule.sale_type == product.sale_type,
|
||||
FreightCostRule.unit_of_measure == product.unit_of_measure,
|
||||
)
|
||||
query = select(FreightCostRule).where(
|
||||
FreightCostRule.sale_type == product.sale_type,
|
||||
FreightCostRule.unit_of_measure == product.unit_of_measure,
|
||||
)
|
||||
if product.tenant_id:
|
||||
query = query.where(FreightCostRule.tenant_id == product.tenant_id)
|
||||
rule = db.scalar(query)
|
||||
if rule is None:
|
||||
return 0.0, ["Freight rule not found"]
|
||||
return overrides.get("freight_costs", {}).get(str(rule.id), rule.cost_per_unit), []
|
||||
@@ -185,9 +191,11 @@ def _extract_unit_quantity_kg(unit_of_measure: str) -> float:
|
||||
|
||||
def calculate_product_cost(db: Session, product_id: int, overrides: dict | None = None) -> dict:
|
||||
overrides = overrides or {}
|
||||
overrides = {**overrides, "tenant_id": overrides.get("tenant_id")}
|
||||
product = db.scalar(select(Product).where(Product.id == product_id).options(selectinload(Product.mix)))
|
||||
if product is None:
|
||||
raise ValueError(f"Product {product_id} not found")
|
||||
overrides["tenant_id"] = product.tenant_id
|
||||
|
||||
mix_result = calculate_mix_cost(db, product.mix_id, overrides=overrides)
|
||||
warnings = list(mix_result["warnings"])
|
||||
|
||||
@@ -8,13 +8,14 @@ from app.services.costing_engine import calculate_product_cost
|
||||
|
||||
def run_scenario(db: Session, scenario: Scenario) -> list[dict]:
|
||||
db.execute(delete(CostingResult).where(CostingResult.scenario_id == scenario.id))
|
||||
products = db.scalars(select(Product).order_by(Product.name)).all()
|
||||
products = db.scalars(select(Product).where(Product.tenant_id == scenario.tenant_id).order_by(Product.name)).all()
|
||||
results: list[dict] = []
|
||||
|
||||
for product in products:
|
||||
breakdown = calculate_product_cost(db, product.id, overrides=scenario.overrides or {})
|
||||
db.add(
|
||||
CostingResult(
|
||||
tenant_id=scenario.tenant_id,
|
||||
scenario_id=scenario.id,
|
||||
product_id=product.id,
|
||||
finished_product_delivered=breakdown["finished_product_delivered"],
|
||||
@@ -29,4 +30,3 @@ def run_scenario(db: Session, scenario: Scenario) -> list[dict]:
|
||||
scenario.status = "reviewed"
|
||||
db.commit()
|
||||
return results
|
||||
|
||||
|
||||
Reference in New Issue
Block a user