feat: add scenarios list page with metrics and quick actions
Some checks failed
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / lint (push) Failing after 15s
CI / deploy (push) Has been skipped

- Introduced a new template for listing scenarios associated with a project.
- Added metrics for total, active, draft, and archived scenarios.
- Implemented quick actions for creating new scenarios and reviewing project overview.
- Enhanced navigation with breadcrumbs for better user experience.

refactor: update Opex and Profitability templates for consistency

- Changed titles and button labels for clarity in Opex and Profitability templates.
- Updated form IDs and action URLs for better alignment with new naming conventions.
- Improved navigation links to include scenario and project overviews.

test: add integration tests for Opex calculations

- Created new tests for Opex calculation HTML and JSON flows.
- Validated successful calculations and ensured correct data persistence.
- Implemented tests for currency mismatch and unsupported frequency scenarios.

test: enhance project and scenario route tests

- Added tests to verify scenario list rendering and calculator shortcuts.
- Ensured scenario detail pages link back to the portfolio correctly.
- Validated project detail pages show associated scenarios accurately.
This commit is contained in:
2025-11-13 16:21:36 +01:00
parent 4f00bf0d3c
commit 522b1e4105
54 changed files with 3419 additions and 700 deletions

View File

@@ -4,23 +4,26 @@ from typing import List
from fastapi import APIRouter, Depends, Form, HTTPException, Request, status
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from dependencies import (
get_pricing_metadata,
get_unit_of_work,
require_any_role,
require_any_role_html,
require_project_resource,
require_project_resource_html,
require_roles,
require_roles_html,
)
from models import MiningOperationType, Project, ScenarioStatus, User
from schemas.project import ProjectCreate, ProjectRead, ProjectUpdate
from services.exceptions import EntityConflictError
from services.pricing import PricingMetadata
from services.unit_of_work import UnitOfWork
from routes.template_filters import create_templates
router = APIRouter(prefix="/projects", tags=["Projects"])
templates = Jinja2Templates(directory="templates")
templates = create_templates()
READ_ROLES = ("viewer", "analyst", "project_manager", "admin")
MANAGE_ROLES = ("project_manager", "admin")
@@ -79,7 +82,7 @@ def create_project(
)
def project_list_page(
request: Request,
_: User = Depends(require_any_role(*READ_ROLES)),
_: User = Depends(require_any_role_html(*READ_ROLES)),
uow: UnitOfWork = Depends(get_unit_of_work),
) -> HTMLResponse:
projects = _require_project_repo(uow).list(with_children=True)
@@ -101,7 +104,8 @@ def project_list_page(
name="projects.create_project_form",
)
def create_project_form(
request: Request, _: User = Depends(require_roles(*MANAGE_ROLES))
request: Request,
_: User = Depends(require_roles_html(*MANAGE_ROLES)),
) -> HTMLResponse:
return templates.TemplateResponse(
request,
@@ -122,7 +126,7 @@ def create_project_form(
)
def create_project_submit(
request: Request,
_: User = Depends(require_roles(*MANAGE_ROLES)),
_: User = Depends(require_roles_html(*MANAGE_ROLES)),
name: str = Form(...),
location: str | None = Form(None),
operation_type: str = Form(...),
@@ -221,7 +225,8 @@ def delete_project(
)
def view_project(
request: Request,
project: Project = Depends(require_project_resource()),
_: User = Depends(require_any_role_html(*READ_ROLES)),
project: Project = Depends(require_project_resource_html()),
uow: UnitOfWork = Depends(get_unit_of_work),
) -> HTMLResponse:
project = _require_project_repo(uow).get(project.id, with_children=True)
@@ -256,8 +261,9 @@ def view_project(
)
def edit_project_form(
request: Request,
_: User = Depends(require_roles_html(*MANAGE_ROLES)),
project: Project = Depends(
require_project_resource(require_manage=True)
require_project_resource_html(require_manage=True)
),
) -> HTMLResponse:
return templates.TemplateResponse(
@@ -283,8 +289,9 @@ def edit_project_form(
)
def edit_project_submit(
request: Request,
_: User = Depends(require_roles_html(*MANAGE_ROLES)),
project: Project = Depends(
require_project_resource(require_manage=True)
require_project_resource_html(require_manage=True)
),
name: str = Form(...),
location: str | None = Form(None),