from __future__ import annotations

from contextlib import contextmanager
from dataclasses import dataclass
from datetime import date, datetime, time, timedelta, timezone
import hashlib
import json
import os
from pathlib import Path
import re
import secrets
import sqlite3
import threading
from typing import Literal
import unicodedata
import uuid
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

from pydantic import BaseModel, ConfigDict, Field

from app.core.config import get_settings
from app.core.mock_state import MODULE_CATALOG
from app.models.tenant import Tenant
from app.models.tenant_module import TenantModule
from app.models.user import User
from app.models.venue import Venue

SUPER_ADMIN_TENANT_ID = "tenant_powerup_admin"
SUPER_ADMIN_VENUE_ID = "venue_powerup_admin"
SUPER_ADMIN_USER_ID = "user_powerup_admin"
SUPER_ADMIN_TENANT_NAME = "PowerUp Admin"
SUPER_ADMIN_TENANT_SLUG = "powerup-admin"
SUPER_ADMIN_USERNAME = "zanchinfede@gmail.com"
SUPER_ADMIN_EMAIL = "superadmin@powerup.local"
TENANT_PERMISSION_KEYS = (
    "assistant",
    "ordini",
    "prenotazioni",
    "documents",
    "menu",
    "homemade",
    "homemade_manage",
    "fiscal_documents",
    "timeclock",
    "inventory",
    "consumptions",
    "reports",
    "tips_sala",
    "tips_sala_manage",
    "tips_bar",
    "tips_bar_manage",
)
ASSISTANT_SCOPE_KEYS = (
    "ordini",
    "prenotazioni",
    "documents",
    "menu",
    "homemade",
    "fiscal_documents",
    "timeclock",
    "inventory",
    "tips_sala",
    "tips_bar",
)
DEFAULT_STAFF_PERMISSIONS = (
    "prenotazioni",
    "menu",
    "homemade",
)
HOMEMADE_STOCK_UNIT = "pz"
HOMEMADE_USAGE_SCOPES = {"bar", "restaurant", "both"}
HOMEMADE_USAGE_SCOPE_LABELS = {
    "bar": "Bar",
    "restaurant": "Ristorante",
    "both": "Bar e ristorante",
}


def _utcnow() -> datetime:
    return datetime.now(timezone.utc)


def _iso_now() -> str:
    return _utcnow().isoformat()


def _tenant_local_timezone() -> ZoneInfo:
    try:
        return ZoneInfo(get_settings().assistant_timezone)
    except (ZoneInfoNotFoundError, ValueError):
        return ZoneInfo("Europe/Rome")


def _local_now() -> datetime:
    return datetime.now(_tenant_local_timezone())


def _local_iso_now() -> str:
    return _local_now().isoformat()


def _normalize_slug(value: str) -> str:
    normalized = unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
    slug = re.sub(r"[^a-zA-Z0-9]+", "-", normalized.lower()).strip("-")
    return slug or f"tenant-{secrets.token_hex(3)}"


def _normalize_lookup(value: str) -> str:
    return value.strip().lower()


def _normalize_homemade_usage_scope(value: str | None) -> Literal["bar", "restaurant", "both"]:
    normalized = (value or "both").strip().lower()
    if normalized in {"ristorante", "restaurant"}:
        return "restaurant"
    if normalized in {"bar", "club"}:
        return "bar"
    if normalized in {"both", "entrambi", "entrambe", "all"}:
        return "both"
    return "both"


def _homemade_usage_scope_label(value: str | None) -> str:
    return HOMEMADE_USAGE_SCOPE_LABELS[_normalize_homemade_usage_scope(value)]


def _normalize_inventory_barcode(value: str | None) -> str:
    return re.sub(r"\s+", "", str(value or "").strip()).lower()


def _build_carton_barcode_aliases(value: str | None) -> tuple[str, ...]:
    normalized = _normalize_inventory_barcode(value)
    if not normalized:
        return tuple()
    aliases = [normalized]
    if normalized.isdigit() and len(normalized) > 1:
        alternate = normalized[1:] if normalized.startswith("0") else f"0{normalized}"
        if alternate and alternate != normalized:
            aliases.append(alternate)
    return tuple(dict.fromkeys(aliases))


def _normalize_homemade_ingredient_unit(value: str | None) -> Literal["ml", "g", "g_per_liter", "drops_per_liter", "part"]:
    normalized = (value or "").strip().lower()
    if normalized in {"g", "gr", "grammi", "grammo"}:
        return "g"
    if normalized in {"g_per_liter", "g/l", "gr/l", "gr litro", "g litro", "grammi litro"}:
        return "g_per_liter"
    if normalized in {
        "drops_per_liter",
        "drop/l",
        "drops/l",
        "gocce/l",
        "goccia litro",
        "gocce litro",
        "gocce per litro",
        "gocce/litro",
    }:
        return "drops_per_liter"
    if normalized in {"part", "parts", "parte", "parti"}:
        return "part"
    return "ml"


def _derive_homemade_recipe_measurement_unit(ingredients: list[dict[str, object]]) -> Literal["ml", "g"]:
    proportional_units = {
        str(item.get("measurement_unit") or "ml")
        for item in ingredients
        if str(item.get("measurement_unit") or "ml") in {"ml", "g", "part"}
    }
    return "g" if proportional_units == {"g"} else "ml"


def _compute_homemade_reference_total(ingredients: list[dict[str, object]]) -> float:
    liquid_reference_total = 0.0
    fallback_proportional_total = 0.0
    for ingredient in ingredients:
        measurement_unit = _normalize_homemade_ingredient_unit(str(ingredient.get("measurement_unit") or "ml"))
        part_amount = float(ingredient.get("part_amount") or 0.0)
        if part_amount <= 0:
            continue
        if measurement_unit not in {"g_per_liter", "drops_per_liter"}:
            fallback_proportional_total += part_amount
        if measurement_unit in {"ml", "part"}:
            liquid_reference_total += part_amount
    return liquid_reference_total if liquid_reference_total > 0 else fallback_proportional_total


def _compute_homemade_liquid_reference_total(ingredients: list[dict[str, object]]) -> float:
    total = 0.0
    for ingredient in ingredients:
        measurement_unit = _normalize_homemade_ingredient_unit(str(ingredient.get("measurement_unit") or "ml"))
        if measurement_unit not in {"ml", "part"}:
            continue
        part_amount = float(ingredient.get("part_amount") or 0.0)
        if part_amount > 0:
            total += part_amount
    return total


def _has_homemade_explicit_yield(
    *,
    yield_ingredient_name: object | None,
    yield_input_amount: object | None,
    yield_output_ml: object | None,
) -> bool:
    try:
        input_amount = float(yield_input_amount or 0)
        output_ml = float(yield_output_ml or 0)
    except (TypeError, ValueError):
        return False
    return bool(str(yield_ingredient_name or "").strip()) and input_amount > 0 and output_ml > 0


def _compute_homemade_recipe_reference_total(
    ingredients: list[dict[str, object]],
    *,
    yield_ingredient_name: object | None = None,
    yield_input_amount: object | None = None,
    yield_output_ml: object | None = None,
) -> float:
    if _has_homemade_explicit_yield(
        yield_ingredient_name=yield_ingredient_name,
        yield_input_amount=yield_input_amount,
        yield_output_ml=yield_output_ml,
    ):
        return float(yield_output_ml or 0)
    liquid_reference_total = _compute_homemade_liquid_reference_total(ingredients)
    if liquid_reference_total > 0:
        return liquid_reference_total
    return _compute_homemade_reference_total(ingredients)


def _normalize_tips_area(value: str | None) -> Literal["sala", "bar"]:
    normalized = (value or "").strip().lower()
    if normalized not in {"sala", "bar"}:
        raise ValueError("Area mance non valida.")
    return normalized  # type: ignore[return-value]


def _hash_password(password: str) -> str:
    salt = os.urandom(16)
    iterations = 240_000
    digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations)
    return f"pbkdf2_sha256${iterations}${salt.hex()}${digest.hex()}"


def _verify_password(password: str, stored_hash: str) -> bool:
    try:
        algorithm, raw_iterations, salt_hex, digest_hex = stored_hash.split("$", 3)
    except ValueError:
        return False

    if algorithm != "pbkdf2_sha256":
        return False

    try:
        iterations = int(raw_iterations)
        salt = bytes.fromhex(salt_hex)
        expected_digest = bytes.fromhex(digest_hex)
    except ValueError:
        return False

    candidate = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, iterations)
    return secrets.compare_digest(candidate, expected_digest)


def _normalize_permission_values(values: list[str] | tuple[str, ...] | None) -> tuple[str, ...]:
    if not values:
        return tuple()

    normalized: list[str] = []
    seen: set[str] = set()
    for raw_value in values:
        value = str(raw_value or "").strip().lower()
        if value not in TENANT_PERMISSION_KEYS or value in seen:
            continue
        normalized.append(value)
        seen.add(value)
    return tuple(normalized)


def _resolve_role_permissions(role: str, permissions_json: str | None) -> tuple[str, ...]:
    if role in {"owner", "super_admin"}:
        return TENANT_PERMISSION_KEYS

    if permissions_json is None:
        return DEFAULT_STAFF_PERMISSIONS if role == "staff" else tuple()

    raw_permissions: list[str] | tuple[str, ...] | None = None
    try:
        parsed = json.loads(permissions_json)
    except json.JSONDecodeError:
        parsed = None
    if isinstance(parsed, list):
        raw_permissions = [str(item) for item in parsed]

    normalized = _normalize_permission_values(raw_permissions)
    return normalized


def _serialize_role_permissions(role: str, permissions: list[str] | tuple[str, ...] | None) -> str | None:
    if role in {"owner", "super_admin"}:
        return None
    return json.dumps(list(_normalize_permission_values(permissions)))


def _normalize_assistant_scope_values(values: list[str] | tuple[str, ...] | None) -> tuple[str, ...]:
    if not values:
        return tuple()

    normalized: list[str] = []
    seen: set[str] = set()
    for raw_value in values:
        value = str(raw_value or "").strip().lower()
        if value not in ASSISTANT_SCOPE_KEYS or value in seen:
            continue
        normalized.append(value)
        seen.add(value)
    return tuple(normalized)


def _assistant_scopes_from_permissions(permissions: list[str] | tuple[str, ...] | None) -> tuple[str, ...]:
    normalized_permissions = _normalize_permission_values(permissions)
    scopes: list[str] = []
    seen: set[str] = set()
    for permission in normalized_permissions:
        mapped_scope = None
        if permission in ASSISTANT_SCOPE_KEYS:
            mapped_scope = permission
        elif permission == "homemade_manage":
            mapped_scope = "homemade"
        elif permission == "tips_sala_manage":
            mapped_scope = "tips_sala"
        elif permission == "tips_bar_manage":
            mapped_scope = "tips_bar"
        if mapped_scope and mapped_scope not in seen:
            scopes.append(mapped_scope)
            seen.add(mapped_scope)
    return tuple(scopes)


def _resolve_user_assistant_scopes(
    role: str,
    permissions_json: str | None,
    assistant_scopes_json: str | None,
) -> tuple[str, ...]:
    if role in {"owner", "super_admin"}:
        return ASSISTANT_SCOPE_KEYS

    raw_scopes: list[str] | tuple[str, ...] | None = None
    if assistant_scopes_json is not None:
        try:
            parsed = json.loads(assistant_scopes_json)
        except json.JSONDecodeError:
            parsed = None
        if isinstance(parsed, list):
            raw_scopes = [str(item) for item in parsed]
        return _normalize_assistant_scope_values(raw_scopes)

    permissions = _resolve_role_permissions(role, permissions_json)
    return _assistant_scopes_from_permissions(permissions)


def _serialize_user_assistant_scopes(role: str, scopes: list[str] | tuple[str, ...] | None) -> str | None:
    if role in {"owner", "super_admin"}:
        return None
    return json.dumps(list(_normalize_assistant_scope_values(scopes)))


class RegisterTenantPayload(BaseModel):
    locale_name: str = Field(min_length=2)
    address: str = ""
    phone_number: str | None = None
    whatsapp_number: str | None = None
    admin_name: str = Field(min_length=2)
    username: str = Field(min_length=3)
    email: str = Field(min_length=5)
    password: str = Field(min_length=6)


class LoginPayload(BaseModel):
    identifier: str = Field(min_length=3)
    password: str = Field(min_length=1)


class BootstrapSuperAdminPayload(BaseModel):
    password: str = Field(min_length=8)


class UpdatePasswordPayload(BaseModel):
    current_password: str | None = Field(default=None, min_length=1)
    new_password: str = Field(min_length=8)


class AdminCreateTenantPayload(RegisterTenantPayload):
    pass


class AdminUpdateTenantAdminPayload(BaseModel):
    locale_name: str = Field(min_length=2)
    address: str = ""
    phone_number: str | None = None
    whatsapp_number: str | None = None
    admin_name: str = Field(min_length=2)
    username: str = Field(min_length=3)
    email: str = Field(min_length=5)
    password: str | None = Field(default=None, min_length=8)


class TenantStaffUserCreatePayload(BaseModel):
    name: str = Field(min_length=2)
    username: str = Field(min_length=3)
    email: str = Field(min_length=5)
    phone_number: str | None = None
    password: str = Field(min_length=8)
    permissions: list[str] = Field(default_factory=list)
    assistant_scopes: list[str] = Field(default_factory=list)


class TenantStaffUserUpdatePayload(BaseModel):
    name: str = Field(min_length=2)
    username: str = Field(min_length=3)
    email: str = Field(min_length=5)
    phone_number: str | None = None
    password: str | None = Field(default=None, min_length=8)
    permissions: list[str] = Field(default_factory=list)
    assistant_scopes: list[str] = Field(default_factory=list)


class TimeclockOverviewQuery(BaseModel):
    user_id: str | None = None
    start_date: str | None = None
    end_date: str | None = None
    limit: int = Field(default=200, ge=1, le=1000)


class InventoryWarehouseCreatePayload(BaseModel):
    name: str = Field(min_length=2, max_length=120)


class InventoryStockWritePayload(BaseModel):
    product_id: int = Field(ge=1)
    quantity: float = Field(ge=0)
    operation: Literal["add", "set"] = "add"
    lot_code: str | None = Field(default=None, max_length=40)
    units_per_pack: float | None = Field(default=None, gt=0)
    save_inventory_session: bool = False


class InventoryProductCodeWritePayload(BaseModel):
    product_code: str = Field(min_length=3, max_length=120)
    lot_code: str | None = Field(default=None, max_length=40)


class InventoryProductCreatePayload(BaseModel):
    product_name: str = Field(min_length=2, max_length=180)
    lot_code: str = Field(min_length=1, max_length=120)
    supplier_name: str = Field(min_length=2, max_length=180)
    product_code: str | None = Field(default=None, max_length=120)
    final_price_vat: float | None = Field(default=None, ge=0)
    vat_rate: float | None = Field(default=None, ge=0)
    weight_kg: float | None = Field(default=None, ge=0)
    unit_price_per_kg: float | None = Field(default=None, ge=0)
    category: str | None = Field(default=None, max_length=120)
    notes: str | None = Field(default=None, max_length=2000)
    units_per_pack: float | None = Field(default=None, gt=0)
    liters_per_unit: float | None = Field(default=None, ge=0)


class InventorySessionCreatePayload(BaseModel):
    inventory_date: str | None = None


class InventorySnapshotCreatePayload(BaseModel):
    snapshot_date: str | None = None


class InventoryConsumptionWritePayload(BaseModel):
    warehouse_id: str = Field(min_length=3, max_length=120)
    product_id: int = Field(ge=1)
    quantity: float = Field(gt=0)
    lot_code: str | None = Field(default=None, max_length=40)
    units_per_pack: float | None = Field(default=None, gt=0)
    occurred_at: str | None = Field(default=None, max_length=40)


class InventoryTransferWritePayload(BaseModel):
    source_warehouse_id: str = Field(min_length=3, max_length=120)
    destination_warehouse_id: str = Field(min_length=3, max_length=120)
    stock_item_id: str = Field(min_length=3, max_length=120)
    quantity: float = Field(gt=0)
    lot_code: str | None = Field(default=None, max_length=40)
    source_lot_code: str | None = Field(default=None, max_length=40)
    units_per_pack: float | None = Field(default=None, gt=0)
    occurred_at: str | None = Field(default=None, max_length=40)


class HomemadeRecipeIngredientPayload(BaseModel):
    ingredient_name: str | None = Field(default=None, max_length=160)
    part_amount: float = Field(gt=0)
    measurement_unit: Literal["ml", "g", "g_per_liter", "drops_per_liter", "part"] = "ml"
    linked_recipe_id: str | None = Field(default=None, max_length=120)


class HomemadeRecipeWritePayload(BaseModel):
    name: str = Field(min_length=2, max_length=120)
    measurement_unit: Literal["ml", "g"] = "ml"
    usage_scope: Literal["bar", "restaurant", "both"] = "both"
    notes: str | None = Field(default=None, max_length=4000)
    yield_ingredient_name: str | None = Field(default=None, max_length=160)
    yield_input_amount: float | None = Field(default=None, gt=0)
    yield_input_unit: Literal["g", "ml"] | None = None
    yield_output_ml: float | None = Field(default=None, gt=0)
    ingredients: list[HomemadeRecipeIngredientPayload] = Field(default_factory=list, min_length=1, max_length=60)


class HomemadePreparationWritePayload(BaseModel):
    prepared_on: str = Field(min_length=10, max_length=10)


class HomemadeStockWritePayload(BaseModel):
    recipe_id: str = Field(min_length=3, max_length=120)
    quantity: float = Field(ge=0)
    operation: Literal["add", "set"] = "set"


class HomemadeOperationalCalendarRulePayload(BaseModel):
    start_date: str | None = Field(default=None, max_length=10)
    end_date: str | None = Field(default=None, max_length=10)
    weekdays: list[int] = Field(default_factory=list, max_length=7)


class HomemadeOperationalCalendarPayload(BaseModel):
    rules: list[HomemadeOperationalCalendarRulePayload] = Field(default_factory=list, max_length=24)


class HomemadeStockSettingsPayload(BaseModel):
    minimum_stock_days: float = Field(ge=0, le=365)
    bar_calendar: HomemadeOperationalCalendarPayload | None = None
    restaurant_calendar: HomemadeOperationalCalendarPayload | None = None


class TipsRosterEntryPayload(BaseModel):
    name: str = Field(min_length=2, max_length=120)
    score: float = Field(ge=0)


class TipsRosterWritePayload(BaseModel):
    entries: list[TipsRosterEntryPayload] = Field(default_factory=list, max_length=200)


class TipsRunPreviewPayload(BaseModel):
    tip_date: str | None = Field(default=None, min_length=10, max_length=10)
    total_amount: float = Field(ge=0)
    pos_amount: float = Field(default=0, ge=0)
    pos_effective_amount: float | None = Field(default=None, ge=0)
    absent_names: list[str] = Field(default_factory=list, max_length=200)
    history_run_ids: list[str] = Field(default_factory=list, max_length=120)
    mark_as_delivered: bool = False


class TipsRunPayoutStatusPayload(BaseModel):
    delivered: bool = True


class ReportCreatePayload(BaseModel):
    title: str = Field(min_length=3, max_length=160)
    description: str = Field(min_length=5, max_length=4000)
    category: Literal["problem", "damage", "breakage", "malfunction", "purchase_request", "other"] = "problem"
    location: str | None = Field(default=None, max_length=160)
    priority: Literal["low", "normal", "high", "urgent"] = "normal"


class ReportStatusUpdatePayload(BaseModel):
    status: Literal["new", "reviewed", "in_progress", "reported_to_owner", "resolved"]
    admin_note: str | None = Field(default=None, max_length=4000)


class PushSubscriptionKeysPayload(BaseModel):
    p256dh: str = Field(min_length=20, max_length=512)
    auth: str = Field(min_length=8, max_length=256)


class PushSubscriptionPayload(BaseModel):
    endpoint: str = Field(min_length=20, max_length=2048)
    subscription_keys: PushSubscriptionKeysPayload = Field(alias="keys")
    user_agent: str | None = Field(default=None, max_length=512)

    model_config = ConfigDict(populate_by_name=True)


class PushSubscriptionDeletePayload(BaseModel):
    endpoint: str = Field(min_length=20, max_length=2048)


@dataclass(frozen=True)
class SessionIdentity:
    token: str
    tenant_id: str
    tenant_slug: str
    tenant_name: str
    home_tenant_id: str
    home_tenant_slug: str
    home_tenant_name: str
    user_id: str
    user_email: str
    username: str | None
    user_name: str | None
    role: str
    permissions: tuple[str, ...]
    assistant_scopes: tuple[str, ...]
    database_path: str
    expires_at: str


@dataclass(frozen=True)
class MenuAssetRecord:
    id: str
    tenant_id: str
    original_name: str
    display_name: str
    mime_type: str
    kind: str
    file_size_bytes: int
    storage_path: str
    extracted_text: str
    analysis_text: str
    status: str
    error_detail: str | None
    created_at: str
    updated_at: str


@dataclass(frozen=True)
class FiscalDocumentRecord:
    id: str
    tenant_id: str
    original_name: str
    display_name: str
    mime_type: str
    kind: str
    file_size_bytes: int
    file_hash: str | None
    storage_path: str
    document_type: str
    document_number: str | None
    document_date: str | None
    supplier_name: str | None
    total_amount: float | None
    currency: str
    summary_text: str
    extracted_text: str
    preview_text: str
    drive_file_id: str | None
    drive_web_url: str | None
    drive_uploaded_at: str | None
    status: str
    matching_status: str
    review_status: str
    error_detail: str | None
    created_at: str
    updated_at: str


@dataclass(frozen=True)
class FiscalDocumentLineItemRecord:
    id: str
    tenant_id: str
    document_id: str
    line_index: int
    product_code: str | None
    iso_code: str | None
    description: str
    category_code: str | None
    unit_code: str | None
    pack_count: float | None
    quantity: float | None
    gross_quantity: float | None
    tare_quantity: float | None
    net_quantity: float | None
    unit_price: float | None
    line_total: float | None
    vat_code: str | None
    raw_row_text: str
    created_at: str
    updated_at: str


@dataclass(frozen=True)
class FiscalDocumentSettingsRecord:
    tenant_id: str
    inbound_email: str | None
    updated_at: str | None


@dataclass(frozen=True)
class FiscalDocumentInboxItemRecord:
    id: str
    tenant_id: str
    message_id: str
    attachment_id: str
    file_hash: str | None
    subject: str
    sender: str | None
    received_at: str | None
    attachment_name: str
    mime_type: str
    sync_status: str
    document_id: str | None
    error_detail: str | None
    created_at: str
    updated_at: str


@dataclass(frozen=True)
class TimeclockEntryRecord:
    id: str
    user_id: str
    user_name: str | None
    username: str | None
    user_email: str | None
    started_at: str
    ended_at: str | None
    duration_seconds: int | None
    started_source: str
    ended_source: str | None
    notes: str | None
    created_at: str
    updated_at: str


@dataclass(frozen=True)
class TenantReportRecord:
    id: str
    reporter_user_id: str
    reporter_name: str | None
    reporter_username: str | None
    reporter_email: str | None
    title: str
    description: str
    category: str
    location: str | None
    priority: str
    status: str
    admin_note: str | None
    status_updated_by_user_id: str | None
    status_updated_by_name: str | None
    resolved_at: str | None
    created_at: str
    updated_at: str


@dataclass(frozen=True)
class AssistantThreadRecord:
    id: str
    tenant_id: str
    user_id: str
    surface: str
    status: str
    state_json: str | None
    created_at: str
    updated_at: str


@dataclass(frozen=True)
class AssistantStoredMessage:
    id: str
    thread_id: str
    role: str
    content: str
    created_at: str
    sort_index: int


@dataclass(frozen=True)
class AssistantRunRecord:
    id: str
    thread_id: str
    tenant_id: str
    user_id: str
    surface: str
    route: str
    model: str
    user_message: str
    assistant_reply: str
    trace_json: str
    created_at: str


class TenantStore:
    def __init__(self) -> None:
        settings = get_settings()
        self._registry_path = Path(settings.tenancy_registry_database)
        self._tenant_dir = Path(settings.tenancy_databases_dir)
        self._menu_assets_dir = Path(settings.menu_asset_storage_dir)
        self._fiscal_documents_dir = Path(settings.fiscal_document_storage_dir)
        self._session_duration = timedelta(hours=settings.tenancy_session_duration_hours)
        # Store operations sometimes compose write and read helpers in the same
        # request path. Use a reentrant lock to avoid self-deadlocks while still
        # serializing writes against the tenant SQLite database.
        self._lock = threading.RLock()
        self._ensure_storage()

    def _ensure_storage(self) -> None:
        self._registry_path.parent.mkdir(parents=True, exist_ok=True)
        self._tenant_dir.mkdir(parents=True, exist_ok=True)
        self._menu_assets_dir.mkdir(parents=True, exist_ok=True)
        self._fiscal_documents_dir.mkdir(parents=True, exist_ok=True)

        with self._connect_registry() as connection:
            connection.executescript(
                """
                CREATE TABLE IF NOT EXISTS tenants (
                    id TEXT PRIMARY KEY,
                    name TEXT NOT NULL,
                    slug TEXT NOT NULL UNIQUE,
                    database_path TEXT NOT NULL,
                    created_at TEXT NOT NULL
                );

                CREATE TABLE IF NOT EXISTS venues (
                    id TEXT PRIMARY KEY,
                    tenant_id TEXT NOT NULL,
                    name TEXT NOT NULL,
                    address TEXT NOT NULL DEFAULT '',
                    phone_number TEXT,
                    whatsapp_number TEXT,
                    created_at TEXT NOT NULL,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id)
                );

                CREATE TABLE IF NOT EXISTS users (
                    id TEXT PRIMARY KEY,
                    tenant_id TEXT NOT NULL,
                    name TEXT NOT NULL,
                    username TEXT NOT NULL UNIQUE,
                    email TEXT NOT NULL UNIQUE,
                    phone_number TEXT,
                    password_hash TEXT NOT NULL,
                    role TEXT NOT NULL,
                    permissions_json TEXT,
                    assistant_scopes_json TEXT,
                    created_at TEXT NOT NULL,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id)
                );

                CREATE TABLE IF NOT EXISTS tenant_modules (
                    id TEXT PRIMARY KEY,
                    tenant_id TEXT NOT NULL,
                    module_key TEXT NOT NULL,
                    enabled INTEGER NOT NULL,
                    plan_name TEXT NOT NULL,
                    activated_at TEXT,
                    expires_at TEXT,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id)
                );

                CREATE UNIQUE INDEX IF NOT EXISTS idx_tenant_modules_unique
                    ON tenant_modules(tenant_id, module_key);

                CREATE TABLE IF NOT EXISTS sessions (
                    token TEXT PRIMARY KEY,
                    tenant_id TEXT NOT NULL,
                    user_id TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    expires_at TEXT NOT NULL,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id),
                    FOREIGN KEY(user_id) REFERENCES users(id)
                );

                CREATE TABLE IF NOT EXISTS tenant_admin_push_recipients (
                    tenant_id TEXT NOT NULL,
                    user_id TEXT NOT NULL,
                    enabled INTEGER NOT NULL DEFAULT 1,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    PRIMARY KEY (tenant_id, user_id),
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id),
                    FOREIGN KEY(user_id) REFERENCES users(id)
                );

                CREATE TABLE IF NOT EXISTS tenant_llm_settings (
                    tenant_id TEXT NOT NULL,
                    scope TEXT NOT NULL,
                    config_json TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    PRIMARY KEY (tenant_id, scope),
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id)
                );

                CREATE TABLE IF NOT EXISTS tenant_menu_assets (
                    id TEXT PRIMARY KEY,
                    tenant_id TEXT NOT NULL,
                    original_name TEXT NOT NULL,
                    display_name TEXT NOT NULL,
                    mime_type TEXT NOT NULL,
                    kind TEXT NOT NULL,
                    file_size_bytes INTEGER NOT NULL,
                    storage_path TEXT NOT NULL,
                    extracted_text TEXT NOT NULL DEFAULT '',
                    analysis_text TEXT NOT NULL DEFAULT '',
                    status TEXT NOT NULL,
                    error_detail TEXT,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id)
                );

                CREATE INDEX IF NOT EXISTS idx_tenant_menu_assets_tenant
                    ON tenant_menu_assets(tenant_id, created_at DESC);

                CREATE TABLE IF NOT EXISTS tenant_fiscal_documents (
                    id TEXT PRIMARY KEY,
                    tenant_id TEXT NOT NULL,
                    original_name TEXT NOT NULL,
                    display_name TEXT NOT NULL,
                    mime_type TEXT NOT NULL,
                    kind TEXT NOT NULL,
                    file_size_bytes INTEGER NOT NULL,
                    file_hash TEXT,
                    storage_path TEXT NOT NULL,
                    document_type TEXT NOT NULL,
                    document_number TEXT,
                    document_date TEXT,
                    supplier_name TEXT,
                    total_amount REAL,
                    currency TEXT NOT NULL DEFAULT 'EUR',
                    summary_text TEXT NOT NULL DEFAULT '',
                    extracted_text TEXT NOT NULL DEFAULT '',
                    preview_text TEXT NOT NULL DEFAULT '',
                    drive_file_id TEXT,
                    drive_web_url TEXT,
                    drive_uploaded_at TEXT,
                    status TEXT NOT NULL,
                    matching_status TEXT NOT NULL DEFAULT 'pending',
                    review_status TEXT NOT NULL DEFAULT 'to_review',
                    error_detail TEXT,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id)
                );

                CREATE INDEX IF NOT EXISTS idx_tenant_fiscal_documents_tenant
                    ON tenant_fiscal_documents(tenant_id, created_at DESC);

                CREATE TABLE IF NOT EXISTS tenant_fiscal_document_items (
                    id TEXT PRIMARY KEY,
                    tenant_id TEXT NOT NULL,
                    document_id TEXT NOT NULL,
                    line_index INTEGER NOT NULL,
                    product_code TEXT,
                    iso_code TEXT,
                    description TEXT NOT NULL,
                    category_code TEXT,
                    unit_code TEXT,
                    pack_count REAL,
                    quantity REAL,
                    gross_quantity REAL,
                    tare_quantity REAL,
                    net_quantity REAL,
                    unit_price REAL,
                    line_total REAL,
                    vat_code TEXT,
                    raw_row_text TEXT NOT NULL DEFAULT '',
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id),
                    FOREIGN KEY(document_id) REFERENCES tenant_fiscal_documents(id)
                );

                CREATE INDEX IF NOT EXISTS idx_tenant_fiscal_document_items_document
                    ON tenant_fiscal_document_items(tenant_id, document_id, line_index ASC);

                CREATE TABLE IF NOT EXISTS tenant_fiscal_document_settings (
                    tenant_id TEXT PRIMARY KEY,
                    inbound_email TEXT,
                    updated_at TEXT NOT NULL,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id)
                );

                CREATE TABLE IF NOT EXISTS tenant_fiscal_document_inbox_items (
                    id TEXT PRIMARY KEY,
                    tenant_id TEXT NOT NULL,
                    message_id TEXT NOT NULL,
                    attachment_id TEXT NOT NULL,
                    file_hash TEXT,
                    subject TEXT NOT NULL DEFAULT '',
                    sender TEXT,
                    received_at TEXT,
                    attachment_name TEXT NOT NULL,
                    mime_type TEXT NOT NULL,
                    sync_status TEXT NOT NULL,
                    document_id TEXT,
                    error_detail TEXT,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id),
                    FOREIGN KEY(document_id) REFERENCES tenant_fiscal_documents(id),
                    UNIQUE(tenant_id, message_id, attachment_id)
                );

                CREATE INDEX IF NOT EXISTS idx_tenant_fiscal_document_inbox_items_tenant
                    ON tenant_fiscal_document_inbox_items(tenant_id, created_at DESC);

                CREATE TABLE IF NOT EXISTS assistant_threads (
                    id TEXT PRIMARY KEY,
                    tenant_id TEXT NOT NULL,
                    user_id TEXT NOT NULL,
                    surface TEXT NOT NULL,
                    status TEXT NOT NULL DEFAULT 'active',
                    state_json TEXT,
                    created_at TEXT NOT NULL,
                    updated_at TEXT NOT NULL,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id),
                    FOREIGN KEY(user_id) REFERENCES users(id)
                );

                CREATE INDEX IF NOT EXISTS idx_assistant_threads_lookup
                    ON assistant_threads(tenant_id, user_id, surface, status, updated_at DESC);

                CREATE TABLE IF NOT EXISTS assistant_messages (
                    id TEXT PRIMARY KEY,
                    thread_id TEXT NOT NULL,
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    sort_index INTEGER NOT NULL,
                    FOREIGN KEY(thread_id) REFERENCES assistant_threads(id) ON DELETE CASCADE
                );

                CREATE INDEX IF NOT EXISTS idx_assistant_messages_thread
                    ON assistant_messages(thread_id, sort_index ASC);

                CREATE TABLE IF NOT EXISTS assistant_runs (
                    id TEXT PRIMARY KEY,
                    thread_id TEXT NOT NULL,
                    tenant_id TEXT NOT NULL,
                    user_id TEXT NOT NULL,
                    surface TEXT NOT NULL,
                    route TEXT NOT NULL,
                    model TEXT NOT NULL,
                    user_message TEXT NOT NULL,
                    assistant_reply TEXT NOT NULL,
                    trace_json TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    FOREIGN KEY(thread_id) REFERENCES assistant_threads(id) ON DELETE CASCADE,
                    FOREIGN KEY(tenant_id) REFERENCES tenants(id),
                    FOREIGN KEY(user_id) REFERENCES users(id)
                );

                CREATE INDEX IF NOT EXISTS idx_assistant_runs_thread
                    ON assistant_runs(thread_id, created_at DESC);
                """
            )
            self._ensure_registry_column(connection, "users", "permissions_json", "TEXT")
            self._ensure_registry_column(connection, "users", "assistant_scopes_json", "TEXT")
            self._ensure_registry_column(connection, "tenant_fiscal_documents", "file_hash", "TEXT")
            self._ensure_registry_column(connection, "tenant_fiscal_documents", "preview_text", "TEXT NOT NULL DEFAULT ''")
            self._ensure_registry_column(connection, "tenant_fiscal_documents", "drive_file_id", "TEXT")
            self._ensure_registry_column(connection, "tenant_fiscal_documents", "drive_web_url", "TEXT")
            self._ensure_registry_column(connection, "tenant_fiscal_documents", "drive_uploaded_at", "TEXT")
            self._ensure_registry_column(connection, "tenant_fiscal_document_inbox_items", "file_hash", "TEXT")
            self._ensure_registry_column(connection, "assistant_threads", "state_json", "TEXT")
            connection.execute(
                """
                CREATE INDEX IF NOT EXISTS idx_tenant_fiscal_documents_hash
                    ON tenant_fiscal_documents(tenant_id, file_hash)
                """
            )
            connection.execute(
                """
                CREATE INDEX IF NOT EXISTS idx_tenant_fiscal_document_inbox_items_hash
                    ON tenant_fiscal_document_inbox_items(tenant_id, file_hash)
                """
            )
            self._ensure_super_admin_account(connection)

    def _ensure_tenant_database_schema(self, connection: sqlite3.Connection) -> None:
        connection.executescript(
            """
            CREATE TABLE IF NOT EXISTS tenant_profile (
                tenant_id TEXT PRIMARY KEY,
                tenant_name TEXT NOT NULL,
                tenant_slug TEXT NOT NULL UNIQUE,
                venue_name TEXT NOT NULL,
                address TEXT NOT NULL DEFAULT '',
                phone_number TEXT,
                whatsapp_number TEXT,
                admin_name TEXT NOT NULL,
                admin_username TEXT NOT NULL,
                admin_email TEXT NOT NULL,
                admin_phone_number TEXT,
                password_hash TEXT NOT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS tenant_module_settings (
                module_key TEXT PRIMARY KEY,
                enabled INTEGER NOT NULL,
                plan_name TEXT NOT NULL,
                activated_at TEXT,
                expires_at TEXT
            );

            CREATE TABLE IF NOT EXISTS tenant_timeclock_entries (
                id TEXT PRIMARY KEY,
                user_id TEXT NOT NULL,
                user_name TEXT,
                username TEXT,
                user_email TEXT,
                started_at TEXT NOT NULL,
                ended_at TEXT,
                duration_seconds INTEGER,
                started_source TEXT NOT NULL DEFAULT 'portal',
                ended_source TEXT,
                notes TEXT,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_timeclock_user_started
                ON tenant_timeclock_entries(user_id, started_at DESC);

            CREATE INDEX IF NOT EXISTS idx_tenant_timeclock_active
                ON tenant_timeclock_entries(ended_at, started_at DESC);

            CREATE TABLE IF NOT EXISTS tenant_reports (
                id TEXT PRIMARY KEY,
                reporter_user_id TEXT NOT NULL,
                reporter_name TEXT,
                reporter_username TEXT,
                reporter_email TEXT,
                title TEXT NOT NULL,
                description TEXT NOT NULL,
                category TEXT NOT NULL,
                location TEXT,
                priority TEXT NOT NULL DEFAULT 'normal',
                status TEXT NOT NULL DEFAULT 'new',
                admin_note TEXT,
                status_updated_by_user_id TEXT,
                status_updated_by_name TEXT,
                resolved_at TEXT,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_reports_created
                ON tenant_reports(created_at DESC);

            CREATE INDEX IF NOT EXISTS idx_tenant_reports_status
                ON tenant_reports(status, updated_at DESC);

            CREATE INDEX IF NOT EXISTS idx_tenant_reports_reporter
                ON tenant_reports(reporter_user_id, created_at DESC);

            CREATE TABLE IF NOT EXISTS tenant_push_subscriptions (
                id TEXT PRIMARY KEY,
                user_id TEXT NOT NULL,
                endpoint TEXT NOT NULL UNIQUE,
                p256dh TEXT NOT NULL,
                auth TEXT NOT NULL,
                user_agent TEXT,
                enabled INTEGER NOT NULL DEFAULT 1,
                last_error TEXT,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_push_subscriptions_user
                ON tenant_push_subscriptions(user_id, enabled, updated_at DESC);

            CREATE TABLE IF NOT EXISTS tenant_inventory_warehouses (
                id TEXT PRIMARY KEY,
                name TEXT NOT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS tenant_inventory_stock_items (
                id TEXT PRIMARY KEY,
                warehouse_id TEXT NOT NULL,
                product_id INTEGER,
                product_name TEXT NOT NULL,
                product_lookup TEXT NOT NULL,
                supplier_name TEXT NOT NULL,
                supplier_lookup TEXT NOT NULL,
                total_equivalent_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (warehouse_id) REFERENCES tenant_inventory_warehouses(id) ON DELETE CASCADE,
                UNIQUE (warehouse_id, product_lookup, supplier_lookup)
            );

            CREATE TABLE IF NOT EXISTS tenant_inventory_stock_lots (
                id TEXT PRIMARY KEY,
                item_id TEXT NOT NULL,
                lot_code TEXT NOT NULL,
                lot_lookup TEXT NOT NULL,
                quantity REAL NOT NULL DEFAULT 0,
                units_per_pack REAL,
                equivalent_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (item_id) REFERENCES tenant_inventory_stock_items(id) ON DELETE CASCADE,
                UNIQUE (item_id, lot_lookup)
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_warehouse_name
                ON tenant_inventory_warehouses(name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_items_warehouse
                ON tenant_inventory_stock_items(warehouse_id, product_name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_lots_item
                ON tenant_inventory_stock_lots(item_id, lot_code ASC);

            CREATE TABLE IF NOT EXISTS tenant_inventory_sessions (
                id TEXT PRIMARY KEY,
                warehouse_id TEXT NOT NULL,
                warehouse_name TEXT NOT NULL,
                inventory_date TEXT NOT NULL,
                label TEXT,
                created_by_user_id TEXT,
                created_by_name TEXT,
                total_products INTEGER NOT NULL DEFAULT 0,
                total_equivalent_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (warehouse_id) REFERENCES tenant_inventory_warehouses(id) ON DELETE CASCADE,
                UNIQUE (warehouse_id, inventory_date)
            );

            CREATE TABLE IF NOT EXISTS tenant_inventory_session_items (
                id TEXT PRIMARY KEY,
                session_id TEXT NOT NULL,
                product_id INTEGER,
                product_name TEXT NOT NULL,
                product_lookup TEXT NOT NULL,
                supplier_name TEXT NOT NULL,
                supplier_lookup TEXT NOT NULL,
                total_equivalent_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (session_id) REFERENCES tenant_inventory_sessions(id) ON DELETE CASCADE,
                UNIQUE (session_id, product_lookup, supplier_lookup)
            );

            CREATE TABLE IF NOT EXISTS tenant_inventory_session_lots (
                id TEXT PRIMARY KEY,
                session_item_id TEXT NOT NULL,
                lot_code TEXT NOT NULL,
                lot_lookup TEXT NOT NULL,
                quantity REAL NOT NULL DEFAULT 0,
                units_per_pack REAL,
                equivalent_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (session_item_id) REFERENCES tenant_inventory_session_items(id) ON DELETE CASCADE,
                UNIQUE (session_item_id, lot_lookup)
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_session_warehouse_date
                ON tenant_inventory_sessions(warehouse_id, inventory_date DESC);

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_session_items_session
                ON tenant_inventory_session_items(session_id, product_name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_session_lots_item
                ON tenant_inventory_session_lots(session_item_id, lot_code ASC);

            CREATE TABLE IF NOT EXISTS tenant_inventory_movements (
                id TEXT PRIMARY KEY,
                warehouse_id TEXT NOT NULL,
                product_id INTEGER,
                product_name TEXT NOT NULL,
                product_lookup TEXT NOT NULL,
                supplier_name TEXT NOT NULL,
                supplier_lookup TEXT NOT NULL,
                lot_code TEXT NOT NULL,
                lot_lookup TEXT NOT NULL,
                quantity REAL NOT NULL DEFAULT 0,
                units_per_pack REAL,
                equivalent_units REAL NOT NULL DEFAULT 0,
                movement_kind TEXT NOT NULL DEFAULT 'in',
                source_type TEXT NOT NULL DEFAULT 'manual_stock_add',
                source_label TEXT,
                occurred_at TEXT NOT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (warehouse_id) REFERENCES tenant_inventory_warehouses(id) ON DELETE CASCADE
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_movements_warehouse_occurred
                ON tenant_inventory_movements(warehouse_id, occurred_at DESC);

            CREATE TABLE IF NOT EXISTS tenant_inventory_consumption_product_stats (
                id TEXT PRIMARY KEY,
                product_id INTEGER,
                product_lookup TEXT NOT NULL,
                supplier_lookup TEXT NOT NULL,
                product_name TEXT NOT NULL,
                supplier_name TEXT NOT NULL,
                total_consumed_units REAL NOT NULL DEFAULT 0,
                workdays_count INTEGER NOT NULL DEFAULT 0,
                consumed_days_count INTEGER NOT NULL DEFAULT 0,
                average_daily_consumed_units REAL NOT NULL DEFAULT 0,
                average_consumed_units_on_consumption_days REAL NOT NULL DEFAULT 0,
                movement_count INTEGER NOT NULL DEFAULT 0,
                first_consumption_date TEXT,
                last_consumption_date TEXT,
                calculation_source TEXT NOT NULL DEFAULT 'manual_consumption_movements',
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                UNIQUE (product_lookup, supplier_lookup)
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_consumption_product_stats_avg
                ON tenant_inventory_consumption_product_stats(average_daily_consumed_units DESC, product_name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_consumption_product_stats_last
                ON tenant_inventory_consumption_product_stats(last_consumption_date DESC, product_name ASC);

            CREATE TABLE IF NOT EXISTS tenant_inventory_daily_consumptions (
                id TEXT PRIMARY KEY,
                warehouse_id TEXT NOT NULL,
                warehouse_name TEXT NOT NULL,
                consumption_date TEXT NOT NULL,
                period_start_date TEXT NOT NULL,
                period_end_date TEXT NOT NULL,
                period_days REAL NOT NULL DEFAULT 0,
                start_session_id TEXT,
                end_session_id TEXT NOT NULL,
                product_lookup TEXT NOT NULL,
                supplier_lookup TEXT NOT NULL,
                product_name TEXT NOT NULL,
                supplier_name TEXT NOT NULL,
                opening_units REAL NOT NULL DEFAULT 0,
                incoming_units REAL NOT NULL DEFAULT 0,
                outgoing_transfer_units REAL NOT NULL DEFAULT 0,
                closing_units REAL NOT NULL DEFAULT 0,
                consumed_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (warehouse_id) REFERENCES tenant_inventory_warehouses(id) ON DELETE CASCADE,
                FOREIGN KEY (start_session_id) REFERENCES tenant_inventory_sessions(id) ON DELETE SET NULL,
                FOREIGN KEY (end_session_id) REFERENCES tenant_inventory_sessions(id) ON DELETE CASCADE,
                UNIQUE (warehouse_id, consumption_date, product_lookup, supplier_lookup)
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_daily_consumptions_date
                ON tenant_inventory_daily_consumptions(consumption_date DESC, warehouse_name ASC, product_name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_daily_consumptions_warehouse_date
                ON tenant_inventory_daily_consumptions(warehouse_id, consumption_date DESC);

            CREATE TABLE IF NOT EXISTS tenant_inventory_estimated_consumptions (
                id TEXT PRIMARY KEY,
                consumption_date TEXT NOT NULL,
                period_start_date TEXT NOT NULL,
                period_end_date TEXT NOT NULL,
                period_days REAL NOT NULL DEFAULT 0,
                product_id INTEGER,
                product_lookup TEXT NOT NULL,
                supplier_lookup TEXT NOT NULL,
                product_name TEXT NOT NULL,
                supplier_name TEXT NOT NULL,
                warehouse_count INTEGER NOT NULL DEFAULT 0,
                opening_units REAL NOT NULL DEFAULT 0,
                incoming_units REAL NOT NULL DEFAULT 0,
                closing_units REAL NOT NULL DEFAULT 0,
                consumed_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                UNIQUE (consumption_date, product_lookup, supplier_lookup)
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_estimated_consumptions_date
                ON tenant_inventory_estimated_consumptions(consumption_date DESC, product_name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_estimated_consumptions_product
                ON tenant_inventory_estimated_consumptions(product_lookup, supplier_lookup, consumption_date DESC);

            CREATE TABLE IF NOT EXISTS tenant_inventory_snapshots (
                id TEXT PRIMARY KEY,
                snapshot_date TEXT NOT NULL UNIQUE,
                label TEXT,
                created_by_user_id TEXT,
                created_by_name TEXT,
                total_warehouses INTEGER NOT NULL DEFAULT 0,
                total_products INTEGER NOT NULL DEFAULT 0,
                total_equivalent_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS tenant_inventory_snapshot_items (
                id TEXT PRIMARY KEY,
                snapshot_id TEXT NOT NULL,
                warehouse_id TEXT,
                warehouse_name TEXT NOT NULL,
                product_lookup TEXT NOT NULL,
                supplier_lookup TEXT NOT NULL,
                product_name TEXT NOT NULL,
                supplier_name TEXT NOT NULL,
                total_equivalent_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (snapshot_id) REFERENCES tenant_inventory_snapshots(id) ON DELETE CASCADE,
                UNIQUE (snapshot_id, warehouse_id, product_lookup, supplier_lookup)
            );

            CREATE TABLE IF NOT EXISTS tenant_inventory_snapshot_lots (
                id TEXT PRIMARY KEY,
                snapshot_item_id TEXT NOT NULL,
                lot_code TEXT NOT NULL,
                lot_lookup TEXT NOT NULL,
                quantity REAL NOT NULL DEFAULT 0,
                units_per_pack REAL,
                equivalent_units REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (snapshot_item_id) REFERENCES tenant_inventory_snapshot_items(id) ON DELETE CASCADE,
                UNIQUE (snapshot_item_id, lot_lookup)
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_snapshot_date
                ON tenant_inventory_snapshots(snapshot_date DESC);

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_snapshot_items_snapshot
                ON tenant_inventory_snapshot_items(snapshot_id, warehouse_name ASC, product_name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_inventory_snapshot_lots_item
                ON tenant_inventory_snapshot_lots(snapshot_item_id, lot_code ASC);

            CREATE TABLE IF NOT EXISTS tenant_homemade_recipes (
                id TEXT PRIMARY KEY,
                name TEXT NOT NULL,
                name_lookup TEXT NOT NULL UNIQUE,
                measurement_unit TEXT NOT NULL DEFAULT 'ml',
                usage_scope TEXT NOT NULL DEFAULT 'both',
                notes TEXT,
                yield_ingredient_name TEXT,
                yield_ingredient_lookup TEXT,
                yield_input_amount REAL,
                yield_input_unit TEXT,
                yield_output_ml REAL,
                total_parts REAL NOT NULL DEFAULT 0,
                ingredient_count INTEGER NOT NULL DEFAULT 0,
                preparation_date TEXT,
                preparation_drive_file_id TEXT,
                preparation_drive_web_url TEXT,
                preparation_drive_updated_at TEXT,
                created_by_user_id TEXT,
                created_by_name TEXT,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS tenant_homemade_recipe_ingredients (
                id TEXT PRIMARY KEY,
                recipe_id TEXT NOT NULL,
                ingredient_name TEXT NOT NULL,
                ingredient_lookup TEXT NOT NULL,
                linked_recipe_id TEXT,
                part_amount REAL NOT NULL DEFAULT 0,
                measurement_unit TEXT NOT NULL DEFAULT 'ml',
                share_ratio REAL NOT NULL DEFAULT 0,
                percentage REAL NOT NULL DEFAULT 0,
                sort_order INTEGER NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (recipe_id) REFERENCES tenant_homemade_recipes(id) ON DELETE CASCADE
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_homemade_recipe_name
                ON tenant_homemade_recipes(name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_homemade_ingredient_recipe
                ON tenant_homemade_recipe_ingredients(recipe_id, sort_order ASC, ingredient_name ASC);

            CREATE TABLE IF NOT EXISTS tenant_homemade_stock_warehouses (
                id TEXT PRIMARY KEY,
                name TEXT NOT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS tenant_homemade_stock_items (
                id TEXT PRIMARY KEY,
                warehouse_id TEXT NOT NULL,
                recipe_id TEXT NOT NULL,
                recipe_name TEXT NOT NULL,
                recipe_lookup TEXT NOT NULL,
                measurement_unit TEXT NOT NULL DEFAULT 'pz',
                quantity REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (warehouse_id) REFERENCES tenant_homemade_stock_warehouses(id) ON DELETE CASCADE,
                FOREIGN KEY (recipe_id) REFERENCES tenant_homemade_recipes(id) ON DELETE CASCADE,
                UNIQUE (warehouse_id, recipe_lookup)
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_homemade_stock_warehouse_name
                ON tenant_homemade_stock_warehouses(name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_homemade_stock_items_warehouse
                ON tenant_homemade_stock_items(warehouse_id, recipe_name ASC);

            CREATE TABLE IF NOT EXISTS tenant_homemade_stock_sessions (
                id TEXT PRIMARY KEY,
                warehouse_id TEXT NOT NULL,
                warehouse_name TEXT NOT NULL,
                inventory_date TEXT NOT NULL,
                label TEXT,
                created_by_user_id TEXT,
                created_by_name TEXT,
                total_recipes INTEGER NOT NULL DEFAULT 0,
                total_quantity REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (warehouse_id) REFERENCES tenant_homemade_stock_warehouses(id) ON DELETE CASCADE,
                UNIQUE (warehouse_id, inventory_date)
            );

            CREATE TABLE IF NOT EXISTS tenant_homemade_stock_session_items (
                id TEXT PRIMARY KEY,
                session_id TEXT NOT NULL,
                recipe_id TEXT NOT NULL,
                recipe_name TEXT NOT NULL,
                recipe_lookup TEXT NOT NULL,
                measurement_unit TEXT NOT NULL DEFAULT 'pz',
                quantity REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (session_id) REFERENCES tenant_homemade_stock_sessions(id) ON DELETE CASCADE,
                FOREIGN KEY (recipe_id) REFERENCES tenant_homemade_recipes(id) ON DELETE CASCADE,
                UNIQUE (session_id, recipe_lookup)
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_homemade_stock_session_warehouse_date
                ON tenant_homemade_stock_sessions(warehouse_id, inventory_date DESC);

            CREATE INDEX IF NOT EXISTS idx_tenant_homemade_stock_session_items_session
                ON tenant_homemade_stock_session_items(session_id, recipe_name ASC);

            CREATE TABLE IF NOT EXISTS tenant_homemade_stock_settings (
                id TEXT PRIMARY KEY,
                minimum_stock_days REAL NOT NULL DEFAULT 0,
                bar_calendar_json TEXT,
                restaurant_calendar_json TEXT,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS tenant_homemade_stock_movements (
                id TEXT PRIMARY KEY,
                warehouse_id TEXT NOT NULL,
                warehouse_name TEXT NOT NULL,
                recipe_id TEXT NOT NULL,
                recipe_name TEXT NOT NULL,
                recipe_lookup TEXT NOT NULL,
                measurement_unit TEXT NOT NULL DEFAULT 'pz',
                quantity_before REAL NOT NULL DEFAULT 0,
                quantity_after REAL NOT NULL DEFAULT 0,
                delta_quantity REAL NOT NULL DEFAULT 0,
                added_quantity REAL NOT NULL DEFAULT 0,
                consumed_quantity REAL NOT NULL DEFAULT 0,
                movement_type TEXT NOT NULL DEFAULT 'stock_set',
                occurred_at TEXT NOT NULL,
                created_by_user_id TEXT,
                created_by_name TEXT,
                created_at TEXT NOT NULL,
                FOREIGN KEY (warehouse_id) REFERENCES tenant_homemade_stock_warehouses(id) ON DELETE CASCADE,
                FOREIGN KEY (recipe_id) REFERENCES tenant_homemade_recipes(id) ON DELETE CASCADE
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_homemade_stock_movements_recipe_date
                ON tenant_homemade_stock_movements(recipe_lookup, occurred_at DESC);

            CREATE INDEX IF NOT EXISTS idx_tenant_homemade_stock_movements_warehouse_date
                ON tenant_homemade_stock_movements(warehouse_id, occurred_at DESC);

            CREATE TABLE IF NOT EXISTS tenant_tips_roster_entries (
                id TEXT PRIMARY KEY,
                area TEXT NOT NULL,
                staff_name TEXT NOT NULL,
                staff_lookup TEXT NOT NULL,
                score REAL NOT NULL DEFAULT 0,
                sort_order INTEGER NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                UNIQUE (area, staff_lookup)
            );

            CREATE TABLE IF NOT EXISTS tenant_tips_runs (
                id TEXT PRIMARY KEY,
                area TEXT NOT NULL,
                tip_date TEXT NOT NULL,
                total_tip_amount REAL NOT NULL DEFAULT 0,
                tip_pos_amount REAL NOT NULL DEFAULT 0,
                tip_pos_effective_amount REAL NOT NULL DEFAULT 0,
                tip_cash_amount REAL NOT NULL DEFAULT 0,
                total_score REAL NOT NULL DEFAULT 0,
                historical_total_amount REAL NOT NULL DEFAULT 0,
                payable_total_amount REAL NOT NULL DEFAULT 0,
                present_staff_count INTEGER NOT NULL DEFAULT 0,
                absent_staff_count INTEGER NOT NULL DEFAULT 0,
                payout_status TEXT NOT NULL DEFAULT 'pending',
                settled_at TEXT,
                settled_by_user_id TEXT,
                settled_by_name TEXT,
                saved_by_user_id TEXT,
                saved_by_name TEXT,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                UNIQUE (area, tip_date)
            );

            CREATE TABLE IF NOT EXISTS tenant_tips_run_entries (
                id TEXT PRIMARY KEY,
                run_id TEXT NOT NULL,
                area TEXT NOT NULL,
                staff_name TEXT NOT NULL,
                staff_lookup TEXT NOT NULL,
                score REAL NOT NULL DEFAULT 0,
                is_present INTEGER NOT NULL DEFAULT 0,
                amount_today REAL NOT NULL DEFAULT 0,
                historical_amount REAL NOT NULL DEFAULT 0,
                total_amount REAL NOT NULL DEFAULT 0,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (run_id) REFERENCES tenant_tips_runs(id) ON DELETE CASCADE,
                UNIQUE (run_id, staff_lookup)
            );

            CREATE TABLE IF NOT EXISTS tenant_tips_run_history_sources (
                id TEXT PRIMARY KEY,
                run_id TEXT NOT NULL,
                source_run_id TEXT NOT NULL,
                source_tip_date TEXT NOT NULL,
                created_at TEXT NOT NULL,
                updated_at TEXT NOT NULL,
                FOREIGN KEY (run_id) REFERENCES tenant_tips_runs(id) ON DELETE CASCADE,
                FOREIGN KEY (source_run_id) REFERENCES tenant_tips_runs(id) ON DELETE CASCADE,
                UNIQUE (run_id, source_run_id)
            );

            CREATE INDEX IF NOT EXISTS idx_tenant_tips_roster_area
                ON tenant_tips_roster_entries(area, sort_order ASC, staff_name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_tips_runs_area_date
                ON tenant_tips_runs(area, tip_date DESC, updated_at DESC);

            CREATE INDEX IF NOT EXISTS idx_tenant_tips_run_entries_run
                ON tenant_tips_run_entries(run_id, staff_name ASC);

            CREATE INDEX IF NOT EXISTS idx_tenant_tips_history_sources_run
                ON tenant_tips_run_history_sources(run_id, source_tip_date DESC);
            """
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "preparation_date",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "usage_scope",
            "TEXT NOT NULL DEFAULT 'both'",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "yield_ingredient_name",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "yield_ingredient_lookup",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "yield_input_amount",
            "REAL",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "yield_input_unit",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "yield_output_ml",
            "REAL",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "preparation_drive_file_id",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "preparation_drive_web_url",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipes",
            "preparation_drive_updated_at",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipe_ingredients",
            "linked_recipe_id",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_recipe_ingredients",
            "measurement_unit",
            "TEXT NOT NULL DEFAULT 'ml'",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_stock_settings",
            "bar_calendar_json",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_homemade_stock_settings",
            "restaurant_calendar_json",
            "TEXT",
        )
        existing_tips_run_columns = {
            row["name"]
            for row in connection.execute("PRAGMA table_info(tenant_tips_runs)").fetchall()
        }
        had_tip_pos_effective_amount = "tip_pos_effective_amount" in existing_tips_run_columns
        self._ensure_tenant_column(
            connection,
            "tenant_tips_runs",
            "tip_pos_amount",
            "REAL NOT NULL DEFAULT 0",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_tips_runs",
            "tip_pos_effective_amount",
            "REAL NOT NULL DEFAULT 0",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_tips_runs",
            "tip_cash_amount",
            "REAL NOT NULL DEFAULT 0",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_tips_runs",
            "payout_status",
            "TEXT NOT NULL DEFAULT 'pending'",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_tips_runs",
            "settled_at",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_tips_runs",
            "settled_by_user_id",
            "TEXT",
        )
        self._ensure_tenant_column(
            connection,
            "tenant_tips_runs",
            "settled_by_name",
            "TEXT",
        )
        connection.execute(
            """
            UPDATE tenant_tips_runs
            SET payout_status = 'pending'
            WHERE payout_status IS NULL OR trim(payout_status) = ''
            """
        )
        connection.execute(
            """
            UPDATE tenant_tips_runs
            SET payout_status = 'carried'
            WHERE payout_status = 'pending'
              AND id IN (
                  SELECT DISTINCT source_run_id
                  FROM tenant_tips_run_history_sources
              )
            """
        )
        connection.execute(
            """
            UPDATE tenant_tips_runs
            SET tip_cash_amount = total_tip_amount
            WHERE COALESCE(tip_pos_amount, 0) = 0
              AND COALESCE(tip_cash_amount, 0) = 0
              AND COALESCE(total_tip_amount, 0) > 0
            """
        )
        if not had_tip_pos_effective_amount:
            connection.execute(
                """
                UPDATE tenant_tips_runs
                SET tip_pos_effective_amount = tip_pos_amount
                WHERE COALESCE(tip_pos_effective_amount, 0) = 0
                  AND COALESCE(tip_pos_amount, 0) > 0
                """
            )
        connection.execute(
            """
            UPDATE tenant_homemade_recipe_ingredients
            SET measurement_unit = (
                SELECT CASE
                    WHEN lower(COALESCE(recipes.measurement_unit, 'ml')) = 'g' THEN 'g'
                    ELSE 'ml'
                END
                FROM tenant_homemade_recipes AS recipes
                WHERE recipes.id = tenant_homemade_recipe_ingredients.recipe_id
            )
            WHERE measurement_unit IS NULL OR trim(measurement_unit) = ''
            """
        )
        connection.execute(
            """
            UPDATE tenant_homemade_recipes
            SET usage_scope = 'both'
            WHERE usage_scope IS NULL
               OR trim(usage_scope) = ''
               OR lower(trim(usage_scope)) NOT IN ('bar', 'restaurant', 'both')
            """
        )
        connection.execute(
            """
            UPDATE tenant_homemade_stock_items
            SET measurement_unit = ?
            WHERE measurement_unit IS NULL OR trim(measurement_unit) != ?
            """,
            (HOMEMADE_STOCK_UNIT, HOMEMADE_STOCK_UNIT),
        )
        connection.execute(
            """
            UPDATE tenant_homemade_stock_session_items
            SET measurement_unit = ?
            WHERE measurement_unit IS NULL OR trim(measurement_unit) != ?
            """,
            (HOMEMADE_STOCK_UNIT, HOMEMADE_STOCK_UNIT),
        )
        self._ensure_inventory_catalog_sync_triggers(connection)
        connection.commit()

    def _ensure_registry_column(self, connection: sqlite3.Connection, table_name: str, column_name: str, column_sql: str) -> None:
        existing_columns = {
            row["name"]
            for row in connection.execute(f"PRAGMA table_info({table_name})").fetchall()
        }
        if column_name not in existing_columns:
            connection.execute(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_sql}")
            connection.commit()
            connection.commit()

    def _ensure_tenant_column(self, connection: sqlite3.Connection, table_name: str, column_name: str, column_sql: str) -> None:
        existing_columns = {
            row["name"]
            for row in connection.execute(f"PRAGMA table_info({table_name})").fetchall()
        }
        if column_name not in existing_columns:
            connection.execute(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_sql}")
            connection.commit()

    def _ensure_inventory_catalog_sync_triggers(self, connection: sqlite3.Connection) -> None:
        if not self._tenant_table_exists(connection, "ordini_products"):
            return
        product_columns = self._tenant_table_columns(connection, "ordini_products")
        if "product_name" not in product_columns or "supplier_name" not in product_columns:
            return
        connection.executescript(
            """
            CREATE TRIGGER IF NOT EXISTS trg_ordini_products_sync_inventory_names
            AFTER UPDATE OF product_name, supplier_name ON ordini_products
            WHEN COALESCE(OLD.product_name, '') <> COALESCE(NEW.product_name, '')
              OR COALESCE(OLD.supplier_name, '') <> COALESCE(NEW.supplier_name, '')
            BEGIN
                UPDATE tenant_inventory_stock_items
                SET
                    product_name = trim(COALESCE(NEW.product_name, '')),
                    product_lookup = lower(trim(COALESCE(NEW.product_name, ''))),
                    supplier_name = trim(COALESCE(NEW.supplier_name, '')),
                    supplier_lookup = lower(trim(COALESCE(NEW.supplier_name, ''))),
                    updated_at = datetime('now')
                WHERE product_id = NEW.id;

                UPDATE tenant_inventory_session_items
                SET
                    product_name = trim(COALESCE(NEW.product_name, '')),
                    product_lookup = lower(trim(COALESCE(NEW.product_name, ''))),
                    supplier_name = trim(COALESCE(NEW.supplier_name, '')),
                    supplier_lookup = lower(trim(COALESCE(NEW.supplier_name, ''))),
                    updated_at = datetime('now')
                WHERE product_id = NEW.id;

                UPDATE tenant_inventory_movements
                SET
                    product_name = trim(COALESCE(NEW.product_name, '')),
                    product_lookup = lower(trim(COALESCE(NEW.product_name, ''))),
                    supplier_name = trim(COALESCE(NEW.supplier_name, '')),
                    supplier_lookup = lower(trim(COALESCE(NEW.supplier_name, ''))),
                    updated_at = datetime('now')
                WHERE product_id = NEW.id;

                UPDATE tenant_inventory_daily_consumptions
                SET
                    product_name = trim(COALESCE(NEW.product_name, '')),
                    product_lookup = lower(trim(COALESCE(NEW.product_name, ''))),
                    supplier_name = trim(COALESCE(NEW.supplier_name, '')),
                    supplier_lookup = lower(trim(COALESCE(NEW.supplier_name, ''))),
                    updated_at = datetime('now')
                WHERE product_name = OLD.product_name
                  AND supplier_name = OLD.supplier_name;

                UPDATE tenant_inventory_snapshot_items
                SET
                    product_name = trim(COALESCE(NEW.product_name, '')),
                    product_lookup = lower(trim(COALESCE(NEW.product_name, ''))),
                    supplier_name = trim(COALESCE(NEW.supplier_name, '')),
                    supplier_lookup = lower(trim(COALESCE(NEW.supplier_name, ''))),
                    updated_at = datetime('now')
                WHERE product_name = OLD.product_name
                  AND supplier_name = OLD.supplier_name;
            END;
            """
        )

    @contextmanager
    def _connect_registry(self):
        connection = sqlite3.connect(self._registry_path)
        connection.row_factory = sqlite3.Row
        connection.execute("PRAGMA foreign_keys = ON;")
        connection.execute("PRAGMA journal_mode = WAL;")
        try:
            yield connection
        finally:
            connection.close()

    @contextmanager
    def _connect_tenant_database(self, database_path: str):
        path = Path(database_path)
        path.parent.mkdir(parents=True, exist_ok=True)
        connection = sqlite3.connect(path)
        connection.row_factory = sqlite3.Row
        connection.execute("PRAGMA foreign_keys = ON;")
        self._ensure_tenant_database_schema(connection)
        try:
            yield connection
        finally:
            connection.close()

    def _upsert_tenant_profile(
        self,
        connection: sqlite3.Connection,
        *,
        tenant_id: str,
        tenant_name: str,
        tenant_slug: str,
        venue_name: str,
        address: str,
        phone_number: str | None,
        whatsapp_number: str | None,
        admin_name: str,
        admin_username: str,
        admin_email: str,
        admin_phone_number: str | None,
        password_hash: str,
        created_at: str,
    ) -> None:
        connection.execute(
            """
            INSERT OR REPLACE INTO tenant_profile (
                tenant_id,
                tenant_name,
                tenant_slug,
                venue_name,
                address,
                phone_number,
                whatsapp_number,
                admin_name,
                admin_username,
                admin_email,
                admin_phone_number,
                password_hash,
                created_at,
                updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                tenant_id,
                tenant_name,
                tenant_slug,
                venue_name,
                address,
                phone_number,
                whatsapp_number,
                admin_name,
                admin_username,
                admin_email,
                admin_phone_number,
                password_hash,
                created_at,
                _iso_now(),
            ),
        )

    def _ensure_super_admin_account(self, connection: sqlite3.Connection) -> None:
        created_at = _iso_now()
        database_path = str(self._tenant_dir / f"{SUPER_ADMIN_TENANT_SLUG}.sqlite3")
        tenant_exists = connection.execute(
            "SELECT id FROM tenants WHERE id = ? LIMIT 1",
            (SUPER_ADMIN_TENANT_ID,),
        ).fetchone()
        if tenant_exists is None:
            connection.execute(
                """
                INSERT INTO tenants (id, name, slug, database_path, created_at)
                VALUES (?, ?, ?, ?, ?)
                """,
                (
                    SUPER_ADMIN_TENANT_ID,
                    SUPER_ADMIN_TENANT_NAME,
                    SUPER_ADMIN_TENANT_SLUG,
                    database_path,
                    created_at,
                ),
            )

        venue_exists = connection.execute(
            "SELECT id FROM venues WHERE id = ? LIMIT 1",
            (SUPER_ADMIN_VENUE_ID,),
        ).fetchone()
        if venue_exists is None:
            connection.execute(
                """
                INSERT INTO venues (id, tenant_id, name, address, phone_number, whatsapp_number, created_at)
                VALUES (?, ?, ?, '', NULL, NULL, ?)
                """,
                (
                    SUPER_ADMIN_VENUE_ID,
                    SUPER_ADMIN_TENANT_ID,
                    SUPER_ADMIN_TENANT_NAME,
                    created_at,
                ),
            )

        user_exists = connection.execute(
            "SELECT id, password_hash, name FROM users WHERE id = ? LIMIT 1",
            (SUPER_ADMIN_USER_ID,),
        ).fetchone()
        if user_exists is None:
            connection.execute(
                """
                INSERT INTO users (id, tenant_id, name, username, email, phone_number, password_hash, role, created_at)
                VALUES (?, ?, ?, ?, ?, NULL, '', ?, ?)
                """,
                (
                    SUPER_ADMIN_USER_ID,
                    SUPER_ADMIN_TENANT_ID,
                    "Super Admin",
                    SUPER_ADMIN_USERNAME,
                    SUPER_ADMIN_EMAIL,
                    "super_admin",
                    created_at,
                ),
            )
            password_hash = ""
            admin_name = "Super Admin"
        else:
            password_hash = user_exists["password_hash"] or ""
            admin_name = user_exists["name"] or "Super Admin"
            connection.execute(
                "UPDATE users SET username = ?, email = ? WHERE id = ?",
                (SUPER_ADMIN_USERNAME, SUPER_ADMIN_EMAIL, SUPER_ADMIN_USER_ID),
            )

        self._create_tenant_database(
            database_path=database_path,
            tenant_id=SUPER_ADMIN_TENANT_ID,
            tenant_name=SUPER_ADMIN_TENANT_NAME,
            tenant_slug=SUPER_ADMIN_TENANT_SLUG,
            venue_name=SUPER_ADMIN_TENANT_NAME,
            address="",
            phone_number=None,
            whatsapp_number=None,
            admin_name=admin_name,
            admin_username=SUPER_ADMIN_USERNAME,
            admin_email=SUPER_ADMIN_EMAIL,
            admin_phone_number=None,
            password_hash=password_hash,
            created_at=created_at,
        )
        with self._connect_tenant_database(database_path) as tenant_connection:
            tenant_connection.execute("DELETE FROM tenant_module_settings")
            tenant_connection.commit()

    def _create_tenant_database(
        self,
        *,
        database_path: str,
        tenant_id: str,
        tenant_name: str,
        tenant_slug: str,
        venue_name: str,
        address: str,
        phone_number: str | None,
        whatsapp_number: str | None,
        admin_name: str,
        admin_username: str,
        admin_email: str,
        admin_phone_number: str | None,
        password_hash: str,
        created_at: str,
    ) -> None:
        with self._connect_tenant_database(database_path) as connection:
            self._upsert_tenant_profile(
                connection,
                tenant_id=tenant_id,
                tenant_name=tenant_name,
                tenant_slug=tenant_slug,
                venue_name=venue_name,
                address=address,
                phone_number=phone_number,
                whatsapp_number=whatsapp_number,
                admin_name=admin_name,
                admin_username=admin_username,
                admin_email=admin_email,
                admin_phone_number=admin_phone_number,
                password_hash=password_hash,
                created_at=created_at,
            )

            for module in MODULE_CATALOG:
                enabled = 1 if module.status == "active" else 0
                plan_name = "Platform Active" if enabled else "Planned"
                connection.execute(
                    """
                    INSERT OR REPLACE INTO tenant_module_settings (
                        module_key,
                        enabled,
                        plan_name,
                        activated_at,
                        expires_at
                    ) VALUES (?, ?, ?, ?, ?)
                    """,
                    (
                        module.key,
                        enabled,
                        plan_name,
                        created_at if enabled else None,
                        None,
                    ),
                )

            connection.commit()

    def _session_identity_from_row(self, row: sqlite3.Row) -> SessionIdentity:
        return SessionIdentity(
            token=row["token"],
            tenant_id=row["tenant_id"],
            tenant_slug=row["tenant_slug"],
            tenant_name=row["tenant_name"],
            home_tenant_id=row["home_tenant_id"],
            home_tenant_slug=row["home_tenant_slug"],
            home_tenant_name=row["home_tenant_name"],
            user_id=row["user_id"],
            user_email=row["user_email"],
            username=row["username"],
            user_name=row["user_name"],
            role=row["role"],
            permissions=_resolve_role_permissions(row["role"], row["permissions_json"] if "permissions_json" in row.keys() else None),
            assistant_scopes=_resolve_user_assistant_scopes(
                row["role"],
                row["permissions_json"] if "permissions_json" in row.keys() else None,
                row["assistant_scopes_json"] if "assistant_scopes_json" in row.keys() else None,
            ),
            database_path=row["database_path"],
            expires_at=row["expires_at"],
        )

    def _menu_asset_from_row(self, row: sqlite3.Row) -> MenuAssetRecord:
        return MenuAssetRecord(
            id=row["id"],
            tenant_id=row["tenant_id"],
            original_name=row["original_name"],
            display_name=row["display_name"],
            mime_type=row["mime_type"],
            kind=row["kind"],
            file_size_bytes=int(row["file_size_bytes"]),
            storage_path=row["storage_path"],
            extracted_text=row["extracted_text"] or "",
            analysis_text=row["analysis_text"] or "",
            status=row["status"],
            error_detail=row["error_detail"],
            created_at=row["created_at"],
            updated_at=row["updated_at"],
        )

    def _fiscal_document_from_row(self, row: sqlite3.Row) -> FiscalDocumentRecord:
        total_amount = row["total_amount"]
        return FiscalDocumentRecord(
            id=row["id"],
            tenant_id=row["tenant_id"],
            original_name=row["original_name"],
            display_name=row["display_name"],
            mime_type=row["mime_type"],
            kind=row["kind"],
            file_size_bytes=int(row["file_size_bytes"]),
            file_hash=row["file_hash"],
            storage_path=row["storage_path"],
            document_type=row["document_type"],
            document_number=row["document_number"],
            document_date=row["document_date"],
            supplier_name=row["supplier_name"],
            total_amount=float(total_amount) if total_amount is not None else None,
            currency=row["currency"] or "EUR",
            summary_text=row["summary_text"] or "",
            extracted_text=row["extracted_text"] or "",
            preview_text=row["preview_text"] or "",
            drive_file_id=row["drive_file_id"],
            drive_web_url=row["drive_web_url"],
            drive_uploaded_at=row["drive_uploaded_at"],
            status=row["status"],
            matching_status=row["matching_status"] or "pending",
            review_status=row["review_status"] or "to_review",
            error_detail=row["error_detail"],
            created_at=row["created_at"],
            updated_at=row["updated_at"],
        )

    def _fiscal_document_line_item_from_row(self, row: sqlite3.Row) -> FiscalDocumentLineItemRecord:
        def _float_or_none(value: object) -> float | None:
            return float(value) if value is not None else None

        return FiscalDocumentLineItemRecord(
            id=row["id"],
            tenant_id=row["tenant_id"],
            document_id=row["document_id"],
            line_index=int(row["line_index"]),
            product_code=row["product_code"],
            iso_code=row["iso_code"],
            description=row["description"] or "",
            category_code=row["category_code"],
            unit_code=row["unit_code"],
            pack_count=_float_or_none(row["pack_count"]),
            quantity=_float_or_none(row["quantity"]),
            gross_quantity=_float_or_none(row["gross_quantity"]),
            tare_quantity=_float_or_none(row["tare_quantity"]),
            net_quantity=_float_or_none(row["net_quantity"]),
            unit_price=_float_or_none(row["unit_price"]),
            line_total=_float_or_none(row["line_total"]),
            vat_code=row["vat_code"],
            raw_row_text=row["raw_row_text"] or "",
            created_at=row["created_at"],
            updated_at=row["updated_at"],
        )

    def _fiscal_document_dedup_key(self, record: FiscalDocumentRecord) -> tuple[object, ...]:
        return (
            "semantic",
            (record.document_type or "").strip().casefold(),
            (record.document_number or "").strip().casefold(),
            (record.document_date or "").strip(),
            (record.supplier_name or "").strip().casefold(),
            round(float(record.total_amount), 2) if record.total_amount is not None else None,
            (record.original_name or "").strip().casefold(),
        )

    def _fiscal_document_inbox_dedup_key(self, record: FiscalDocumentInboxItemRecord) -> tuple[object, ...]:
        message_id = (record.message_id or "").strip()
        attachment_name = (record.attachment_name or "").strip().casefold()
        if message_id and attachment_name:
            return ("message", message_id, attachment_name)
        file_hash = (record.file_hash or "").strip()
        if file_hash:
            return ("hash", file_hash)
        return ("id", record.id)

    def _fiscal_document_inbox_dedup_priority(self, record: FiscalDocumentInboxItemRecord) -> int:
        status = (record.sync_status or "").strip()
        if status == "imported" and (record.document_id or "").strip():
            return 0
        elif status == "imported":
            return 1
        elif (record.file_hash or "").strip():
            return 2
        elif status == "unsupported":
            return 3
        elif status == "error":
            return 4
        return 5

    def _assistant_thread_from_row(self, row: sqlite3.Row) -> AssistantThreadRecord:
        return AssistantThreadRecord(
            id=row["id"],
            tenant_id=row["tenant_id"],
            user_id=row["user_id"],
            surface=row["surface"],
            status=row["status"],
            state_json=row["state_json"] if "state_json" in row.keys() else None,
            created_at=row["created_at"],
            updated_at=row["updated_at"],
        )

    def _assistant_message_from_row(self, row: sqlite3.Row) -> AssistantStoredMessage:
        return AssistantStoredMessage(
            id=row["id"],
            thread_id=row["thread_id"],
            role=row["role"],
            content=row["content"],
            created_at=row["created_at"],
            sort_index=int(row["sort_index"]),
        )

    def _assistant_run_from_row(self, row: sqlite3.Row) -> AssistantRunRecord:
        return AssistantRunRecord(
            id=row["id"],
            thread_id=row["thread_id"],
            tenant_id=row["tenant_id"],
            user_id=row["user_id"],
            surface=row["surface"],
            route=row["route"],
            model=row["model"],
            user_message=row["user_message"],
            assistant_reply=row["assistant_reply"],
            trace_json=row["trace_json"],
            created_at=row["created_at"],
        )

    def _fiscal_document_settings_from_row(self, row: sqlite3.Row) -> FiscalDocumentSettingsRecord:
        return FiscalDocumentSettingsRecord(
            tenant_id=row["tenant_id"],
            inbound_email=row["inbound_email"],
            updated_at=row["updated_at"],
        )

    def _fiscal_document_inbox_item_from_row(self, row: sqlite3.Row) -> FiscalDocumentInboxItemRecord:
        return FiscalDocumentInboxItemRecord(
            id=row["id"],
            tenant_id=row["tenant_id"],
            message_id=row["message_id"],
            attachment_id=row["attachment_id"],
            file_hash=row["file_hash"],
            subject=row["subject"] or "",
            sender=row["sender"],
            received_at=row["received_at"],
            attachment_name=row["attachment_name"],
            mime_type=row["mime_type"],
            sync_status=row["sync_status"],
            document_id=row["document_id"],
            error_detail=row["error_detail"],
            created_at=row["created_at"],
            updated_at=row["updated_at"],
        )

    def _timeclock_entry_from_row(self, row: sqlite3.Row) -> TimeclockEntryRecord:
        duration_seconds = row["duration_seconds"]
        return TimeclockEntryRecord(
            id=row["id"],
            user_id=row["user_id"],
            user_name=row["user_name"],
            username=row["username"],
            user_email=row["user_email"],
            started_at=row["started_at"],
            ended_at=row["ended_at"],
            duration_seconds=int(duration_seconds) if duration_seconds is not None else None,
            started_source=row["started_source"] or "portal",
            ended_source=row["ended_source"],
            notes=row["notes"],
            created_at=row["created_at"],
            updated_at=row["updated_at"],
        )

    def _report_from_row(self, row: sqlite3.Row) -> TenantReportRecord:
        return TenantReportRecord(
            id=row["id"],
            reporter_user_id=row["reporter_user_id"],
            reporter_name=row["reporter_name"],
            reporter_username=row["reporter_username"],
            reporter_email=row["reporter_email"],
            title=row["title"] or "",
            description=row["description"] or "",
            category=row["category"] or "problem",
            location=row["location"],
            priority=row["priority"] or "normal",
            status=row["status"] or "new",
            admin_note=row["admin_note"],
            status_updated_by_user_id=row["status_updated_by_user_id"],
            status_updated_by_name=row["status_updated_by_name"],
            resolved_at=row["resolved_at"],
            created_at=row["created_at"],
            updated_at=row["updated_at"],
        )

    def _create_session(self, connection: sqlite3.Connection, *, tenant_id: str, user_id: str) -> SessionIdentity:
        token = secrets.token_urlsafe(32)
        created_at = _iso_now()
        expires_at = (_utcnow() + self._session_duration).isoformat()
        connection.execute(
            """
            INSERT INTO sessions (token, tenant_id, user_id, created_at, expires_at)
            VALUES (?, ?, ?, ?, ?)
            """,
            (token, tenant_id, user_id, created_at, expires_at),
        )
        connection.commit()

        return self.get_session(token)  # type: ignore[return-value]

    def _lookup_user_for_login(self, connection: sqlite3.Connection, identifier: str) -> sqlite3.Row | None:
        normalized = _normalize_lookup(identifier)
        return connection.execute(
            """
            SELECT
                users.*,
                tenants.name AS tenant_name,
                tenants.slug AS tenant_slug,
                tenants.database_path AS database_path
            FROM users
            JOIN tenants ON tenants.id = users.tenant_id
            WHERE lower(users.email) = ? OR lower(users.username) = ?
            LIMIT 1
            """,
            (normalized, normalized),
        ).fetchone()

    def _lookup_login_candidates(self, connection: sqlite3.Connection, identifier: str) -> list[sqlite3.Row]:
        normalized = _normalize_lookup(identifier)
        return list(
            connection.execute(
                """
                SELECT
                    users.*,
                    tenants.name AS tenant_name,
                    tenants.slug AS tenant_slug,
                    tenants.database_path AS database_path
                FROM users
                JOIN tenants ON tenants.id = users.tenant_id
                WHERE lower(users.email) = ? OR lower(users.username) = ?
                ORDER BY
                    CASE
                        WHEN lower(users.username) = ? THEN 0
                        WHEN lower(users.email) = ? THEN 1
                        ELSE 2
                    END,
                    CASE WHEN users.role = 'super_admin' THEN 0 ELSE 1 END,
                    users.created_at ASC
                """,
                (normalized, normalized, normalized, normalized),
            )
        )

    def _context_rows(self, connection: sqlite3.Connection, tenant_id: str) -> tuple[sqlite3.Row, list[sqlite3.Row], list[sqlite3.Row], list[sqlite3.Row]]:
        tenant = connection.execute("SELECT * FROM tenants WHERE id = ?", (tenant_id,)).fetchone()
        if tenant is None:
            raise KeyError(f"Tenant {tenant_id} non trovato")

        venues = list(connection.execute("SELECT * FROM venues WHERE tenant_id = ? ORDER BY created_at ASC", (tenant_id,)))
        users = list(connection.execute("SELECT * FROM users WHERE tenant_id = ? ORDER BY created_at ASC", (tenant_id,)))
        modules = list(
            connection.execute(
                "SELECT * FROM tenant_modules WHERE tenant_id = ? ORDER BY enabled DESC, module_key ASC",
                (tenant_id,),
            )
        )
        return tenant, venues, users, modules

    def _normalize_register_like_payload(
        self,
        payload: RegisterTenantPayload | AdminCreateTenantPayload | AdminUpdateTenantAdminPayload,
    ) -> dict[str, str | None]:
        return {
            "locale_name": payload.locale_name.strip(),
            "address": payload.address.strip(),
            "phone_number": payload.phone_number.strip() if payload.phone_number else None,
            "whatsapp_number": payload.whatsapp_number.strip() if payload.whatsapp_number else None,
            "admin_name": payload.admin_name.strip(),
            "username": payload.username.strip(),
            "email": payload.email.strip(),
        }

    def _normalize_tenant_staff_payload(
        self,
        payload: TenantStaffUserCreatePayload | TenantStaffUserUpdatePayload,
    ) -> dict[str, str | None]:
        return {
            "name": payload.name.strip(),
            "username": payload.username.strip(),
            "email": payload.email.strip(),
            "phone_number": payload.phone_number.strip() if payload.phone_number else None,
        }

    def _can_manage_tenant_accounts(self, session: SessionIdentity) -> bool:
        if session.tenant_id == SUPER_ADMIN_TENANT_ID:
            return False
        return session.role in {"owner", "super_admin"}

    def _can_access_timeclock(self, session: SessionIdentity) -> bool:
        if session.tenant_id == SUPER_ADMIN_TENANT_ID:
            return False
        return session.role in {"owner", "super_admin"} or self.session_has_permission(session, "timeclock")

    def _parse_iso_datetime(self, raw_value: str | None) -> datetime | None:
        if not raw_value:
            return None
        try:
            parsed = datetime.fromisoformat(raw_value)
        except ValueError:
            return None
        if parsed.tzinfo is None:
            parsed = parsed.replace(tzinfo=timezone.utc)
        return parsed

    def _timeclock_local_datetime(self, raw_value: str | None) -> datetime | None:
        parsed = self._parse_iso_datetime(raw_value)
        if parsed is None:
            return None
        return parsed.astimezone(_tenant_local_timezone())

    def _timeclock_local_iso(self, raw_value: str | None) -> str | None:
        local_datetime = self._timeclock_local_datetime(raw_value)
        return local_datetime.isoformat() if local_datetime is not None else raw_value

    def _timeclock_local_date(self, raw_value: str | None) -> date | None:
        local_datetime = self._timeclock_local_datetime(raw_value)
        return local_datetime.date() if local_datetime is not None else None

    def _serialize_timeclock_entry(self, record: TimeclockEntryRecord, *, reference_now: datetime | None = None) -> dict[str, object]:
        started_at = self._parse_iso_datetime(record.started_at)
        ended_at = self._parse_iso_datetime(record.ended_at)
        now = reference_now or _local_now()
        duration_seconds = record.duration_seconds
        if duration_seconds is None and started_at is not None:
            end_reference = ended_at or now
            duration_seconds = max(int((end_reference - started_at).total_seconds()), 0)
        entry_date = self._timeclock_local_date(record.started_at)
        return {
            "id": record.id,
            "user_id": record.user_id,
            "user_name": record.user_name,
            "username": record.username,
            "user_email": record.user_email,
            "started_at": self._timeclock_local_iso(record.started_at),
            "ended_at": self._timeclock_local_iso(record.ended_at),
            "duration_seconds": duration_seconds,
            "duration_hours": round((duration_seconds or 0) / 3600, 2),
            "is_active": record.ended_at is None,
            "started_source": record.started_source,
            "ended_source": record.ended_source,
            "notes": record.notes,
            "entry_date": entry_date,
            "created_at": record.created_at,
            "updated_at": record.updated_at,
        }

    def _read_timeclock_entries(
        self,
        session: SessionIdentity,
        *,
        user_id: str | None = None,
        start_date: str | None = None,
        end_date: str | None = None,
        include_active_only: bool = False,
        limit: int = 200,
    ) -> list[TimeclockEntryRecord]:
        filters: list[str] = []
        values: list[object] = []
        if user_id:
            filters.append("user_id = ?")
            values.append(user_id)
        if start_date:
            filters.append("date(substr(started_at, 1, 10)) >= date(?, '-1 day')")
            values.append(start_date)
        if end_date:
            filters.append("date(substr(started_at, 1, 10)) <= date(?, '+1 day')")
            values.append(end_date)
        if include_active_only:
            filters.append("ended_at IS NULL")
        where_sql = f"WHERE {' AND '.join(filters)}" if filters else ""
        requested_limit = max(1, min(limit, 2000))
        sql_limit = max(requested_limit, 2000) if start_date or end_date else requested_limit

        with self._connect_tenant_database(session.database_path) as connection:
            rows = connection.execute(
                f"""
                SELECT *
                FROM tenant_timeclock_entries
                {where_sql}
                ORDER BY started_at DESC
                LIMIT ?
                """,
                (*values, sql_limit),
            ).fetchall()

        entries = [self._timeclock_entry_from_row(row) for row in rows]
        if start_date or end_date:
            start_day = date.fromisoformat(start_date) if start_date else None
            end_day = date.fromisoformat(end_date) if end_date else None
            filtered_entries: list[TimeclockEntryRecord] = []
            for entry in entries:
                entry_day = self._timeclock_local_date(entry.started_at)
                if entry_day is None:
                    continue
                if start_day is not None and entry_day < start_day:
                    continue
                if end_day is not None and entry_day > end_day:
                    continue
                filtered_entries.append(entry)
            entries = filtered_entries
        entries.sort(
            key=lambda entry: self._parse_iso_datetime(entry.started_at) or datetime.min.replace(tzinfo=timezone.utc),
            reverse=True,
        )
        return entries[:requested_limit]

    def _build_timeclock_summary(
        self,
        entries: list[TimeclockEntryRecord],
        *,
        now: datetime | None = None,
    ) -> list[dict[str, object]]:
        reference_now = now or _local_now()
        grouped: dict[str, dict[str, object]] = {}
        for entry in entries:
            serialized = self._serialize_timeclock_entry(entry, reference_now=reference_now)
            bucket = grouped.setdefault(
                entry.user_id,
                {
                    "user_id": entry.user_id,
                    "user_name": entry.user_name,
                    "username": entry.username,
                    "user_email": entry.user_email,
                    "entries": 0,
                    "total_seconds": 0,
                    "active_entries": 0,
                    "last_started_at": None,
                },
            )
            bucket["entries"] = int(bucket["entries"]) + 1
            bucket["total_seconds"] = int(bucket["total_seconds"]) + int(serialized["duration_seconds"] or 0)
            if serialized["is_active"]:
                bucket["active_entries"] = int(bucket["active_entries"]) + 1
            if bucket["last_started_at"] is None or str(serialized["started_at"]) > str(bucket["last_started_at"]):
                bucket["last_started_at"] = serialized["started_at"]

        summaries: list[dict[str, object]] = []
        for item in grouped.values():
            total_seconds = int(item["total_seconds"] or 0)
            summaries.append(
                {
                    **item,
                    "total_hours": round(total_seconds / 3600, 2),
                }
            )
        summaries.sort(
            key=lambda item: (
                str(item.get("user_name") or item.get("username") or item.get("user_email") or "").lower(),
                str(item.get("user_id") or ""),
            )
        )
        return summaries

    def _serialize_report_record(self, record: TenantReportRecord, *, include_admin_note: bool) -> dict[str, object]:
        payload: dict[str, object] = {
            "id": record.id,
            "reporter_user_id": record.reporter_user_id,
            "reporter_name": record.reporter_name,
            "reporter_username": record.reporter_username,
            "reporter_email": record.reporter_email,
            "title": record.title,
            "description": record.description,
            "category": record.category,
            "location": record.location,
            "priority": record.priority,
            "status": record.status,
            "status_updated_by_user_id": record.status_updated_by_user_id,
            "status_updated_by_name": record.status_updated_by_name,
            "resolved_at": record.resolved_at,
            "created_at": record.created_at,
            "updated_at": record.updated_at,
        }
        if include_admin_note:
            payload["admin_note"] = record.admin_note
        return payload

    def _serialize_push_subscription_row(self, row: sqlite3.Row) -> dict[str, object]:
        return {
            "id": row["id"],
            "user_id": row["user_id"],
            "endpoint": row["endpoint"],
            "keys": {
                "p256dh": row["p256dh"],
                "auth": row["auth"],
            },
            "user_agent": row["user_agent"],
            "enabled": bool(row["enabled"]),
            "last_error": row["last_error"],
            "created_at": row["created_at"],
            "updated_at": row["updated_at"],
        }

    def _read_report_row(self, connection: sqlite3.Connection, report_id: str) -> sqlite3.Row:
        row = connection.execute(
            "SELECT * FROM tenant_reports WHERE id = ? LIMIT 1",
            (report_id,),
        ).fetchone()
        if row is None:
            raise KeyError("Segnalazione non trovata.")
        return row

    def session_has_permission(self, session: SessionIdentity, permission: str) -> bool:
        normalized = permission.strip().lower()
        if session.role in {"owner", "super_admin"}:
            return True
        return normalized in session.permissions

    def session_has_assistant_scope(self, session: SessionIdentity, scope: str) -> bool:
        normalized = scope.strip().lower()
        if session.role in {"owner", "super_admin"}:
            return True
        return normalized in session.assistant_scopes

    def _can_access_inventory(self, session: SessionIdentity) -> bool:
        return session.role in {"owner", "super_admin"} or self.session_has_permission(session, "inventory")

    def _can_access_consumptions(self, session: SessionIdentity) -> bool:
        return session.role in {"owner", "super_admin"} or self.session_has_permission(session, "consumptions")

    def _can_access_inventory_data_for_consumptions(self, session: SessionIdentity) -> bool:
        return self._can_access_inventory(session) or self._can_access_consumptions(session)

    def _can_manage_inventory_warehouses(self, session: SessionIdentity) -> bool:
        return session.role == "super_admin"

    def _can_access_reports(self, session: SessionIdentity) -> bool:
        return session.role in {"owner", "super_admin"} or self.session_has_permission(session, "reports")

    def _can_manage_reports(self, session: SessionIdentity) -> bool:
        return session.role in {"owner", "super_admin"}

    def _can_access_homemade(self, session: SessionIdentity) -> bool:
        return session.role in {"owner", "super_admin"} or self.session_has_permission(session, "homemade")

    def _can_manage_homemade(self, session: SessionIdentity) -> bool:
        return session.role in {"owner", "super_admin"} or self.session_has_permission(session, "homemade_manage")

    def _can_manage_homemade_stock_warehouses(self, session: SessionIdentity) -> bool:
        return session.role == "super_admin"

    def _can_access_tips(self, session: SessionIdentity, area: str) -> bool:
        normalized_area = _normalize_tips_area(area)
        access_permission = f"tips_{normalized_area}"
        manage_permission = f"tips_{normalized_area}_manage"
        return (
            session.role in {"owner", "super_admin"}
            or self.session_has_permission(session, access_permission)
            or self.session_has_permission(session, manage_permission)
        )

    def _can_manage_tips(self, session: SessionIdentity, area: str) -> bool:
        normalized_area = _normalize_tips_area(area)
        return session.role in {"owner", "super_admin"} or self.session_has_permission(
            session,
            f"tips_{normalized_area}_manage",
        )

    def _tenant_table_exists(self, connection: sqlite3.Connection, table_name: str) -> bool:
        row = connection.execute(
            "SELECT name FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1",
            (table_name,),
        ).fetchone()
        return row is not None

    def _tenant_table_columns(self, connection: sqlite3.Connection, table_name: str) -> set[str]:
        if not self._tenant_table_exists(connection, table_name):
            return set()
        return {
            str(row["name"])
            for row in connection.execute(f"PRAGMA table_info({table_name})").fetchall()
        }

    def _serialize_inventory_warehouse_row(self, row: sqlite3.Row) -> dict[str, object]:
        return {
            "id": str(row["id"]),
            "name": str(row["name"]),
            "product_count": int(row["product_count"] or 0),
            "total_equivalent_units": float(row["total_equivalent_units"] or 0),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _serialize_homemade_stock_warehouse_row(self, row: sqlite3.Row) -> dict[str, object]:
        return {
            "id": str(row["id"]),
            "name": str(row["name"]),
            "recipe_count": int(row["recipe_count"] or 0),
            "total_quantity": float(row["total_quantity"] or 0),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _serialize_homemade_stock_item_row(self, row: sqlite3.Row) -> dict[str, object]:
        usage_scope = _normalize_homemade_usage_scope(
            str(row["usage_scope"] or "both") if "usage_scope" in row.keys() else "both"
        )
        return {
            "id": str(row["id"]),
            "recipe_id": str(row["recipe_id"]),
            "recipe_name": str(row["recipe_name"] or ""),
            "usage_scope": usage_scope,
            "usage_scope_label": _homemade_usage_scope_label(usage_scope),
            "measurement_unit": HOMEMADE_STOCK_UNIT,
            "quantity": float(row["quantity"] or 0),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _serialize_homemade_stock_session_row(self, row: sqlite3.Row) -> dict[str, object]:
        return {
            "id": str(row["id"]),
            "warehouse_id": str(row["warehouse_id"]),
            "warehouse_name": str(row["warehouse_name"] or ""),
            "inventory_date": str(row["inventory_date"]),
            "label": str(row["label"] or ""),
            "created_by_name": str(row["created_by_name"] or ""),
            "total_recipes": int(row["total_recipes"] or 0),
            "total_quantity": float(row["total_quantity"] or 0),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _serialize_homemade_recipe_row(self, row: sqlite3.Row) -> dict[str, object]:
        yield_ingredient_name = (
            str(row["yield_ingredient_name"] or "").strip() if "yield_ingredient_name" in row.keys() else ""
        )
        yield_ingredient_lookup = (
            str(row["yield_ingredient_lookup"] or "").strip() if "yield_ingredient_lookup" in row.keys() else ""
        )
        yield_input_amount = float(row["yield_input_amount"] or 0) if "yield_input_amount" in row.keys() else 0.0
        yield_input_unit = str(row["yield_input_unit"] or "").strip() if "yield_input_unit" in row.keys() else ""
        yield_output_ml = float(row["yield_output_ml"] or 0) if "yield_output_ml" in row.keys() else 0.0
        yield_ml_per_input_unit = (
            yield_output_ml / yield_input_amount
            if yield_input_amount > 0 and yield_output_ml > 0
            else None
        )
        usage_scope = _normalize_homemade_usage_scope(
            str(row["usage_scope"] or "both") if "usage_scope" in row.keys() else "both"
        )
        return {
            "id": str(row["id"]),
            "name": str(row["name"]),
            "measurement_unit": str(row["measurement_unit"] or "ml"),
            "usage_scope": usage_scope,
            "usage_scope_label": _homemade_usage_scope_label(usage_scope),
            "notes": str(row["notes"] or "") or None,
            "yield_ingredient_name": yield_ingredient_name or None,
            "yield_ingredient_lookup": yield_ingredient_lookup or None,
            "yield_input_amount": yield_input_amount if yield_input_amount > 0 else None,
            "yield_input_unit": yield_input_unit or None,
            "yield_output_ml": yield_output_ml if yield_output_ml > 0 else None,
            "yield_ml_per_input_unit": yield_ml_per_input_unit,
            "total_parts": float(row["total_parts"] or 0),
            "ingredient_count": int(row["ingredient_count"] or 0),
            "preparation_date": str(row["preparation_date"] or "") or None,
            "preparation_drive_file_id": str(row["preparation_drive_file_id"] or "") or None,
            "preparation_drive_web_url": str(row["preparation_drive_web_url"] or "") or None,
            "preparation_drive_updated_at": str(row["preparation_drive_updated_at"] or "") or None,
            "created_by_name": str(row["created_by_name"] or ""),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _serialize_homemade_recipe_ingredient_row(
        self,
        row: sqlite3.Row,
        reference_total: float | None = None,
    ) -> dict[str, object]:
        measurement_unit = _normalize_homemade_ingredient_unit(
            str(
                row["effective_measurement_unit"]
                if "effective_measurement_unit" in row.keys()
                else row["measurement_unit"] if "measurement_unit" in row.keys() else "ml"
            )
        )
        linked_recipe_id = str(row["linked_recipe_id"] or "").strip() if "linked_recipe_id" in row.keys() else ""
        linked_recipe_name = str(row["linked_recipe_name"] or "").strip() if "linked_recipe_name" in row.keys() else ""
        effective_ingredient_name = linked_recipe_name or str(row["ingredient_name"])
        part_amount = float(row["part_amount"] or 0)
        if measurement_unit in {"g_per_liter", "drops_per_liter"}:
            share_ratio = 0.0
            percentage = 0.0
            per_liter_quantity = part_amount
        else:
            safe_reference_total = float(reference_total or 0.0)
            share_ratio = (
                part_amount / safe_reference_total
                if safe_reference_total > 0
                else float(row["share_ratio"] or 0.0)
            )
            percentage = share_ratio * 100
            per_liter_quantity = share_ratio * 1000
        return {
            "id": str(row["id"]),
            "ingredient_name": effective_ingredient_name,
            "ingredient_mode": "recipe" if linked_recipe_id else "ingredient",
            "linked_recipe_id": linked_recipe_id or None,
            "linked_recipe_name": linked_recipe_name or None,
            "part_amount": part_amount,
            "measurement_unit": measurement_unit,
            "share_ratio": share_ratio,
            "percentage": percentage,
            "per_liter_quantity": per_liter_quantity,
            "calculation_mode": "per_liter" if measurement_unit in {"g_per_liter", "drops_per_liter"} else "proportional",
            "sort_order": int(row["sort_order"] or 0),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _serialize_inventory_stock_item_row(
        self,
        row: sqlite3.Row,
        lots: list[dict[str, object]],
    ) -> dict[str, object]:
        return {
            "id": str(row["id"]),
            "product_id": int(row["product_id"]) if row["product_id"] is not None else None,
            "product_name": str(row["product_name"]),
            "supplier_name": str(row["supplier_name"]),
            "total_equivalent_units": float(row["total_equivalent_units"] or 0),
            "lot_count": len(lots),
            "lots": lots,
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _serialize_inventory_session_row(self, row: sqlite3.Row) -> dict[str, object]:
        return {
            "id": str(row["id"]),
            "warehouse_id": str(row["warehouse_id"]),
            "warehouse_name": str(row["warehouse_name"] or ""),
            "inventory_date": str(row["inventory_date"]),
            "label": str(row["label"] or ""),
            "created_by_user_id": str(row["created_by_user_id"] or ""),
            "created_by_name": str(row["created_by_name"] or ""),
            "total_products": int(row["total_products"] or 0),
            "total_equivalent_units": float(row["total_equivalent_units"] or 0),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _serialize_inventory_snapshot_row(self, row: sqlite3.Row) -> dict[str, object]:
        return {
            "id": str(row["id"]),
            "snapshot_date": str(row["snapshot_date"]),
            "label": str(row["label"] or ""),
            "created_by_name": str(row["created_by_name"] or ""),
            "total_warehouses": int(row["total_warehouses"] or 0),
            "total_products": int(row["total_products"] or 0),
            "total_equivalent_units": float(row["total_equivalent_units"] or 0),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _inventory_lot_requires_units_per_pack(self, lot_code: str | None) -> bool:
        return _normalize_lookup(str(lot_code or "")) in {"ct", "cartone", "cartoni", "cassa", "casse"}

    def _inventory_catalog_units_per_pack_by_item_rows(
        self,
        connection: sqlite3.Connection,
        item_rows: list[sqlite3.Row],
    ) -> dict[str, float]:
        if not item_rows or not self._tenant_table_exists(connection, "ordini_products"):
            return {}
        product_columns = self._tenant_table_columns(connection, "ordini_products")
        if "units_per_pack" not in product_columns:
            return {}

        keys_by_item_id: dict[str, tuple[str, str]] = {}
        target_keys: set[tuple[str, str]] = set()
        for row in item_rows:
            key = (str(row["product_lookup"] or ""), str(row["supplier_lookup"] or ""))
            if not key[0]:
                continue
            item_id = str(row["id"])
            keys_by_item_id[item_id] = key
            target_keys.add(key)
        if not target_keys:
            return {}

        catalog_by_key: dict[tuple[str, str], float] = {}
        rows = connection.execute(
            """
            SELECT product_name, supplier_name, lot_code, units_per_pack
            FROM ordini_products
            WHERE active = 1
              AND units_per_pack IS NOT NULL
              AND units_per_pack > 0
            """
        ).fetchall()
        for row in rows:
            if not self._inventory_lot_requires_units_per_pack(str(row["lot_code"] or "")):
                continue
            key = (_normalize_lookup(str(row["product_name"] or "")), _normalize_lookup(str(row["supplier_name"] or "")))
            if key not in target_keys:
                continue
            units_per_pack = float(row["units_per_pack"] or 0)
            if units_per_pack <= 0:
                continue
            current = catalog_by_key.get(key)
            if current is None or units_per_pack > current:
                catalog_by_key[key] = units_per_pack

        return {
            item_id: catalog_by_key[key]
            for item_id, key in keys_by_item_id.items()
            if key in catalog_by_key
        }

    def _inventory_barcode_matches(
        self,
        *,
        stored_code: str | None,
        scanned_code: str | None,
        lot_code: str | None,
    ) -> bool:
        normalized_stored = _normalize_inventory_barcode(stored_code)
        normalized_scanned = _normalize_inventory_barcode(scanned_code)
        if not normalized_stored or not normalized_scanned:
            return False
        if normalized_stored == normalized_scanned:
            return True
        if not self._inventory_lot_requires_units_per_pack(lot_code):
            return False
        return normalized_stored in _build_carton_barcode_aliases(normalized_scanned)

    def _inventory_barcodes_conflict(
        self,
        *,
        left_code: str | None,
        left_lot_code: str | None,
        right_code: str | None,
        right_lot_code: str | None,
    ) -> bool:
        normalized_left = _normalize_inventory_barcode(left_code)
        normalized_right = _normalize_inventory_barcode(right_code)
        if not normalized_left or not normalized_right:
            return False
        if normalized_left == normalized_right:
            return True
        if not (
            self._inventory_lot_requires_units_per_pack(left_lot_code)
            and self._inventory_lot_requires_units_per_pack(right_lot_code)
        ):
            return False
        return normalized_left in _build_carton_barcode_aliases(normalized_right)

    def _inventory_effective_units(
        self,
        *,
        lot_code: str | None,
        quantity: float,
        units_per_pack: float | None,
    ) -> float:
        safe_quantity = float(quantity)
        if safe_quantity < 0:
            raise ValueError("La quantita non puo essere negativa.")
        if safe_quantity == 0:
            return 0.0
        if self._inventory_lot_requires_units_per_pack(lot_code):
            if units_per_pack is None or float(units_per_pack) <= 0:
                raise ValueError(
                    "Per questo lotto manca il dato unita per pack nel catalogo prodotti. "
                    "Completa prima il prodotto e poi aggiungilo in inventario."
                )
            return safe_quantity * float(units_per_pack)
        return safe_quantity

    def _normalize_inventory_stock_item_lots(
        self,
        connection: sqlite3.Connection,
        item_id: str,
        *,
        timestamp: str,
    ) -> bool:
        lot_rows = connection.execute(
            """
            SELECT *
            FROM tenant_inventory_stock_lots
            WHERE item_id = ?
            ORDER BY lower(lot_code) ASC, created_at ASC
            """,
            (item_id,),
        ).fetchall()
        if not lot_rows:
            return False

        pack_row = next(
            (
                row
                for row in lot_rows
                if self._inventory_lot_requires_units_per_pack(str(row["lot_code"] or ""))
                and row["units_per_pack"] is not None
                and float(row["units_per_pack"] or 0) > 0
            ),
            None,
        )
        if pack_row is None:
            return False

        units_per_pack = float(pack_row["units_per_pack"] or 0)
        if units_per_pack <= 1:
            return False

        pack_lot_code = str(pack_row["lot_code"] or "ct") or "ct"
        pack_lot_lookup = str(pack_row["lot_lookup"] or _normalize_lookup(pack_lot_code))
        single_rows = [
            row
            for row in lot_rows
            if not self._inventory_lot_requires_units_per_pack(str(row["lot_code"] or ""))
        ]
        single_lot_code = str(single_rows[0]["lot_code"] or "bt") if single_rows else "bt"
        single_lot_lookup = _normalize_lookup(single_lot_code)
        total_equivalent_units = round(sum(float(row["equivalent_units"] or 0) for row in lot_rows), 6)
        normalized_pack_quantity = int((total_equivalent_units + 1e-6) // units_per_pack)
        normalized_pack_equivalent_units = round(normalized_pack_quantity * units_per_pack, 6)
        normalized_single_quantity = round(total_equivalent_units - normalized_pack_equivalent_units, 6)

        current_pack_quantity = sum(
            float(row["quantity"] or 0)
            for row in lot_rows
            if str(row["lot_lookup"] or "") == pack_lot_lookup
        )
        current_pack_equivalent_units = sum(
            float(row["equivalent_units"] or 0)
            for row in lot_rows
            if str(row["lot_lookup"] or "") == pack_lot_lookup
        )
        current_single_equivalent_units = sum(
            float(row["equivalent_units"] or 0)
            for row in lot_rows
            if str(row["lot_lookup"] or "") == single_lot_lookup
        )
        if (
            abs(current_pack_quantity - normalized_pack_quantity) <= 1e-6
            and abs(current_pack_equivalent_units - normalized_pack_equivalent_units) <= 1e-6
            and abs(current_single_equivalent_units - normalized_single_quantity) <= 1e-6
            and all(
                str(row["lot_lookup"] or "") in {pack_lot_lookup, single_lot_lookup}
                for row in lot_rows
            )
        ):
            return False

        keep_pack_id = str(pack_row["id"])
        keep_single_row = next((row for row in single_rows if str(row["lot_lookup"] or "") == single_lot_lookup), None)
        keep_single_id = str(keep_single_row["id"]) if keep_single_row is not None else f"inventory_lot_{uuid.uuid4().hex}"

        connection.execute(
            "DELETE FROM tenant_inventory_stock_lots WHERE item_id = ? AND id NOT IN (?, ?)",
            (item_id, keep_pack_id, keep_single_id),
        )
        if normalized_pack_quantity > 0:
            connection.execute(
                """
                UPDATE tenant_inventory_stock_lots
                SET lot_code = ?, lot_lookup = ?, quantity = ?, units_per_pack = ?, equivalent_units = ?, updated_at = ?
                WHERE id = ?
                """,
                (
                    pack_lot_code,
                    pack_lot_lookup,
                    float(normalized_pack_quantity),
                    units_per_pack,
                    normalized_pack_equivalent_units,
                    timestamp,
                    keep_pack_id,
                ),
            )
        else:
            connection.execute("DELETE FROM tenant_inventory_stock_lots WHERE id = ?", (keep_pack_id,))

        if normalized_single_quantity > 1e-6:
            if keep_single_row is None:
                connection.execute(
                    """
                    INSERT INTO tenant_inventory_stock_lots (
                        id, item_id, lot_code, lot_lookup, quantity, units_per_pack, equivalent_units, created_at, updated_at
                    ) VALUES (?, ?, ?, ?, ?, NULL, ?, ?, ?)
                    """,
                    (
                        keep_single_id,
                        item_id,
                        single_lot_code,
                        single_lot_lookup,
                        normalized_single_quantity,
                        normalized_single_quantity,
                        timestamp,
                        timestamp,
                    ),
                )
            else:
                connection.execute(
                    """
                    UPDATE tenant_inventory_stock_lots
                    SET lot_code = ?, lot_lookup = ?, quantity = ?, units_per_pack = NULL, equivalent_units = ?, updated_at = ?
                    WHERE id = ?
                    """,
                    (
                        single_lot_code,
                        single_lot_lookup,
                        normalized_single_quantity,
                        normalized_single_quantity,
                        timestamp,
                        keep_single_id,
                    ),
                )
        elif keep_single_row is not None:
            connection.execute("DELETE FROM tenant_inventory_stock_lots WHERE id = ?", (keep_single_id,))

        connection.execute(
            """
            UPDATE tenant_inventory_stock_items
            SET total_equivalent_units = ?, updated_at = ?
            WHERE id = ?
            """,
            (total_equivalent_units, timestamp, item_id),
        )
        return True

    def _normalize_inventory_warehouse_stock_lots(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
        *,
        timestamp: str,
    ) -> bool:
        item_rows = connection.execute(
            "SELECT id FROM tenant_inventory_stock_items WHERE warehouse_id = ?",
            (warehouse_id,),
        ).fetchall()
        changed = False
        for row in item_rows:
            changed = self._normalize_inventory_stock_item_lots(connection, str(row["id"]), timestamp=timestamp) or changed
        return changed

    def _normalize_inventory_date(self, value: str | None) -> str:
        raw_value = (value or "").strip()
        if not raw_value:
            return _utcnow().date().isoformat()
        try:
            return date.fromisoformat(raw_value).isoformat()
        except ValueError as exc:
            raise ValueError("Data inventario non valida. Usa il formato YYYY-MM-DD.") from exc

    def _normalize_inventory_snapshot_date(self, value: str | None) -> str:
        return self._normalize_inventory_date(value)

    def _normalize_inventory_movement_timestamp(self, value: str | None) -> str:
        raw_value = (value or "").strip()
        if not raw_value:
            return _iso_now()

        normalized_raw_value = raw_value[:-1] + "+00:00" if raw_value.endswith("Z") else raw_value
        try:
            parsed = datetime.fromisoformat(normalized_raw_value)
        except ValueError as exc:
            raise ValueError("Data consumo non valida. Usa un formato data/ora leggibile.") from exc

        if parsed.tzinfo is None:
            return parsed.replace(microsecond=0).isoformat()
        return parsed.astimezone(timezone.utc).replace(microsecond=0).isoformat()

    def _parse_inventory_datetime(self, value: object | None) -> datetime | None:
        raw_value = str(value or "").strip()
        if not raw_value:
            return None

        candidates = [raw_value]
        if raw_value.endswith("Z"):
            candidates.append(f"{raw_value[:-1]}+00:00")

        for candidate in candidates:
            try:
                parsed = datetime.fromisoformat(candidate)
            except ValueError:
                continue
            if parsed.tzinfo is None:
                parsed = parsed.replace(tzinfo=timezone.utc)
            return parsed.astimezone(timezone.utc)
        return None

    def _serialize_inventory_movement_row(self, row: sqlite3.Row) -> dict[str, object]:
        warehouse_name = None
        try:
            warehouse_name = row["warehouse_name"]
        except (KeyError, IndexError):
            warehouse_name = None

        return {
            "id": str(row["id"]),
            "warehouse_id": str(row["warehouse_id"] or ""),
            "warehouse_name": str(warehouse_name or ""),
            "product_id": int(row["product_id"]) if row["product_id"] is not None else None,
            "product_name": str(row["product_name"] or ""),
            "supplier_name": str(row["supplier_name"] or ""),
            "lot_code": str(row["lot_code"] or ""),
            "quantity": float(row["quantity"] or 0),
            "units_per_pack": float(row["units_per_pack"]) if row["units_per_pack"] is not None else None,
            "equivalent_units": float(row["equivalent_units"] or 0),
            "movement_kind": str(row["movement_kind"] or ""),
            "source_type": str(row["source_type"] or ""),
            "source_label": str(row["source_label"] or ""),
            "occurred_at": str(row["occurred_at"] or ""),
            "created_at": str(row["created_at"] or ""),
            "updated_at": str(row["updated_at"] or ""),
        }

    def _inventory_consumption_stat_id(self, product_lookup: str, supplier_lookup: str) -> str:
        digest = hashlib.sha1(f"{product_lookup}\0{supplier_lookup}".encode("utf-8")).hexdigest()
        return f"inventory_consumption_stat_{digest}"

    def _serialize_inventory_consumption_product_stat_row(self, row: sqlite3.Row) -> dict[str, object]:
        return {
            "id": str(row["id"]),
            "product_id": int(row["product_id"]) if row["product_id"] is not None else None,
            "product_name": str(row["product_name"] or ""),
            "supplier_name": str(row["supplier_name"] or ""),
            "total_consumed_units": float(row["total_consumed_units"] or 0),
            "workdays_count": int(row["workdays_count"] or 0),
            "consumed_days_count": int(row["consumed_days_count"] or 0),
            "average_daily_consumed_units": float(row["average_daily_consumed_units"] or 0),
            "average_consumed_units_on_consumption_days": float(row["average_consumed_units_on_consumption_days"] or 0),
            "movement_count": int(row["movement_count"] or 0),
            "first_consumption_date": str(row["first_consumption_date"] or "") or None,
            "last_consumption_date": str(row["last_consumption_date"] or "") or None,
            "calculation_source": str(row["calculation_source"] or "manual_consumption_movements"),
            "created_at": str(row["created_at"] or ""),
            "updated_at": str(row["updated_at"] or ""),
        }

    def _read_inventory_consumption_product_stat_row(
        self,
        connection: sqlite3.Connection,
        *,
        product_lookup: str,
        supplier_lookup: str,
    ) -> sqlite3.Row | None:
        return connection.execute(
            """
            SELECT *
            FROM tenant_inventory_consumption_product_stats
            WHERE product_lookup = ? AND supplier_lookup = ?
            LIMIT 1
            """,
            (product_lookup, supplier_lookup),
        ).fetchone()

    def _refresh_inventory_consumption_product_stats(
        self,
        connection: sqlite3.Connection,
        *,
        timestamp: str | None = None,
    ) -> None:
        updated_at = timestamp or _iso_now()
        self._refresh_all_inventory_estimated_consumptions(connection, timestamp=updated_at)
        period_rows = connection.execute(
            """
            SELECT DISTINCT
                product_lookup,
                supplier_lookup,
                period_start_date,
                period_end_date
            FROM tenant_inventory_estimated_consumptions
            WHERE consumed_units > 0
              AND period_start_date <> ''
              AND period_end_date <> ''
            """
        ).fetchall()
        settings = self._read_homemade_stock_settings(connection)
        periods_by_product: dict[tuple[str, str], list[tuple[str, str]]] = {}
        all_periods: list[tuple[str, str]] = []
        for period_row in period_rows:
            period = (str(period_row["period_start_date"] or ""), str(period_row["period_end_date"] or ""))
            key = (str(period_row["product_lookup"] or ""), str(period_row["supplier_lookup"] or ""))
            periods_by_product.setdefault(key, []).append(period)
            all_periods.append(period)
        workdays_count = len(
            self._inventory_bar_operational_days_for_periods(
                connection,
                all_periods,
                settings=settings,
            )
        )

        connection.execute("DELETE FROM tenant_inventory_consumption_product_stats")
        if workdays_count <= 0:
            return

        rows = connection.execute(
            """
            SELECT
                MIN(product_id) AS product_id,
                product_lookup,
                supplier_lookup,
                MAX(product_name) AS product_name,
                MAX(supplier_name) AS supplier_name,
                ROUND(SUM(consumed_units), 6) AS total_consumed_units,
                COUNT(DISTINCT consumption_date) AS consumed_days_count,
                COUNT(*) AS movement_count,
                MIN(consumption_date) AS first_consumption_date,
                MAX(consumption_date) AS last_consumption_date
            FROM tenant_inventory_estimated_consumptions
            WHERE consumed_units > 0
              AND product_lookup <> ''
              AND supplier_lookup <> ''
              AND consumption_date <> ''
            GROUP BY product_lookup, supplier_lookup
            """
        ).fetchall()

        for row in rows:
            product_lookup = str(row["product_lookup"] or "").strip()
            supplier_lookup = str(row["supplier_lookup"] or "").strip()
            if not product_lookup or not supplier_lookup:
                continue
            total_consumed_units = round(float(row["total_consumed_units"] or 0), 6)
            if total_consumed_units <= 0:
                continue
            consumed_days_count = max(int(row["consumed_days_count"] or 0), 1)
            product_workdays_count = len(
                self._inventory_bar_operational_days_for_periods(
                    connection,
                    periods_by_product.get((product_lookup, supplier_lookup), []),
                    settings=settings,
                )
            )
            average_daily = (
                round(total_consumed_units / float(product_workdays_count), 6)
                if product_workdays_count > 0
                else 0.0
            )
            average_on_consumption_days = round(total_consumed_units / float(consumed_days_count), 6)
            connection.execute(
                """
                INSERT INTO tenant_inventory_consumption_product_stats (
                    id,
                    product_id,
                    product_lookup,
                    supplier_lookup,
                    product_name,
                    supplier_name,
                    total_consumed_units,
                    workdays_count,
                    consumed_days_count,
                    average_daily_consumed_units,
                    average_consumed_units_on_consumption_days,
                    movement_count,
                    first_consumption_date,
                    last_consumption_date,
                    calculation_source,
                    created_at,
                    updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    self._inventory_consumption_stat_id(product_lookup, supplier_lookup),
                    int(row["product_id"]) if row["product_id"] is not None else None,
                    product_lookup,
                    supplier_lookup,
                    str(row["product_name"] or product_lookup),
                    str(row["supplier_name"] or supplier_lookup),
                    total_consumed_units,
                    product_workdays_count,
                    consumed_days_count,
                    average_daily,
                    average_on_consumption_days,
                    int(row["movement_count"] or 0),
                    str(row["first_consumption_date"] or "") or None,
                    str(row["last_consumption_date"] or "") or None,
                    "estimated_from_inventory_confirmed_orders_and_bar_calendar",
                    updated_at,
                    updated_at,
                ),
            )

    def _read_inventory_warehouse_row(self, connection: sqlite3.Connection, warehouse_id: str) -> sqlite3.Row:
        row = connection.execute(
            """
            SELECT
                warehouses.*,
                COUNT(items.id) AS product_count,
                COALESCE(SUM(items.total_equivalent_units), 0) AS total_equivalent_units
            FROM tenant_inventory_warehouses AS warehouses
            LEFT JOIN tenant_inventory_stock_items AS items
                ON items.warehouse_id = warehouses.id
            WHERE warehouses.id = ?
            GROUP BY warehouses.id
            LIMIT 1
            """,
            (warehouse_id,),
        ).fetchone()
        if row is None:
            raise KeyError("Magazzino non trovato.")
        return row

    def _read_homemade_stock_warehouse_row(self, connection: sqlite3.Connection, warehouse_id: str) -> sqlite3.Row:
        row = connection.execute(
            """
            SELECT
                warehouses.*,
                (
                    SELECT COUNT(*)
                    FROM tenant_homemade_recipes
                ) AS recipe_count,
                (
                    SELECT COALESCE(SUM(items.quantity), 0)
                    FROM tenant_homemade_stock_items AS items
                    WHERE items.warehouse_id = warehouses.id
                ) AS total_quantity
            FROM tenant_homemade_stock_warehouses AS warehouses
            WHERE warehouses.id = ?
            LIMIT 1
            """,
            (warehouse_id,),
        ).fetchone()
        if row is None:
            raise KeyError("Magazzino/frigo stock non trovato.")
        return row

    def _read_inventory_snapshot_row(self, connection: sqlite3.Connection, snapshot_id: str) -> sqlite3.Row:
        row = connection.execute(
            "SELECT * FROM tenant_inventory_snapshots WHERE id = ? LIMIT 1",
            (snapshot_id,),
        ).fetchone()
        if row is None:
            raise KeyError("Fotografia inventario non trovata.")
        return row

    def _read_inventory_session_row(self, connection: sqlite3.Connection, session_id: str) -> sqlite3.Row:
        row = connection.execute(
            "SELECT * FROM tenant_inventory_sessions WHERE id = ? LIMIT 1",
            (session_id,),
        ).fetchone()
        if row is None:
            raise KeyError("Inventario magazzino non trovato.")
        return row

    def _read_homemade_stock_session_row(self, connection: sqlite3.Connection, session_id: str) -> sqlite3.Row:
        row = connection.execute(
            "SELECT * FROM tenant_homemade_stock_sessions WHERE id = ? LIMIT 1",
            (session_id,),
        ).fetchone()
        if row is None:
            raise KeyError("Stock salvato non trovato.")
        return row

    def _read_inventory_latest_session_row(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
    ) -> sqlite3.Row | None:
        return connection.execute(
            """
            SELECT *
            FROM tenant_inventory_sessions
            WHERE warehouse_id = ?
            ORDER BY inventory_date DESC, created_at DESC
            LIMIT 1
            """,
            (warehouse_id,),
        ).fetchone()

    def _read_homemade_stock_latest_session_row(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
    ) -> sqlite3.Row | None:
        return connection.execute(
            """
            SELECT *
            FROM tenant_homemade_stock_sessions
            WHERE warehouse_id = ?
            ORDER BY inventory_date DESC, created_at DESC
            LIMIT 1
            """,
            (warehouse_id,),
        ).fetchone()

    def _load_inventory_stock_items(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
    ) -> tuple[list[sqlite3.Row], dict[str, list[dict[str, object]]]]:
        item_rows = connection.execute(
            """
            SELECT *
            FROM tenant_inventory_stock_items
            WHERE warehouse_id = ?
            ORDER BY lower(supplier_name) ASC, lower(product_name) ASC
            """,
            (warehouse_id,),
        ).fetchall()

        item_ids = [str(row["id"]) for row in item_rows]
        catalog_units_per_pack_by_item = self._inventory_catalog_units_per_pack_by_item_rows(connection, item_rows)
        lots_by_item: dict[str, list[dict[str, object]]] = {item_id: [] for item_id in item_ids}
        if item_ids:
            placeholders = ", ".join("?" for _ in item_ids)
            lot_rows = connection.execute(
                f"""
                SELECT *
                FROM tenant_inventory_stock_lots
                WHERE item_id IN ({placeholders})
                ORDER BY lower(lot_code) ASC, created_at ASC
                """,
                tuple(item_ids),
            ).fetchall()
            for row in lot_rows:
                item_id = str(row["item_id"])
                lots_by_item.setdefault(item_id, []).append(
                    {
                        "id": str(row["id"]),
                        "lot_code": str(row["lot_code"]),
                        "quantity": float(row["quantity"] or 0),
                        "units_per_pack": float(row["units_per_pack"]) if row["units_per_pack"] is not None else None,
                        "catalog_units_per_pack": catalog_units_per_pack_by_item.get(item_id),
                        "equivalent_units": float(row["equivalent_units"] or 0),
                        "created_at": str(row["created_at"]),
                        "updated_at": str(row["updated_at"]),
                    }
                )
        return item_rows, lots_by_item

    def _load_homemade_stock_items(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
    ) -> list[sqlite3.Row]:
        return connection.execute(
            """
            SELECT
                COALESCE(items.id, 'homemade_stock_virtual_' || recipes.id) AS id,
                ? AS warehouse_id,
                recipes.id AS recipe_id,
                recipes.name AS recipe_name,
                recipes.name_lookup AS recipe_lookup,
                COALESCE(recipes.usage_scope, 'both') AS usage_scope,
                ? AS measurement_unit,
                COALESCE(items.quantity, 0) AS quantity,
                COALESCE(items.created_at, recipes.created_at) AS created_at,
                COALESCE(items.updated_at, recipes.updated_at) AS updated_at
            FROM tenant_homemade_recipes AS recipes
            LEFT JOIN tenant_homemade_stock_items AS items
                ON items.warehouse_id = ?
                AND (items.recipe_id = recipes.id OR items.recipe_lookup = recipes.name_lookup)
            ORDER BY lower(recipes.name) ASC, recipes.created_at ASC
            """,
            (warehouse_id, HOMEMADE_STOCK_UNIT, warehouse_id),
        ).fetchall()

    def _load_inventory_session_items(
        self,
        connection: sqlite3.Connection,
        session_id: str,
    ) -> tuple[list[sqlite3.Row], dict[str, list[dict[str, object]]]]:
        item_rows = connection.execute(
            """
            SELECT *
            FROM tenant_inventory_session_items
            WHERE session_id = ?
            ORDER BY lower(supplier_name) ASC, lower(product_name) ASC
            """,
            (session_id,),
        ).fetchall()

        item_ids = [str(row["id"]) for row in item_rows]
        catalog_units_per_pack_by_item = self._inventory_catalog_units_per_pack_by_item_rows(connection, item_rows)
        lots_by_item: dict[str, list[dict[str, object]]] = {item_id: [] for item_id in item_ids}
        if item_ids:
            placeholders = ", ".join("?" for _ in item_ids)
            lot_rows = connection.execute(
                f"""
                SELECT *
                FROM tenant_inventory_session_lots
                WHERE session_item_id IN ({placeholders})
                ORDER BY lower(lot_code) ASC, created_at ASC
                """,
                tuple(item_ids),
            ).fetchall()
            for row in lot_rows:
                item_id = str(row["session_item_id"])
                lots_by_item.setdefault(item_id, []).append(
                    {
                        "id": str(row["id"]),
                        "lot_code": str(row["lot_code"]),
                        "quantity": float(row["quantity"] or 0),
                        "units_per_pack": float(row["units_per_pack"]) if row["units_per_pack"] is not None else None,
                        "catalog_units_per_pack": catalog_units_per_pack_by_item.get(item_id),
                        "equivalent_units": float(row["equivalent_units"] or 0),
                        "created_at": str(row["created_at"]),
                        "updated_at": str(row["updated_at"]),
                    }
                )
        return item_rows, lots_by_item

    def _load_homemade_stock_session_items(
        self,
        connection: sqlite3.Connection,
        session_id: str,
    ) -> list[sqlite3.Row]:
        return connection.execute(
            """
            SELECT
                session_items.*,
                COALESCE(recipes.usage_scope, 'both') AS usage_scope
            FROM tenant_homemade_stock_session_items AS session_items
            LEFT JOIN tenant_homemade_recipes AS recipes
                ON recipes.id = session_items.recipe_id
            WHERE session_items.session_id = ?
            ORDER BY lower(session_items.recipe_name) ASC, session_items.created_at ASC
            """,
            (session_id,),
        ).fetchall()

    def _aggregate_inventory_snapshot_items(
        self,
        connection: sqlite3.Connection,
        snapshot_id: str,
    ) -> dict[tuple[str, str], dict[str, object]]:
        rows = connection.execute(
            """
            SELECT
                product_lookup,
                supplier_lookup,
                MIN(product_name) AS product_name,
                MIN(supplier_name) AS supplier_name,
                COALESCE(SUM(total_equivalent_units), 0) AS total_equivalent_units
            FROM tenant_inventory_snapshot_items
            WHERE snapshot_id = ?
            GROUP BY product_lookup, supplier_lookup
            """,
            (snapshot_id,),
        ).fetchall()

        aggregated: dict[tuple[str, str], dict[str, object]] = {}
        for row in rows:
            key = (str(row["product_lookup"]), str(row["supplier_lookup"]))
            aggregated[key] = {
                "product_name": str(row["product_name"] or ""),
                "supplier_name": str(row["supplier_name"] or ""),
                "total_equivalent_units": float(row["total_equivalent_units"] or 0),
            }
        return aggregated

    def _aggregate_inventory_session_items(
        self,
        connection: sqlite3.Connection,
        session_id: str,
    ) -> dict[tuple[str, str], dict[str, object]]:
        rows = connection.execute(
            """
            SELECT
                product_lookup,
                supplier_lookup,
                MIN(product_name) AS product_name,
                MIN(supplier_name) AS supplier_name,
                COALESCE(SUM(total_equivalent_units), 0) AS total_equivalent_units
            FROM tenant_inventory_session_items
            WHERE session_id = ?
            GROUP BY product_lookup, supplier_lookup
            """,
            (session_id,),
        ).fetchall()

        aggregated: dict[tuple[str, str], dict[str, object]] = {}
        for row in rows:
            key = (str(row["product_lookup"]), str(row["supplier_lookup"]))
            aggregated[key] = {
                "product_name": str(row["product_name"] or ""),
                "supplier_name": str(row["supplier_name"] or ""),
                "total_equivalent_units": float(row["total_equivalent_units"] or 0),
            }
        return aggregated

    def _inventory_bar_operational_days_for_periods(
        self,
        connection: sqlite3.Connection,
        periods: list[tuple[str, str]],
        *,
        year: int | None = None,
        settings: dict[str, object] | None = None,
    ) -> set[str]:
        active_settings = settings or self._read_homemade_stock_settings(connection)
        bar_rules = self._homemade_calendar_rules_for_scope(active_settings, "bar")
        completed_days_end = _local_now().date() - timedelta(days=1)
        allowed_start = date(year, 1, 1) if year is not None else None
        allowed_end = date(year, 12, 31) if year is not None else None
        operational_days: set[str] = set()

        for raw_start_date, raw_end_date in periods:
            try:
                current = date.fromisoformat(raw_start_date) + timedelta(days=1)
                last = min(date.fromisoformat(raw_end_date), completed_days_end)
            except ValueError:
                continue
            if allowed_start is not None:
                current = max(current, allowed_start)
            if allowed_end is not None:
                last = min(last, allowed_end)
            while current <= last:
                if not bar_rules or any(self._homemade_calendar_rule_matches(current, rule) for rule in bar_rules):
                    operational_days.add(current.isoformat())
                current += timedelta(days=1)

        return operational_days

    def _build_inventory_warehouse_consumption_items_between_sessions(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
        start_session_row: sqlite3.Row,
        end_session_row: sqlite3.Row,
    ) -> dict[str, object]:
        try:
            start_inventory_date = date.fromisoformat(str(start_session_row["inventory_date"]))
            end_inventory_date = date.fromisoformat(str(end_session_row["inventory_date"]))
        except ValueError as exc:
            raise ValueError("Non riesco a leggere correttamente le date degli inventari magazzino.") from exc

        if start_inventory_date > end_inventory_date:
            start_session_row, end_session_row = end_session_row, start_session_row
            start_inventory_date, end_inventory_date = end_inventory_date, start_inventory_date

        opening_map = self._aggregate_inventory_session_items(connection, str(start_session_row["id"]))
        closing_map = self._aggregate_inventory_session_items(connection, str(end_session_row["id"]))

        period_start_dt = self._parse_inventory_datetime(start_session_row["created_at"]) or datetime.combine(
            start_inventory_date,
            time.min,
            tzinfo=timezone.utc,
        )
        period_end_dt = self._parse_inventory_datetime(end_session_row["created_at"]) or datetime.combine(
            end_inventory_date + timedelta(days=1),
            time.min,
            tzinfo=timezone.utc,
        )
        movement_rows = connection.execute(
            """
            SELECT *
            FROM tenant_inventory_movements
            WHERE warehouse_id = ?
              AND (
                movement_kind = 'in'
                OR (movement_kind = 'out' AND source_type = 'warehouse_transfer_out')
              )
            ORDER BY occurred_at ASC, created_at ASC
            """,
            (warehouse_id,),
        ).fetchall()

        incoming_map: dict[tuple[str, str], dict[str, object]] = {}
        outgoing_transfer_map: dict[tuple[str, str], dict[str, object]] = {}
        for row in movement_rows:
            occurred_at = self._parse_inventory_datetime(row["occurred_at"])
            if occurred_at is None or occurred_at <= period_start_dt or occurred_at > period_end_dt:
                continue
            key = (str(row["product_lookup"] or ""), str(row["supplier_lookup"] or ""))
            target_map = (
                outgoing_transfer_map
                if str(row["source_type"] or "") == "warehouse_transfer_out"
                else incoming_map
            )
            bucket = target_map.setdefault(
                key,
                {
                    "product_name": str(row["product_name"] or ""),
                    "supplier_name": str(row["supplier_name"] or ""),
                    "total_equivalent_units": 0.0,
                },
            )
            bucket["total_equivalent_units"] = float(bucket["total_equivalent_units"] or 0) + float(row["equivalent_units"] or 0)

        item_keys = set(opening_map) | set(closing_map) | set(incoming_map) | set(outgoing_transfer_map)
        items: list[dict[str, object]] = []
        for key in item_keys:
            opening = opening_map.get(key)
            closing = closing_map.get(key)
            incoming = incoming_map.get(key)
            outgoing_transfer = outgoing_transfer_map.get(key)
            product_name = str((closing or opening or incoming or outgoing_transfer or {}).get("product_name") or "")
            supplier_name = str((closing or opening or incoming or outgoing_transfer or {}).get("supplier_name") or "")
            opening_units = float((opening or {}).get("total_equivalent_units") or 0)
            incoming_units = float((incoming or {}).get("total_equivalent_units") or 0)
            outgoing_transfer_units = float((outgoing_transfer or {}).get("total_equivalent_units") or 0)
            closing_units = float((closing or {}).get("total_equivalent_units") or 0)
            consumed_units = opening_units + incoming_units - outgoing_transfer_units - closing_units
            if opening_units == 0 and incoming_units == 0 and outgoing_transfer_units == 0 and closing_units == 0:
                continue
            items.append(
                {
                    "product_lookup": key[0],
                    "supplier_lookup": key[1],
                    "product_name": product_name,
                    "supplier_name": supplier_name,
                    "opening_units": round(opening_units, 6),
                    "incoming_units": round(incoming_units, 6),
                    "outgoing_transfer_units": round(outgoing_transfer_units, 6),
                    "closing_units": round(closing_units, 6),
                    "consumed_units": round(consumed_units, 6),
                }
            )

        items.sort(
            key=lambda item: (
                -float(item["consumed_units"]),
                str(item["supplier_name"]).lower(),
                str(item["product_name"]).lower(),
            )
        )
        return {
            "start_inventory_date": start_inventory_date,
            "end_inventory_date": end_inventory_date,
            "period_days": float(
                len(
                    self._inventory_bar_operational_days_for_periods(
                        connection,
                        [(start_inventory_date.isoformat(), end_inventory_date.isoformat())],
                    )
                )
            ),
            "items": items,
        }

    def _refresh_inventory_daily_consumption_for_session(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
        end_session_id: str,
        *,
        timestamp: str,
    ) -> int:
        end_session_row = self._read_inventory_session_row(connection, end_session_id)
        consumption_date = str(end_session_row["inventory_date"] or "")
        connection.execute(
            """
            DELETE FROM tenant_inventory_daily_consumptions
            WHERE warehouse_id = ? AND consumption_date = ?
            """,
            (warehouse_id, consumption_date),
        )

        start_session_row = connection.execute(
            """
            SELECT *
            FROM tenant_inventory_sessions
            WHERE warehouse_id = ?
              AND inventory_date < ?
            ORDER BY inventory_date DESC, created_at DESC
            LIMIT 1
            """,
            (warehouse_id, consumption_date),
        ).fetchone()
        if start_session_row is None:
            return 0

        warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
        period = self._build_inventory_warehouse_consumption_items_between_sessions(
            connection,
            warehouse_id,
            start_session_row,
            end_session_row,
        )
        items = [item for item in period["items"] if isinstance(item, dict)]
        for item in items:
            connection.execute(
                """
                INSERT INTO tenant_inventory_daily_consumptions (
                    id,
                    warehouse_id,
                    warehouse_name,
                    consumption_date,
                    period_start_date,
                    period_end_date,
                    period_days,
                    start_session_id,
                    end_session_id,
                    product_lookup,
                    supplier_lookup,
                    product_name,
                    supplier_name,
                    opening_units,
                    incoming_units,
                    outgoing_transfer_units,
                    closing_units,
                    consumed_units,
                    created_at,
                    updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    f"inventory_daily_consumption_{uuid.uuid4().hex}",
                    warehouse_id,
                    str(warehouse_row["name"] or ""),
                    consumption_date,
                    period["start_inventory_date"].isoformat(),
                    period["end_inventory_date"].isoformat(),
                    float(period["period_days"]),
                    str(start_session_row["id"]),
                    str(end_session_row["id"]),
                    str(item["product_lookup"] or ""),
                    str(item["supplier_lookup"] or ""),
                    str(item["product_name"] or ""),
                    str(item["supplier_name"] or ""),
                    float(item["opening_units"] or 0),
                    float(item["incoming_units"] or 0),
                    float(item["outgoing_transfer_units"] or 0),
                    float(item["closing_units"] or 0),
                    float(item["consumed_units"] or 0),
                    timestamp,
                    timestamp,
                ),
            )
        return len(items)

    def _refresh_next_inventory_daily_consumption_after_date(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
        inventory_date: str,
        *,
        timestamp: str,
    ) -> int:
        next_session_row = connection.execute(
            """
            SELECT *
            FROM tenant_inventory_sessions
            WHERE warehouse_id = ?
              AND inventory_date > ?
            ORDER BY inventory_date ASC, created_at ASC
            LIMIT 1
            """,
            (warehouse_id, inventory_date),
        ).fetchone()
        if next_session_row is None:
            return 0
        return self._refresh_inventory_daily_consumption_for_session(
            connection,
            warehouse_id,
            str(next_session_row["id"]),
            timestamp=timestamp,
        )

    def _aggregate_inventory_latest_sessions_for_date(
        self,
        connection: sqlite3.Connection,
        inventory_date: str,
    ) -> dict[str, object]:
        warehouse_rows = connection.execute(
            """
            SELECT id
            FROM tenant_inventory_warehouses
            ORDER BY lower(name) ASC, created_at ASC
            """
        ).fetchall()

        aggregated: dict[tuple[str, str], dict[str, object]] = {}
        warehouse_count = 0
        for warehouse_row in warehouse_rows:
            session_row = self._read_inventory_effective_session_row_for_date(
                connection,
                str(warehouse_row["id"]),
                inventory_date,
            )
            if session_row is None:
                continue
            warehouse_count += 1
            rows = connection.execute(
                """
                SELECT
                    MIN(product_id) AS product_id,
                    product_lookup,
                    supplier_lookup,
                    MAX(product_name) AS product_name,
                    MAX(supplier_name) AS supplier_name,
                    ROUND(SUM(total_equivalent_units), 6) AS total_equivalent_units
                FROM tenant_inventory_session_items
                WHERE session_id = ?
                GROUP BY product_lookup, supplier_lookup
                """,
                (str(session_row["id"]),),
            ).fetchall()
            for row in rows:
                product_lookup = str(row["product_lookup"] or "").strip()
                supplier_lookup = str(row["supplier_lookup"] or "").strip()
                if not product_lookup or not supplier_lookup:
                    continue
                key = (product_lookup, supplier_lookup)
                bucket = aggregated.setdefault(
                    key,
                    {
                        "product_id": int(row["product_id"]) if row["product_id"] is not None else None,
                        "product_name": str(row["product_name"] or product_lookup),
                        "supplier_name": str(row["supplier_name"] or supplier_lookup),
                        "total_equivalent_units": 0.0,
                    },
                )
                if bucket.get("product_id") is None and row["product_id"] is not None:
                    bucket["product_id"] = int(row["product_id"])
                if not str(bucket.get("product_name") or "").strip():
                    bucket["product_name"] = str(row["product_name"] or product_lookup)
                if not str(bucket.get("supplier_name") or "").strip():
                    bucket["supplier_name"] = str(row["supplier_name"] or supplier_lookup)
                bucket["total_equivalent_units"] = (
                    float(bucket["total_equivalent_units"] or 0)
                    + float(row["total_equivalent_units"] or 0)
                )

        return {
            "warehouse_count": warehouse_count,
            "items": aggregated,
        }

    def _aggregate_inventory_confirmed_order_units_between_dates(
        self,
        connection: sqlite3.Connection,
        *,
        start_date: str,
        end_date: str,
    ) -> dict[tuple[str, str], dict[str, object]]:
        if not (
            self._tenant_table_exists(connection, "ordini_items")
            and self._tenant_table_exists(connection, "ordini_batches")
        ):
            return {}

        item_columns = self._tenant_table_columns(connection, "ordini_items")
        select_item_product_id = "items.product_id" if "product_id" in item_columns else "NULL"
        select_item_units_per_pack = "items.units_per_pack" if "units_per_pack" in item_columns else "NULL"
        has_products_schema = self._tenant_table_exists(connection, "ordini_products")
        product_columns = self._tenant_table_columns(connection, "ordini_products") if has_products_schema else set()
        select_product_units_per_pack = (
            "products.units_per_pack"
            if has_products_schema and "units_per_pack" in product_columns
            else "NULL"
        )
        products_join = (
            """
            LEFT JOIN ordini_products AS products
                ON products.id = items.product_id
            """
            if has_products_schema
            else ""
        )
        rows = connection.execute(
            f"""
            SELECT
                {select_item_product_id} AS product_id,
                items.product_name,
                items.supplier_name,
                items.lot_code,
                items.quantity,
                COALESCE({select_item_units_per_pack}, {select_product_units_per_pack}) AS resolved_units_per_pack
            FROM ordini_items AS items
            JOIN ordini_batches AS batches
                ON batches.id = items.batch_id
            {products_join}
            WHERE substr(COALESCE(batches.confirmed_at, ''), 1, 10) > ?
              AND substr(COALESCE(batches.confirmed_at, ''), 1, 10) <= ?
            ORDER BY batches.confirmed_at ASC, items.id ASC
            """,
            (start_date, end_date),
        ).fetchall()

        aggregated: dict[tuple[str, str], dict[str, object]] = {}
        for row in rows:
            product_name = str(row["product_name"] or "").strip()
            supplier_name = str(row["supplier_name"] or "").strip()
            product_lookup = _normalize_lookup(product_name)
            supplier_lookup = _normalize_lookup(supplier_name)
            if not product_lookup or not supplier_lookup:
                continue
            quantity = float(row["quantity"] or 0)
            if quantity <= 0:
                continue
            units_per_pack = float(row["resolved_units_per_pack"]) if row["resolved_units_per_pack"] is not None else None
            try:
                incoming_units = self._inventory_effective_units(
                    lot_code=str(row["lot_code"] or ""),
                    quantity=quantity,
                    units_per_pack=units_per_pack,
                )
            except ValueError:
                incoming_units = quantity
            if incoming_units <= 0:
                continue
            key = (product_lookup, supplier_lookup)
            bucket = aggregated.setdefault(
                key,
                {
                    "product_id": int(row["product_id"]) if row["product_id"] is not None else None,
                    "product_name": product_name,
                    "supplier_name": supplier_name,
                    "total_equivalent_units": 0.0,
                },
            )
            if bucket.get("product_id") is None and row["product_id"] is not None:
                bucket["product_id"] = int(row["product_id"])
            bucket["total_equivalent_units"] = (
                float(bucket["total_equivalent_units"] or 0) + incoming_units
            )

        return aggregated

    def _refresh_inventory_estimated_consumption_for_date(
        self,
        connection: sqlite3.Connection,
        inventory_date: str,
        *,
        timestamp: str,
    ) -> int:
        normalized_inventory_date = self._normalize_inventory_date(inventory_date)
        connection.execute(
            """
            DELETE FROM tenant_inventory_estimated_consumptions
            WHERE consumption_date = ?
            """,
            (normalized_inventory_date,),
        )

        has_followup_inventory_row = connection.execute(
            """
            SELECT 1
            FROM tenant_inventory_sessions AS current_sessions
            WHERE current_sessions.inventory_date = ?
              AND EXISTS (
                SELECT 1
                FROM tenant_inventory_sessions AS previous_sessions
                WHERE previous_sessions.warehouse_id = current_sessions.warehouse_id
                  AND previous_sessions.inventory_date < current_sessions.inventory_date
              )
            LIMIT 1
            """,
            (normalized_inventory_date,),
        ).fetchone()
        if has_followup_inventory_row is None:
            return 0

        previous_date_row = connection.execute(
            """
            SELECT MAX(inventory_date) AS inventory_date
            FROM tenant_inventory_sessions
            WHERE inventory_date < ?
            """,
            (normalized_inventory_date,),
        ).fetchone()
        previous_inventory_date = str(previous_date_row["inventory_date"] or "") if previous_date_row is not None else ""
        if not previous_inventory_date:
            return 0

        try:
            start_inventory_date = date.fromisoformat(previous_inventory_date)
            end_inventory_date = date.fromisoformat(normalized_inventory_date)
        except ValueError as exc:
            raise ValueError("Non riesco a leggere correttamente le date degli inventari.") from exc

        opening_period = self._aggregate_inventory_latest_sessions_for_date(
            connection,
            previous_inventory_date,
        )
        closing_period = self._aggregate_inventory_latest_sessions_for_date(
            connection,
            normalized_inventory_date,
        )
        incoming_map = self._aggregate_inventory_confirmed_order_units_between_dates(
            connection,
            start_date=previous_inventory_date,
            end_date=normalized_inventory_date,
        )

        opening_map = opening_period["items"] if isinstance(opening_period["items"], dict) else {}
        closing_map = closing_period["items"] if isinstance(closing_period["items"], dict) else {}
        item_keys = set(opening_map) | set(closing_map) | set(incoming_map)
        warehouse_count = max(
            int(opening_period["warehouse_count"] or 0),
            int(closing_period["warehouse_count"] or 0),
        )
        period_days = float(
            len(
                self._inventory_bar_operational_days_for_periods(
                    connection,
                    [(previous_inventory_date, normalized_inventory_date)],
                )
            )
        )
        written_count = 0
        for key in item_keys:
            opening = opening_map.get(key) if isinstance(opening_map, dict) else None
            closing = closing_map.get(key) if isinstance(closing_map, dict) else None
            incoming = incoming_map.get(key)
            source = closing or opening or incoming or {}
            product_lookup, supplier_lookup = key
            product_name = str(source.get("product_name") or product_lookup)
            supplier_name = str(source.get("supplier_name") or supplier_lookup)
            product_id = source.get("product_id")
            opening_units = float((opening or {}).get("total_equivalent_units") or 0)
            incoming_units = float((incoming or {}).get("total_equivalent_units") or 0)
            closing_units = float((closing or {}).get("total_equivalent_units") or 0)
            consumed_units = opening_units + incoming_units - closing_units
            if opening_units == 0 and incoming_units == 0 and closing_units == 0:
                continue
            connection.execute(
                """
                INSERT INTO tenant_inventory_estimated_consumptions (
                    id,
                    consumption_date,
                    period_start_date,
                    period_end_date,
                    period_days,
                    product_id,
                    product_lookup,
                    supplier_lookup,
                    product_name,
                    supplier_name,
                    warehouse_count,
                    opening_units,
                    incoming_units,
                    closing_units,
                    consumed_units,
                    created_at,
                    updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    f"inventory_estimated_consumption_{uuid.uuid4().hex}",
                    normalized_inventory_date,
                    previous_inventory_date,
                    normalized_inventory_date,
                    period_days,
                    int(product_id) if product_id is not None else None,
                    str(product_lookup),
                    str(supplier_lookup),
                    product_name,
                    supplier_name,
                    warehouse_count,
                    round(opening_units, 6),
                    round(incoming_units, 6),
                    round(closing_units, 6),
                    round(consumed_units, 6),
                    timestamp,
                    timestamp,
                ),
            )
            written_count += 1
        return written_count

    def _refresh_next_inventory_estimated_consumption_after_date(
        self,
        connection: sqlite3.Connection,
        inventory_date: str,
        *,
        timestamp: str,
    ) -> int:
        next_date_row = connection.execute(
            """
            SELECT inventory_date
            FROM tenant_inventory_sessions
            WHERE inventory_date > ?
            GROUP BY inventory_date
            ORDER BY inventory_date ASC
            LIMIT 1
            """,
            (inventory_date,),
        ).fetchone()
        if next_date_row is None:
            return 0
        return self._refresh_inventory_estimated_consumption_for_date(
            connection,
            str(next_date_row["inventory_date"] or ""),
            timestamp=timestamp,
        )

    def _refresh_all_inventory_estimated_consumptions(
        self,
        connection: sqlite3.Connection,
        *,
        timestamp: str | None = None,
    ) -> None:
        updated_at = timestamp or _iso_now()
        date_rows = connection.execute(
            """
            SELECT inventory_date
            FROM tenant_inventory_sessions
            GROUP BY inventory_date
            ORDER BY inventory_date ASC
            """
        ).fetchall()
        for row in date_rows:
            self._refresh_inventory_estimated_consumption_for_date(
                connection,
                str(row["inventory_date"] or ""),
                timestamp=updated_at,
            )
        self._refresh_inventory_daily_consumption_period_days(connection)

    def _refresh_inventory_daily_consumption_period_days(self, connection: sqlite3.Connection) -> None:
        period_rows = connection.execute(
            """
            SELECT DISTINCT period_start_date, period_end_date
            FROM tenant_inventory_daily_consumptions
            WHERE period_start_date <> ''
              AND period_end_date <> ''
            """
        ).fetchall()
        settings = self._read_homemade_stock_settings(connection)
        for row in period_rows:
            period_start_date = str(row["period_start_date"] or "")
            period_end_date = str(row["period_end_date"] or "")
            period_days = float(
                len(
                    self._inventory_bar_operational_days_for_periods(
                        connection,
                        [(period_start_date, period_end_date)],
                        settings=settings,
                    )
                )
            )
            connection.execute(
                """
                UPDATE tenant_inventory_daily_consumptions
                SET period_days = ?
                WHERE period_start_date = ?
                  AND period_end_date = ?
                """,
                (period_days, period_start_date, period_end_date),
            )

    def _read_inventory_effective_session_row_for_date(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
        inventory_date: str,
    ) -> sqlite3.Row | None:
        return connection.execute(
            """
            SELECT *
            FROM tenant_inventory_sessions
            WHERE warehouse_id = ? AND inventory_date <= ?
            ORDER BY inventory_date DESC, created_at DESC
            LIMIT 1
            """,
            (warehouse_id, inventory_date),
        ).fetchone()

    def _build_inventory_totals_detail_for_date(
        self,
        connection: sqlite3.Connection,
        *,
        inventory_date: str,
    ) -> dict[str, object]:
        warehouse_rows = connection.execute(
            """
            SELECT *
            FROM tenant_inventory_warehouses
            ORDER BY lower(name) ASC, created_at ASC
            """
        ).fetchall()

        source_sessions: list[dict[str, object]] = []
        aggregated_items: dict[tuple[str, str], dict[str, object]] = {}

        for warehouse_row in warehouse_rows:
            warehouse_id = str(warehouse_row["id"] or "")
            session_row = self._read_inventory_effective_session_row_for_date(connection, warehouse_id, inventory_date)
            if session_row is None:
                continue

            serialized_session = self._serialize_inventory_session_row(session_row)
            source_sessions.append(serialized_session)
            session_item_rows, session_lots_by_item = self._load_inventory_session_items(connection, str(session_row["id"]))

            for item_row in session_item_rows:
                item_id = str(item_row["id"])
                item_key = (
                    str(item_row["product_lookup"] or ""),
                    str(item_row["supplier_lookup"] or ""),
                )
                aggregated_item = aggregated_items.get(item_key)
                if aggregated_item is None:
                    aggregated_item = {
                        "id": f"inventory_total_item_{uuid.uuid4().hex}",
                        "product_id": int(item_row["product_id"]) if item_row["product_id"] is not None else None,
                        "product_name": str(item_row["product_name"] or ""),
                        "supplier_name": str(item_row["supplier_name"] or ""),
                        "total_equivalent_units": 0.0,
                        "lots_map": {},
                        "created_at": str(session_row["created_at"] or ""),
                        "updated_at": str(session_row["updated_at"] or ""),
                    }
                    aggregated_items[item_key] = aggregated_item

                aggregated_item["total_equivalent_units"] = round(
                    float(aggregated_item["total_equivalent_units"]) + float(item_row["total_equivalent_units"] or 0),
                    2,
                )

                lots_map = aggregated_item["lots_map"]
                for lot_row in session_lots_by_item.get(item_id, []):
                    lot_code = str(lot_row["lot_code"] or "")
                    lot_lookup = _normalize_lookup(lot_code)
                    aggregated_lot = lots_map.get(lot_lookup)
                    if aggregated_lot is None:
                        aggregated_lot = {
                            "id": f"inventory_total_lot_{uuid.uuid4().hex}",
                            "lot_code": lot_code,
                            "quantity": 0.0,
                            "units_per_pack": float(lot_row["units_per_pack"]) if lot_row["units_per_pack"] is not None else None,
                            "catalog_units_per_pack": lot_row.get("catalog_units_per_pack"),
                            "equivalent_units": 0.0,
                            "created_at": str(lot_row["created_at"] or session_row["created_at"] or ""),
                            "updated_at": str(lot_row["updated_at"] or session_row["updated_at"] or ""),
                        }
                        lots_map[lot_lookup] = aggregated_lot

                    aggregated_lot["quantity"] = round(
                        float(aggregated_lot["quantity"]) + float(lot_row["quantity"] or 0),
                        2,
                    )
                    aggregated_lot["equivalent_units"] = round(
                        float(aggregated_lot["equivalent_units"]) + float(lot_row["equivalent_units"] or 0),
                        2,
                    )
                    if aggregated_lot["units_per_pack"] is None and lot_row["units_per_pack"] is not None:
                        aggregated_lot["units_per_pack"] = float(lot_row["units_per_pack"])
                    if aggregated_lot.get("catalog_units_per_pack") is None and lot_row.get("catalog_units_per_pack") is not None:
                        aggregated_lot["catalog_units_per_pack"] = lot_row.get("catalog_units_per_pack")

        items: list[dict[str, object]] = []
        for aggregated_item in aggregated_items.values():
            lots = list(aggregated_item.pop("lots_map").values())
            lots.sort(key=lambda lot: str(lot["lot_code"]).lower())
            items.append(
                {
                    "id": str(aggregated_item["id"]),
                    "product_id": aggregated_item["product_id"],
                    "product_name": str(aggregated_item["product_name"]),
                    "supplier_name": str(aggregated_item["supplier_name"]),
                    "total_equivalent_units": float(aggregated_item["total_equivalent_units"]),
                    "lot_count": len(lots),
                    "lots": lots,
                    "created_at": str(aggregated_item["created_at"]),
                    "updated_at": str(aggregated_item["updated_at"]),
                }
            )

        items.sort(key=lambda item: (str(item["supplier_name"]).lower(), str(item["product_name"]).lower()))
        source_sessions.sort(key=lambda item: (str(item["warehouse_name"]).lower(), str(item["warehouse_id"])))

        return {
            "inventory_date": inventory_date,
            "label": f"Totale locale al {inventory_date}",
            "items": items,
            "total_products": len(items),
            "total_equivalent_units": round(sum(float(item["total_equivalent_units"]) for item in items), 2),
            "total_warehouses": len(warehouse_rows),
            "covered_warehouses": len(source_sessions),
            "source_sessions": source_sessions,
        }

    def list_inventory_totals_history(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        with self._connect_tenant_database(session.database_path) as connection:
            date_rows = connection.execute(
                """
                SELECT DISTINCT inventory_date
                FROM tenant_inventory_sessions
                ORDER BY inventory_date DESC
                """
            ).fetchall()

            entries: list[dict[str, object]] = []
            for date_row in date_rows:
                inventory_date = str(date_row["inventory_date"] or "")
                if not inventory_date:
                    continue
                detail = self._build_inventory_totals_detail_for_date(connection, inventory_date=inventory_date)
                entries.append(
                    {
                        "inventory_date": inventory_date,
                        "label": str(detail["label"]),
                        "total_warehouses": int(detail["total_warehouses"]),
                        "covered_warehouses": int(detail["covered_warehouses"]),
                        "total_equivalent_units": float(detail["total_equivalent_units"]),
                        "source_sessions": detail["source_sessions"],
                    }
                )

        return {"entries": entries, "total_count": len(entries)}

    def get_inventory_totals_detail(
        self,
        session: SessionIdentity,
        *,
        inventory_date: str,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        normalized_date = self._normalize_inventory_date(inventory_date)
        with self._connect_tenant_database(session.database_path) as connection:
            existing_row = connection.execute(
                """
                SELECT 1
                FROM tenant_inventory_sessions
                WHERE inventory_date = ?
                LIMIT 1
                """,
                (normalized_date,),
            ).fetchone()
            if existing_row is None:
                raise KeyError("Storico totale non trovato per questa data.")
            return self._build_inventory_totals_detail_for_date(connection, inventory_date=normalized_date)

    def _sum_inventory_movements_since(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
        *,
        start_value: object | None,
    ) -> float:
        if start_value is None:
            return 0.0
        start_dt = self._parse_inventory_datetime(start_value)
        if start_dt is None:
            raw_value = str(start_value or "").strip()
            if not raw_value:
                return 0.0
            start_dt = datetime.combine(date.fromisoformat(raw_value), time.min, tzinfo=timezone.utc)
        rows = connection.execute(
            """
            SELECT occurred_at, equivalent_units
            FROM tenant_inventory_movements
            WHERE warehouse_id = ? AND movement_kind = 'in'
            ORDER BY occurred_at ASC, created_at ASC
            """,
            (warehouse_id,),
        ).fetchall()

        total = 0.0
        for row in rows:
            occurred_at = self._parse_inventory_datetime(row["occurred_at"])
            if occurred_at is None or occurred_at < start_dt:
                continue
            total += float(row["equivalent_units"] or 0)
        return round(total, 2)

    def _build_inventory_item_comparison(
        self,
        *,
        current_items: list[dict[str, object]],
        real_items: list[dict[str, object]],
    ) -> list[dict[str, object]]:
        current_map = {
            (_normalize_lookup(str(item["product_name"])), _normalize_lookup(str(item["supplier_name"]))): item
            for item in current_items
        }
        real_map = {
            (_normalize_lookup(str(item["product_name"])), _normalize_lookup(str(item["supplier_name"]))): item
            for item in real_items
        }
        keys = set(current_map) | set(real_map)
        rows: list[dict[str, object]] = []
        for key in keys:
            current_item = current_map.get(key)
            real_item = real_map.get(key)
            product_name = str((current_item or real_item or {}).get("product_name") or "")
            supplier_name = str((current_item or real_item or {}).get("supplier_name") or "")
            theoretical_units = float((current_item or {}).get("total_equivalent_units") or 0)
            real_units = float((real_item or {}).get("total_equivalent_units") or 0)
            delta_units = theoretical_units - real_units
            if theoretical_units == 0 and real_units == 0:
                continue
            rows.append(
                {
                    "product_name": product_name,
                    "supplier_name": supplier_name,
                    "theoretical_units": round(theoretical_units, 2),
                    "real_units": round(real_units, 2),
                    "delta_units": round(delta_units, 2),
                }
            )

        rows.sort(
            key=lambda item: (
                -abs(float(item["delta_units"])),
                str(item["supplier_name"]).lower(),
                str(item["product_name"]).lower(),
            )
        )
        return rows

    def list_inventory_warehouses(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_inventory_data_for_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Inventario o Consumi.")

        with self._connect_tenant_database(session.database_path) as connection:
            rows = connection.execute(
                """
                SELECT
                    warehouses.*,
                    COUNT(items.id) AS product_count,
                    COALESCE(SUM(items.total_equivalent_units), 0) AS total_equivalent_units
                FROM tenant_inventory_warehouses AS warehouses
                LEFT JOIN tenant_inventory_stock_items AS items
                    ON items.warehouse_id = warehouses.id
                GROUP BY warehouses.id
                ORDER BY LOWER(warehouses.name) ASC, warehouses.created_at ASC
                """
            ).fetchall()

            warehouses: list[dict[str, object]] = []
            for row in rows:
                warehouse_id = str(row["id"])
                latest_session_row = self._read_inventory_latest_session_row(connection, warehouse_id)
                latest_inventory = (
                    self._serialize_inventory_session_row(latest_session_row)
                    if latest_session_row is not None
                    else None
                )
                session_count_row = connection.execute(
                    "SELECT COUNT(*) AS total FROM tenant_inventory_sessions WHERE warehouse_id = ?",
                    (warehouse_id,),
                ).fetchone()
                warehouse_summary = self._serialize_inventory_warehouse_row(row)
                warehouse_summary["theoretical_total_equivalent_units"] = float(row["total_equivalent_units"] or 0)
                warehouse_summary["real_total_equivalent_units"] = (
                    float(latest_session_row["total_equivalent_units"] or 0)
                    if latest_session_row is not None
                    else 0.0
                )
                warehouse_summary["latest_inventory_date"] = (
                    str(latest_session_row["inventory_date"])
                    if latest_session_row is not None
                    else None
                )
                warehouse_summary["latest_inventory"] = latest_inventory
                warehouse_summary["inventory_session_count"] = int(session_count_row["total"] or 0) if session_count_row is not None else 0
                warehouse_summary["incoming_since_latest_inventory_equivalent_units"] = self._sum_inventory_movements_since(
                    connection,
                warehouse_id,
                    start_value=latest_session_row["created_at"] if latest_session_row is not None else None,
                )
                warehouses.append(warehouse_summary)

        return {
            "warehouses": warehouses,
            "warehouse_count": len(warehouses),
            "total_products": sum(int(item["product_count"]) for item in warehouses),
            "total_equivalent_units": sum(float(item["total_equivalent_units"]) for item in warehouses),
            "theoretical_total_equivalent_units": sum(float(item["theoretical_total_equivalent_units"]) for item in warehouses),
            "real_total_equivalent_units": sum(float(item["real_total_equivalent_units"]) for item in warehouses),
            "inventory_session_count": sum(int(item["inventory_session_count"]) for item in warehouses),
        }

    def create_inventory_warehouse(
        self,
        session: SessionIdentity,
        payload: InventoryWarehouseCreatePayload,
    ) -> dict[str, object]:
        if not self._can_manage_inventory_warehouses(session):
            raise ValueError("Solo il superadmin puo modificare i magazzini di Inventario.")

        name = payload.name.strip()
        if len(name) < 2:
            raise ValueError("Dammi un nome magazzino valido.")

        warehouse_id = f"inventory_wh_{uuid.uuid4().hex}"
        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                existing_row = connection.execute(
                    "SELECT id FROM tenant_inventory_warehouses WHERE lower(name) = lower(?) LIMIT 1",
                    (name,),
                ).fetchone()
                if existing_row is not None:
                    raise ValueError("Esiste gia un magazzino con questo nome.")
                connection.execute(
                    """
                    INSERT INTO tenant_inventory_warehouses (id, name, created_at, updated_at)
                    VALUES (?, ?, ?, ?)
                    """,
                    (warehouse_id, name, timestamp, timestamp),
                )
                connection.commit()
                row = self._read_inventory_warehouse_row(connection, warehouse_id)

        return {"warehouse": self._serialize_inventory_warehouse_row(row)}

    def update_inventory_warehouse(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        payload: InventoryWarehouseCreatePayload,
    ) -> dict[str, object]:
        if not self._can_manage_inventory_warehouses(session):
            raise ValueError("Solo il superadmin puo modificare i magazzini di Inventario.")

        name = payload.name.strip()
        if len(name) < 2:
            raise ValueError("Dammi un nome magazzino valido.")

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
                existing_row = connection.execute(
                    "SELECT id FROM tenant_inventory_warehouses WHERE lower(name) = lower(?) AND id != ? LIMIT 1",
                    (name, warehouse_id),
                ).fetchone()
                if existing_row is not None:
                    raise ValueError("Esiste gia un magazzino con questo nome.")
                current_name = str(warehouse_row["name"] or "").strip()
                if current_name != name:
                    connection.execute(
                        """
                        UPDATE tenant_inventory_warehouses
                        SET name = ?,
                            updated_at = ?
                        WHERE id = ?
                        """,
                        (name, timestamp, warehouse_id),
                    )
                    connection.commit()
                updated_row = self._read_inventory_warehouse_row(connection, warehouse_id)

        return {"warehouse": self._serialize_inventory_warehouse_row(updated_row)}

    def delete_inventory_warehouse(
        self,
        session: SessionIdentity,
        warehouse_id: str,
    ) -> dict[str, object]:
        if not self._can_manage_inventory_warehouses(session):
            raise ValueError("Solo il superadmin puo modificare i magazzini di Inventario.")

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
                serialized_warehouse = self._serialize_inventory_warehouse_row(warehouse_row)
                connection.execute(
                    "DELETE FROM tenant_inventory_warehouses WHERE id = ?",
                    (warehouse_id,),
                )
                connection.commit()

        return {
            "deleted": True,
            "warehouse": serialized_warehouse,
        }

    def list_inventory_warehouse_sessions(
        self,
        session: SessionIdentity,
        warehouse_id: str,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        with self._connect_tenant_database(session.database_path) as connection:
            warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
            rows = connection.execute(
                """
                SELECT *
                FROM tenant_inventory_sessions
                WHERE warehouse_id = ?
                ORDER BY inventory_date DESC, created_at DESC
                """,
                (warehouse_id,),
            ).fetchall()

        sessions = [self._serialize_inventory_session_row(row) for row in rows]
        return {
            "warehouse": self._serialize_inventory_warehouse_row(warehouse_row),
            "sessions": sessions,
            "total_count": len(sessions),
        }

    def get_inventory_warehouse_session_detail(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        session_id: str,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        with self._connect_tenant_database(session.database_path) as connection:
            warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
            session_row = self._read_inventory_session_row(connection, session_id)
            if str(session_row["warehouse_id"] or "") != warehouse_id:
                raise ValueError("L'inventario richiesto non appartiene a questo magazzino.")
            item_rows, lots_by_item = self._load_inventory_session_items(connection, session_id)

        items = [
            self._serialize_inventory_stock_item_row(row, lots_by_item.get(str(row["id"]), []))
            for row in item_rows
        ]
        return {
            "warehouse": self._serialize_inventory_warehouse_row(warehouse_row),
            "session": self._serialize_inventory_session_row(session_row),
            "items": items,
            "total_products": len(items),
            "total_equivalent_units": sum(float(item["total_equivalent_units"]) for item in items),
        }

    def load_inventory_warehouse_session_into_stock(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        session_id: str,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
                session_row = self._read_inventory_session_row(connection, session_id)
                if str(session_row["warehouse_id"] or "") != warehouse_id:
                    raise ValueError("L'inventario richiesto non appartiene a questo magazzino.")
                serialized_warehouse = self._serialize_inventory_warehouse_row(warehouse_row)
                serialized_session = self._serialize_inventory_session_row(session_row)

                session_item_rows, session_lots_by_item = self._load_inventory_session_items(connection, session_id)
                connection.execute(
                    "DELETE FROM tenant_inventory_stock_items WHERE warehouse_id = ?",
                    (warehouse_id,),
                )

                for session_item_row in session_item_rows:
                    stock_item_id = f"inventory_item_{uuid.uuid4().hex}"
                    connection.execute(
                        """
                        INSERT INTO tenant_inventory_stock_items (
                            id,
                            warehouse_id,
                            product_id,
                            product_name,
                            product_lookup,
                            supplier_name,
                            supplier_lookup,
                            total_equivalent_units,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            stock_item_id,
                            warehouse_id,
                            int(session_item_row["product_id"]) if session_item_row["product_id"] is not None else None,
                            str(session_item_row["product_name"] or ""),
                            str(session_item_row["product_lookup"] or ""),
                            str(session_item_row["supplier_name"] or ""),
                            str(session_item_row["supplier_lookup"] or ""),
                            float(session_item_row["total_equivalent_units"] or 0),
                            timestamp,
                            timestamp,
                        ),
                    )
                    for lot_row in session_lots_by_item.get(str(session_item_row["id"]), []):
                        connection.execute(
                            """
                            INSERT INTO tenant_inventory_stock_lots (
                                id,
                                item_id,
                                lot_code,
                                lot_lookup,
                                quantity,
                                units_per_pack,
                                equivalent_units,
                                created_at,
                                updated_at
                            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                            """,
                            (
                                f"inventory_lot_{uuid.uuid4().hex}",
                                stock_item_id,
                                str(lot_row["lot_code"] or ""),
                                _normalize_lookup(str(lot_row["lot_code"] or "")),
                                float(lot_row["quantity"] or 0),
                                float(lot_row["units_per_pack"]) if lot_row["units_per_pack"] is not None else None,
                                float(lot_row["equivalent_units"] or 0),
                                timestamp,
                                timestamp,
                            ),
                        )

                connection.execute(
                    """
                    UPDATE tenant_inventory_warehouses
                    SET updated_at = ?
                    WHERE id = ?
                    """,
                    (timestamp, warehouse_id),
                )
                connection.commit()

        detail = self.get_inventory_warehouse_detail(session, warehouse_id)
        detail["loaded_session"] = serialized_session
        detail["warehouse"] = serialized_warehouse
        return detail

    def reset_inventory_warehouse_stock(
        self,
        session: SessionIdentity,
        warehouse_id: str,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._read_inventory_warehouse_row(connection, warehouse_id)
                connection.execute(
                    "DELETE FROM tenant_inventory_stock_items WHERE warehouse_id = ?",
                    (warehouse_id,),
                )
                connection.execute(
                    """
                    UPDATE tenant_inventory_warehouses
                    SET updated_at = ?
                    WHERE id = ?
                    """,
                    (timestamp, warehouse_id),
                )
                connection.commit()

        return self.get_inventory_warehouse_detail(session, warehouse_id)

    def delete_inventory_warehouse_session(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        session_id: str,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
                session_row = self._read_inventory_session_row(connection, session_id)
                if str(session_row["warehouse_id"] or "") != warehouse_id:
                    raise ValueError("L'inventario richiesto non appartiene a questo magazzino.")
                serialized_warehouse = self._serialize_inventory_warehouse_row(warehouse_row)
                serialized_session = self._serialize_inventory_session_row(session_row)
                connection.execute(
                    "DELETE FROM tenant_inventory_sessions WHERE id = ?",
                    (session_id,),
                )
                connection.execute(
                    """
                    DELETE FROM tenant_inventory_daily_consumptions
                    WHERE warehouse_id = ? AND consumption_date = ?
                    """,
                    (warehouse_id, str(session_row["inventory_date"] or "")),
                )
                self._refresh_next_inventory_daily_consumption_after_date(
                    connection,
                    warehouse_id,
                    str(session_row["inventory_date"] or ""),
                    timestamp=timestamp,
                )
                self._refresh_inventory_estimated_consumption_for_date(
                    connection,
                    str(session_row["inventory_date"] or ""),
                    timestamp=timestamp,
                )
                self._refresh_next_inventory_estimated_consumption_after_date(
                    connection,
                    str(session_row["inventory_date"] or ""),
                    timestamp=timestamp,
                )
                connection.execute(
                    """
                    UPDATE tenant_inventory_warehouses
                    SET updated_at = ?
                    WHERE id = ?
                    """,
                    (timestamp, warehouse_id),
                )
                connection.commit()

        return {
            "deleted": True,
            "warehouse": serialized_warehouse,
            "session": serialized_session,
        }

    def _save_inventory_warehouse_session_from_stock(
        self,
        connection: sqlite3.Connection,
        session: SessionIdentity,
        warehouse_id: str,
        *,
        inventory_date: str,
        timestamp: str,
    ) -> tuple[sqlite3.Row, bool]:
        warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
        actor_name = (session.user_name or session.username or session.user_email or "Utente locale").strip()
        session_id = f"inventory_session_{uuid.uuid4().hex}"
        replaced_existing = False

        existing_row = connection.execute(
            """
            SELECT id
            FROM tenant_inventory_sessions
            WHERE warehouse_id = ? AND inventory_date = ?
            LIMIT 1
            """,
            (warehouse_id, inventory_date),
        ).fetchone()
        if existing_row is not None:
            replaced_existing = True
            connection.execute(
                "DELETE FROM tenant_inventory_sessions WHERE id = ?",
                (str(existing_row["id"]),),
            )

        item_rows, lots_by_item = self._load_inventory_stock_items(connection, warehouse_id)
        total_equivalent_units = sum(float(row["total_equivalent_units"] or 0) for row in item_rows)
        connection.execute(
            """
            INSERT INTO tenant_inventory_sessions (
                id,
                warehouse_id,
                warehouse_name,
                inventory_date,
                label,
                created_by_user_id,
                created_by_name,
                total_products,
                total_equivalent_units,
                created_at,
                updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                session_id,
                warehouse_id,
                str(warehouse_row["name"] or ""),
                inventory_date,
                f"Inventario {warehouse_row['name']} del {inventory_date}",
                session.user_id,
                actor_name,
                len(item_rows),
                total_equivalent_units,
                timestamp,
                timestamp,
            ),
        )

        for item_row in item_rows:
            session_item_id = f"inventory_session_item_{uuid.uuid4().hex}"
            connection.execute(
                """
                INSERT INTO tenant_inventory_session_items (
                    id,
                    session_id,
                    product_id,
                    product_name,
                    product_lookup,
                    supplier_name,
                    supplier_lookup,
                    total_equivalent_units,
                    created_at,
                    updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    session_item_id,
                    session_id,
                    int(item_row["product_id"]) if item_row["product_id"] is not None else None,
                    str(item_row["product_name"] or ""),
                    str(item_row["product_lookup"] or ""),
                    str(item_row["supplier_name"] or ""),
                    str(item_row["supplier_lookup"] or ""),
                    float(item_row["total_equivalent_units"] or 0),
                    timestamp,
                    timestamp,
                ),
            )
            for lot_row in lots_by_item.get(str(item_row["id"]), []):
                connection.execute(
                    """
                    INSERT INTO tenant_inventory_session_lots (
                        id,
                        session_item_id,
                        lot_code,
                        lot_lookup,
                        quantity,
                        units_per_pack,
                        equivalent_units,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        f"inventory_session_lot_{uuid.uuid4().hex}",
                        session_item_id,
                        str(lot_row["lot_code"] or ""),
                        _normalize_lookup(str(lot_row["lot_code"] or "")),
                        float(lot_row["quantity"] or 0),
                        float(lot_row["units_per_pack"]) if lot_row["units_per_pack"] is not None else None,
                        float(lot_row["equivalent_units"] or 0),
                        timestamp,
                        timestamp,
                    ),
                )

        saved_session_row = self._read_inventory_session_row(connection, session_id)
        self._refresh_inventory_daily_consumption_for_session(
            connection,
            warehouse_id,
            session_id,
            timestamp=timestamp,
        )
        self._refresh_next_inventory_daily_consumption_after_date(
            connection,
            warehouse_id,
            inventory_date,
            timestamp=timestamp,
        )
        self._refresh_inventory_estimated_consumption_for_date(
            connection,
            inventory_date,
            timestamp=timestamp,
        )
        self._refresh_next_inventory_estimated_consumption_after_date(
            connection,
            inventory_date,
            timestamp=timestamp,
        )
        return saved_session_row, replaced_existing

    def _restore_inventory_stock_item_from_latest_session(
        self,
        connection: sqlite3.Connection,
        warehouse_id: str,
        product_lookup: str,
        supplier_lookup: str,
        *,
        timestamp: str,
    ) -> str | None:
        existing_row = connection.execute(
            """
            SELECT *
            FROM tenant_inventory_stock_items
            WHERE warehouse_id = ? AND product_lookup = ? AND supplier_lookup = ?
            LIMIT 1
            """,
            (warehouse_id, product_lookup, supplier_lookup),
        ).fetchone()

        latest_session_item_row = connection.execute(
            """
            SELECT
                session_items.*,
                sessions.created_at AS session_created_at
            FROM tenant_inventory_session_items AS session_items
            JOIN tenant_inventory_sessions AS sessions
                ON sessions.id = session_items.session_id
            WHERE sessions.warehouse_id = ?
              AND session_items.product_lookup = ?
              AND session_items.supplier_lookup = ?
            ORDER BY sessions.inventory_date DESC, sessions.created_at DESC
            LIMIT 1
            """,
            (warehouse_id, product_lookup, supplier_lookup),
        ).fetchone()
        if latest_session_item_row is None:
            return str(existing_row["id"]) if existing_row is not None else None

        movement_after_session = connection.execute(
            """
            SELECT 1
            FROM tenant_inventory_movements
            WHERE warehouse_id = ?
              AND product_lookup = ?
              AND supplier_lookup = ?
              AND created_at > ?
            LIMIT 1
            """,
            (
                warehouse_id,
                product_lookup,
                supplier_lookup,
                str(latest_session_item_row["session_created_at"] or ""),
            ),
        ).fetchone()
        if movement_after_session is not None:
            return str(existing_row["id"]) if existing_row is not None else None

        if existing_row is None:
            restored_item_id = f"inventory_item_{uuid.uuid4().hex}"
            connection.execute(
                """
                INSERT INTO tenant_inventory_stock_items (
                    id,
                    warehouse_id,
                    product_id,
                    product_name,
                    product_lookup,
                    supplier_name,
                    supplier_lookup,
                    total_equivalent_units,
                    created_at,
                    updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    restored_item_id,
                    warehouse_id,
                    latest_session_item_row["product_id"],
                    str(latest_session_item_row["product_name"] or ""),
                    str(latest_session_item_row["product_lookup"] or ""),
                    str(latest_session_item_row["supplier_name"] or ""),
                    str(latest_session_item_row["supplier_lookup"] or ""),
                    float(latest_session_item_row["total_equivalent_units"] or 0),
                    timestamp,
                    timestamp,
                ),
            )
        else:
            restored_item_id = str(existing_row["id"])
            connection.execute(
                """
                UPDATE tenant_inventory_stock_items
                SET
                    product_id = ?,
                    product_name = ?,
                    product_lookup = ?,
                    supplier_name = ?,
                    supplier_lookup = ?,
                    total_equivalent_units = ?,
                    updated_at = ?
                WHERE id = ?
                """,
                (
                    latest_session_item_row["product_id"],
                    str(latest_session_item_row["product_name"] or ""),
                    str(latest_session_item_row["product_lookup"] or ""),
                    str(latest_session_item_row["supplier_name"] or ""),
                    str(latest_session_item_row["supplier_lookup"] or ""),
                    float(latest_session_item_row["total_equivalent_units"] or 0),
                    timestamp,
                    restored_item_id,
                ),
            )
            connection.execute("DELETE FROM tenant_inventory_stock_lots WHERE item_id = ?", (restored_item_id,))

        lot_rows = connection.execute(
            """
            SELECT *
            FROM tenant_inventory_session_lots
            WHERE session_item_id = ?
            ORDER BY lot_code ASC
            """,
            (str(latest_session_item_row["id"]),),
        ).fetchall()
        for lot_row in lot_rows:
            connection.execute(
                """
                INSERT INTO tenant_inventory_stock_lots (
                    id,
                    item_id,
                    lot_code,
                    lot_lookup,
                    quantity,
                    units_per_pack,
                    equivalent_units,
                    created_at,
                    updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    f"inventory_lot_{uuid.uuid4().hex}",
                    restored_item_id,
                    str(lot_row["lot_code"] or ""),
                    str(lot_row["lot_lookup"] or ""),
                    float(lot_row["quantity"] or 0),
                    float(lot_row["units_per_pack"]) if lot_row["units_per_pack"] is not None else None,
                    float(lot_row["equivalent_units"] or 0),
                    timestamp,
                    timestamp,
                ),
            )
        return restored_item_id

    def create_inventory_warehouse_session(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        payload: InventorySessionCreatePayload,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        inventory_date = self._normalize_inventory_date(payload.inventory_date)
        timestamp = _iso_now()

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                saved_session_row, replaced_existing = self._save_inventory_warehouse_session_from_stock(
                    connection,
                    session,
                    warehouse_id,
                    inventory_date=inventory_date,
                    timestamp=timestamp,
                )
                connection.commit()

        return {
            "session": self._serialize_inventory_session_row(saved_session_row),
            "replaced_existing": replaced_existing,
        }

    def _normalize_homemade_calendar_rules(
        self,
        calendar_payload: HomemadeOperationalCalendarPayload | dict[str, object] | None,
    ) -> list[dict[str, object]]:
        if calendar_payload is None:
            return []
        raw_rules = (
            getattr(calendar_payload, "rules", None)
            if isinstance(calendar_payload, BaseModel)
            else calendar_payload.get("rules") if isinstance(calendar_payload, dict) else None
        )
        rules: list[dict[str, object]] = []
        for raw_rule in raw_rules or []:
            raw_start_date = (
                getattr(raw_rule, "start_date", None)
                if isinstance(raw_rule, BaseModel)
                else raw_rule.get("start_date") if isinstance(raw_rule, dict) else None
            )
            raw_end_date = (
                getattr(raw_rule, "end_date", None)
                if isinstance(raw_rule, BaseModel)
                else raw_rule.get("end_date") if isinstance(raw_rule, dict) else None
            )
            raw_weekdays = (
                getattr(raw_rule, "weekdays", None)
                if isinstance(raw_rule, BaseModel)
                else raw_rule.get("weekdays") if isinstance(raw_rule, dict) else None
            )
            try:
                start_date = date.fromisoformat(str(raw_start_date).strip()).isoformat() if raw_start_date else None
                end_date = date.fromisoformat(str(raw_end_date).strip()).isoformat() if raw_end_date else None
            except ValueError as exc:
                raise ValueError("Date calendario non valide. Usa il formato YYYY-MM-DD.") from exc
            if start_date and end_date and start_date > end_date:
                raise ValueError("La data iniziale del calendario non puo essere successiva alla data finale.")

            weekdays = sorted(
                {
                    int(item)
                    for item in raw_weekdays or []
                    if isinstance(item, int) or str(item).strip().lstrip("-").isdigit()
                    if 0 <= int(item) <= 6
                }
            )
            if not weekdays:
                weekdays = list(range(7))
            rules.append(
                {
                    "start_date": start_date,
                    "end_date": end_date,
                    "weekdays": weekdays,
                }
            )
        return rules

    def _load_homemade_calendar_rules_json(self, value: object | None) -> list[dict[str, object]]:
        raw_value = str(value or "").strip()
        if not raw_value:
            return []
        try:
            decoded = json.loads(raw_value)
        except json.JSONDecodeError:
            return []
        if isinstance(decoded, dict):
            return self._normalize_homemade_calendar_rules(decoded)
        if isinstance(decoded, list):
            return self._normalize_homemade_calendar_rules({"rules": decoded})
        return []

    def _homemade_calendar_rules_for_scope(
        self,
        settings: dict[str, object],
        usage_scope: str | None,
    ) -> list[dict[str, object]]:
        normalized_scope = _normalize_homemade_usage_scope(usage_scope)
        bar_rules = list((settings.get("bar_calendar") or {}).get("rules") or []) if isinstance(settings.get("bar_calendar"), dict) else []
        restaurant_rules = (
            list((settings.get("restaurant_calendar") or {}).get("rules") or [])
            if isinstance(settings.get("restaurant_calendar"), dict)
            else []
        )
        if normalized_scope == "bar":
            return bar_rules
        if normalized_scope == "restaurant":
            return restaurant_rules
        return [*bar_rules, *restaurant_rules]

    def _homemade_calendar_rule_matches(self, target_date: date, rule: dict[str, object]) -> bool:
        start_date = str(rule.get("start_date") or "").strip()
        end_date = str(rule.get("end_date") or "").strip()
        weekdays = {int(item) for item in rule.get("weekdays") or [] if 0 <= int(item) <= 6}
        if start_date and target_date.isoformat() < start_date:
            return False
        if end_date and target_date.isoformat() > end_date:
            return False
        return target_date.weekday() in (weekdays or set(range(7)))

    def _count_homemade_operational_days(
        self,
        *,
        start_date: str | None,
        end_date: str | None,
        settings: dict[str, object],
        usage_scope: str | None,
        fallback_days: int,
    ) -> tuple[int, str]:
        rules = self._homemade_calendar_rules_for_scope(settings, usage_scope)
        if not rules or not start_date or not end_date:
            return fallback_days, "movement_days"
        try:
            current = date.fromisoformat(start_date)
            last = date.fromisoformat(end_date)
        except ValueError:
            return fallback_days, "movement_days"
        if current > last:
            return fallback_days, "movement_days"

        count = 0
        while current <= last:
            if any(self._homemade_calendar_rule_matches(current, rule) for rule in rules):
                count += 1
            current += timedelta(days=1)
        return max(count, fallback_days), "operational_calendar"

    def _read_homemade_stock_settings(self, connection: sqlite3.Connection) -> dict[str, object]:
        row = connection.execute(
            """
            SELECT *
            FROM tenant_homemade_stock_settings
            WHERE id = 'default'
            LIMIT 1
            """
        ).fetchone()
        bar_calendar = self._load_homemade_calendar_rules_json(row["bar_calendar_json"]) if row is not None else []
        restaurant_calendar = (
            self._load_homemade_calendar_rules_json(row["restaurant_calendar_json"]) if row is not None else []
        )
        return {
            "minimum_stock_days": float(row["minimum_stock_days"] or 0) if row is not None else 0.0,
            "bar_calendar": {"rules": bar_calendar},
            "restaurant_calendar": {"rules": restaurant_calendar},
            "updated_at": str(row["updated_at"] or "") if row is not None else None,
        }

    def _record_homemade_stock_movement(
        self,
        connection: sqlite3.Connection,
        *,
        session: SessionIdentity,
        warehouse_row: sqlite3.Row,
        recipe_id: str,
        recipe_name: str,
        recipe_lookup: str,
        quantity_before: float,
        quantity_after: float,
        movement_type: str,
        timestamp: str,
    ) -> None:
        delta_quantity = round(float(quantity_after) - float(quantity_before), 2)
        if abs(delta_quantity) < 0.000001:
            return
        actor_name = (session.user_name or session.username or session.user_email or "Utente locale").strip()
        connection.execute(
            """
            INSERT INTO tenant_homemade_stock_movements (
                id,
                warehouse_id,
                warehouse_name,
                recipe_id,
                recipe_name,
                recipe_lookup,
                measurement_unit,
                quantity_before,
                quantity_after,
                delta_quantity,
                added_quantity,
                consumed_quantity,
                movement_type,
                occurred_at,
                created_by_user_id,
                created_by_name,
                created_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                f"homemade_stock_move_{uuid.uuid4().hex}",
                str(warehouse_row["id"] or ""),
                str(warehouse_row["name"] or ""),
                recipe_id,
                recipe_name,
                recipe_lookup,
                HOMEMADE_STOCK_UNIT,
                round(float(quantity_before), 2),
                round(float(quantity_after), 2),
                delta_quantity,
                round(max(delta_quantity, 0.0), 2),
                round(max(-delta_quantity, 0.0), 2),
                movement_type,
                timestamp,
                session.user_id,
                actor_name,
                timestamp,
            ),
        )

    def list_homemade_stock_warehouses(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        with self._connect_tenant_database(session.database_path) as connection:
            rows = connection.execute(
                """
                SELECT
                    warehouses.*,
                    (
                        SELECT COUNT(*)
                        FROM tenant_homemade_recipes
                    ) AS recipe_count,
                    COALESCE(SUM(items.quantity), 0) AS total_quantity
                FROM tenant_homemade_stock_warehouses AS warehouses
                LEFT JOIN tenant_homemade_stock_items AS items
                    ON items.warehouse_id = warehouses.id
                GROUP BY warehouses.id
                ORDER BY lower(warehouses.name) ASC, warehouses.created_at ASC
                """
            ).fetchall()

            warehouses: list[dict[str, object]] = []
            for row in rows:
                warehouse_id = str(row["id"] or "")
                latest_session_row = self._read_homemade_stock_latest_session_row(connection, warehouse_id)
                latest_inventory = (
                    self._serialize_homemade_stock_session_row(latest_session_row)
                    if latest_session_row is not None
                    else None
                )
                session_count_row = connection.execute(
                    "SELECT COUNT(*) AS total FROM tenant_homemade_stock_sessions WHERE warehouse_id = ?",
                    (warehouse_id,),
                ).fetchone()
                warehouse_summary = self._serialize_homemade_stock_warehouse_row(row)
                warehouse_summary["latest_inventory_date"] = (
                    str(latest_session_row["inventory_date"] or "")
                    if latest_session_row is not None
                    else None
                )
                warehouse_summary["latest_inventory"] = latest_inventory
                warehouse_summary["inventory_session_count"] = int(session_count_row["total"] or 0) if session_count_row is not None else 0
                warehouses.append(warehouse_summary)

        return {
            "warehouses": warehouses,
            "warehouse_count": len(warehouses),
            "total_recipes": sum(int(item["recipe_count"]) for item in warehouses),
            "total_quantity": round(sum(float(item["total_quantity"]) for item in warehouses), 2),
        }

    def list_homemade_stock_totals(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        with self._connect_tenant_database(session.database_path) as connection:
            rows = connection.execute(
                """
                SELECT
                    items.recipe_id,
                    items.recipe_name,
                    items.recipe_lookup,
                    COALESCE(MAX(recipes.usage_scope), 'both') AS usage_scope,
                    'pz' AS measurement_unit,
                    COUNT(DISTINCT items.warehouse_id) AS warehouse_count,
                    ROUND(SUM(items.quantity), 2) AS total_quantity
                FROM tenant_homemade_stock_items AS items
                LEFT JOIN tenant_homemade_recipes AS recipes
                    ON recipes.id = items.recipe_id
                WHERE items.quantity > 0
                GROUP BY items.recipe_lookup
                ORDER BY total_quantity DESC, lower(items.recipe_name) ASC
                """
            ).fetchall()

            breakdown_rows = connection.execute(
                """
                SELECT
                    items.recipe_lookup,
                    'pz' AS measurement_unit,
                    items.recipe_id,
                    items.recipe_name,
                    items.warehouse_id,
                    warehouses.name AS warehouse_name,
                    ROUND(items.quantity, 2) AS quantity
                FROM tenant_homemade_stock_items AS items
                JOIN tenant_homemade_stock_warehouses AS warehouses
                    ON warehouses.id = items.warehouse_id
                WHERE items.quantity > 0
                ORDER BY lower(items.recipe_name) ASC, lower(warehouses.name) ASC
                """
            ).fetchall()

        breakdowns_by_key: dict[str, list[dict[str, object]]] = {}
        for row in breakdown_rows:
            key = f"{row['recipe_lookup']}::{HOMEMADE_STOCK_UNIT}"
            breakdowns_by_key.setdefault(key, []).append(
                {
                    "warehouse_id": str(row["warehouse_id"] or ""),
                    "warehouse_name": str(row["warehouse_name"] or ""),
                    "quantity": float(row["quantity"] or 0),
                }
            )

        items: list[dict[str, object]] = []
        for row in rows:
            key = f"{row['recipe_lookup']}::{HOMEMADE_STOCK_UNIT}"
            total_quantity = float(row["total_quantity"] or 0)
            usage_scope = _normalize_homemade_usage_scope(
                str(row["usage_scope"] or "both") if "usage_scope" in row.keys() else "both"
            )
            items.append(
                {
                    "key": key,
                    "recipe_id": str(row["recipe_id"] or ""),
                    "recipe_name": str(row["recipe_name"] or ""),
                    "usage_scope": usage_scope,
                    "usage_scope_label": _homemade_usage_scope_label(usage_scope),
                    "measurement_unit": HOMEMADE_STOCK_UNIT,
                    "warehouse_count": int(row["warehouse_count"] or 0),
                    "total_quantity": total_quantity,
                    "warehouses": breakdowns_by_key.get(key, []),
                }
            )

        return {
            "items": items,
            "total_count": len(items),
            "total_quantity": round(sum(float(item["total_quantity"]) for item in items), 2),
        }

    def get_homemade_stock_understock(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        with self._connect_tenant_database(session.database_path) as connection:
            settings = self._read_homemade_stock_settings(connection)
            stock_rows = connection.execute(
                """
                SELECT
                    items.recipe_id,
                    items.recipe_name,
                    items.recipe_lookup,
                    COALESCE(recipes.usage_scope, 'both') AS usage_scope,
                    COUNT(DISTINCT items.warehouse_id) AS warehouse_count,
                    ROUND(SUM(items.quantity), 2) AS current_quantity
                FROM tenant_homemade_stock_items AS items
                LEFT JOIN tenant_homemade_recipes AS recipes
                    ON recipes.id = items.recipe_id
                WHERE quantity > 0
                GROUP BY items.recipe_lookup
                """
            ).fetchall()
            movement_rows = connection.execute(
                """
                SELECT
                    movements.recipe_id,
                    movements.recipe_name,
                    movements.recipe_lookup,
                    COALESCE(recipes.usage_scope, 'both') AS usage_scope,
                    COUNT(DISTINCT CASE WHEN consumed_quantity > 0 THEN substr(occurred_at, 1, 10) END) AS workdays_count,
                    MIN(substr(occurred_at, 1, 10)) AS first_observed_date,
                    MAX(substr(occurred_at, 1, 10)) AS last_observed_date,
                    MIN(CASE WHEN consumed_quantity > 0 THEN substr(occurred_at, 1, 10) END) AS first_consumed_date,
                    MAX(CASE WHEN consumed_quantity > 0 THEN substr(occurred_at, 1, 10) END) AS last_consumed_date,
                    ROUND(SUM(consumed_quantity), 2) AS consumed_quantity,
                    ROUND(SUM(added_quantity), 2) AS added_quantity
                FROM tenant_homemade_stock_movements AS movements
                LEFT JOIN tenant_homemade_recipes AS recipes
                    ON recipes.id = movements.recipe_id
                WHERE consumed_quantity > 0 OR added_quantity > 0
                GROUP BY movements.recipe_lookup
                """
            ).fetchall()

        stock_by_lookup = {
            str(row["recipe_lookup"] or ""): row
            for row in stock_rows
            if str(row["recipe_lookup"] or "").strip()
        }
        movement_by_lookup = {
            str(row["recipe_lookup"] or ""): row
            for row in movement_rows
            if str(row["recipe_lookup"] or "").strip()
        }
        minimum_stock_days = float(settings["minimum_stock_days"] or 0)
        completed_days_end_date = (_local_now().date() - timedelta(days=1)).isoformat()
        items: list[dict[str, object]] = []
        for lookup in sorted(set(stock_by_lookup) | set(movement_by_lookup)):
            stock_row = stock_by_lookup.get(lookup)
            movement_row = movement_by_lookup.get(lookup)
            recipe_id = str((stock_row or movement_row)["recipe_id"] or "") if (stock_row or movement_row) is not None else ""
            recipe_name = str((stock_row or movement_row)["recipe_name"] or lookup) if (stock_row or movement_row) is not None else lookup
            usage_scope = _normalize_homemade_usage_scope(
                str((stock_row or movement_row)["usage_scope"] or "both") if (stock_row or movement_row) is not None else "both"
            )
            current_quantity = float(stock_row["current_quantity"] or 0) if stock_row is not None else 0.0
            warehouse_count = int(stock_row["warehouse_count"] or 0) if stock_row is not None else 0
            consumed_quantity = float(movement_row["consumed_quantity"] or 0) if movement_row is not None else 0.0
            added_quantity = float(movement_row["added_quantity"] or 0) if movement_row is not None else 0.0
            consumption_event_days_count = int(movement_row["workdays_count"] or 0) if movement_row is not None else 0
            first_observed_date = str(movement_row["first_observed_date"] or "") if movement_row is not None else ""
            last_observed_date = str(movement_row["last_observed_date"] or "") if movement_row is not None else ""
            first_consumed_date = str(movement_row["first_consumed_date"] or "") if movement_row is not None else ""
            last_consumed_date = str(movement_row["last_consumed_date"] or "") if movement_row is not None else ""
            workdays_count, calculation_basis = self._count_homemade_operational_days(
                start_date=first_observed_date or first_consumed_date or None,
                end_date=completed_days_end_date,
                settings=settings,
                usage_scope=usage_scope,
                fallback_days=consumption_event_days_count,
            )
            average_daily_consumption = round(consumed_quantity / workdays_count, 2) if workdays_count > 0 else 0.0
            minimum_required_quantity = round(average_daily_consumption * minimum_stock_days, 2)
            shortage_quantity = round(max(minimum_required_quantity - current_quantity, 0.0), 2)
            coverage_days = round(current_quantity / average_daily_consumption, 2) if average_daily_consumption > 0 else None
            item = {
                "key": f"{lookup}::{HOMEMADE_STOCK_UNIT}",
                "recipe_id": recipe_id,
                "recipe_name": recipe_name,
                "usage_scope": usage_scope,
                "usage_scope_label": _homemade_usage_scope_label(usage_scope),
                "measurement_unit": HOMEMADE_STOCK_UNIT,
                "warehouse_count": warehouse_count,
                "current_quantity": round(current_quantity, 2),
                "consumed_quantity": round(consumed_quantity, 2),
                "added_quantity": round(added_quantity, 2),
                "workdays_count": workdays_count,
                "consumption_event_days_count": consumption_event_days_count,
                "observed_start_date": first_consumed_date or None,
                "observed_end_date": last_consumed_date or None,
                "tracking_start_date": first_observed_date or None,
                "tracking_last_movement_date": last_observed_date or None,
                "calculation_end_date": completed_days_end_date,
                "calculation_basis": calculation_basis,
                "average_daily_consumption": average_daily_consumption,
                "minimum_required_quantity": minimum_required_quantity,
                "shortage_quantity": shortage_quantity,
                "coverage_days": coverage_days,
                "is_under_stock": minimum_stock_days > 0 and average_daily_consumption > 0 and shortage_quantity > 0,
            }
            items.append(item)

        items.sort(
            key=lambda item: (
                0 if bool(item["is_under_stock"]) else 1,
                -float(item["shortage_quantity"] or 0),
                str(item["recipe_name"]).casefold(),
            )
        )
        under_stock_items = [item for item in items if bool(item["is_under_stock"])]
        return {
            "settings": settings,
            "items": items,
            "under_stock_items": under_stock_items,
            "total_count": len(items),
            "under_stock_count": len(under_stock_items),
            "calculation_mode": "media consumo = decrementi stock / giorni operativi della prep se configurati, altrimenti giorni con decrementi registrati; sotto-stock = stock corrente < media * giorni minimi",
        }

    def update_homemade_stock_settings(
        self,
        session: SessionIdentity,
        payload: HomemadeStockSettingsPayload,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        timestamp = _iso_now()
        minimum_stock_days = round(float(payload.minimum_stock_days or 0), 2)
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                current_settings = self._read_homemade_stock_settings(connection)
                bar_calendar_rules = (
                    self._normalize_homemade_calendar_rules(payload.bar_calendar)
                    if payload.bar_calendar is not None
                    else list((current_settings.get("bar_calendar") or {}).get("rules") or [])
                )
                restaurant_calendar_rules = (
                    self._normalize_homemade_calendar_rules(payload.restaurant_calendar)
                    if payload.restaurant_calendar is not None
                    else list((current_settings.get("restaurant_calendar") or {}).get("rules") or [])
                )
                connection.execute(
                    """
                    INSERT INTO tenant_homemade_stock_settings (
                        id,
                        minimum_stock_days,
                        bar_calendar_json,
                        restaurant_calendar_json,
                        created_at,
                        updated_at
                    ) VALUES ('default', ?, ?, ?, ?, ?)
                    ON CONFLICT(id) DO UPDATE SET
                        minimum_stock_days = excluded.minimum_stock_days,
                        bar_calendar_json = excluded.bar_calendar_json,
                        restaurant_calendar_json = excluded.restaurant_calendar_json,
                        updated_at = excluded.updated_at
                    """,
                    (
                        minimum_stock_days,
                        json.dumps({"rules": bar_calendar_rules}, ensure_ascii=False),
                        json.dumps({"rules": restaurant_calendar_rules}, ensure_ascii=False),
                        timestamp,
                        timestamp,
                    ),
                )
                connection.commit()

        return self.get_homemade_stock_understock(session)

    def create_homemade_stock_warehouse(
        self,
        session: SessionIdentity,
        payload: InventoryWarehouseCreatePayload,
    ) -> dict[str, object]:
        if not self._can_manage_homemade_stock_warehouses(session):
            raise ValueError("Solo il superadmin puo modificare i magazzini/frigo di Stock.")

        name = payload.name.strip()
        if len(name) < 2:
            raise ValueError("Dammi un nome magazzino/frigo valido.")

        warehouse_id = f"homemade_stock_wh_{uuid.uuid4().hex}"
        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                existing_row = connection.execute(
                    "SELECT id FROM tenant_homemade_stock_warehouses WHERE lower(name) = lower(?) LIMIT 1",
                    (name,),
                ).fetchone()
                if existing_row is not None:
                    raise ValueError("Esiste gia un magazzino/frigo con questo nome.")
                connection.execute(
                    """
                    INSERT INTO tenant_homemade_stock_warehouses (id, name, created_at, updated_at)
                    VALUES (?, ?, ?, ?)
                    """,
                    (warehouse_id, name, timestamp, timestamp),
                )
                connection.commit()
                row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)

        return {"warehouse": self._serialize_homemade_stock_warehouse_row(row)}

    def update_homemade_stock_warehouse(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        payload: InventoryWarehouseCreatePayload,
    ) -> dict[str, object]:
        if not self._can_manage_homemade_stock_warehouses(session):
            raise ValueError("Solo il superadmin puo modificare i magazzini/frigo di Stock.")

        name = payload.name.strip()
        if len(name) < 2:
            raise ValueError("Dammi un nome magazzino/frigo valido.")

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)
                existing_row = connection.execute(
                    """
                    SELECT id
                    FROM tenant_homemade_stock_warehouses
                    WHERE lower(name) = lower(?) AND id != ?
                    LIMIT 1
                    """,
                    (name, warehouse_id),
                ).fetchone()
                if existing_row is not None:
                    raise ValueError("Esiste gia un magazzino/frigo con questo nome.")
                current_name = str(warehouse_row["name"] or "").strip()
                if current_name != name:
                    connection.execute(
                        """
                        UPDATE tenant_homemade_stock_warehouses
                        SET name = ?, updated_at = ?
                        WHERE id = ?
                        """,
                        (name, timestamp, warehouse_id),
                    )
                    connection.commit()
                updated_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)

        return {"warehouse": self._serialize_homemade_stock_warehouse_row(updated_row)}

    def delete_homemade_stock_warehouse(
        self,
        session: SessionIdentity,
        warehouse_id: str,
    ) -> dict[str, object]:
        if not self._can_manage_homemade_stock_warehouses(session):
            raise ValueError("Solo il superadmin puo modificare i magazzini/frigo di Stock.")

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)
                serialized_warehouse = self._serialize_homemade_stock_warehouse_row(warehouse_row)
                connection.execute(
                    "DELETE FROM tenant_homemade_stock_warehouses WHERE id = ?",
                    (warehouse_id,),
                )
                connection.commit()

        return {
            "deleted": True,
            "warehouse": serialized_warehouse,
        }

    def list_homemade_stock_warehouse_sessions(
        self,
        session: SessionIdentity,
        warehouse_id: str,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        with self._connect_tenant_database(session.database_path) as connection:
            warehouse_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)
            rows = connection.execute(
                """
                SELECT *
                FROM tenant_homemade_stock_sessions
                WHERE warehouse_id = ?
                ORDER BY inventory_date DESC, created_at DESC
                """,
                (warehouse_id,),
            ).fetchall()

        sessions = [self._serialize_homemade_stock_session_row(row) for row in rows]
        return {
            "warehouse": self._serialize_homemade_stock_warehouse_row(warehouse_row),
            "sessions": sessions,
            "total_count": len(sessions),
        }

    def get_homemade_stock_warehouse_session_detail(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        session_id: str,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        with self._connect_tenant_database(session.database_path) as connection:
            warehouse_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)
            session_row = self._read_homemade_stock_session_row(connection, session_id)
            if str(session_row["warehouse_id"] or "") != warehouse_id:
                raise ValueError("Lo stock richiesto non appartiene a questo magazzino/frigo.")
            item_rows = self._load_homemade_stock_session_items(connection, session_id)

        items = [self._serialize_homemade_stock_item_row(row) for row in item_rows]
        return {
            "warehouse": self._serialize_homemade_stock_warehouse_row(warehouse_row),
            "session": self._serialize_homemade_stock_session_row(session_row),
            "items": items,
            "total_recipes": len(items),
            "total_quantity": round(sum(float(item["quantity"]) for item in items), 2),
        }

    def load_homemade_stock_warehouse_session_into_stock(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        session_id: str,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)
                session_row = self._read_homemade_stock_session_row(connection, session_id)
                if str(session_row["warehouse_id"] or "") != warehouse_id:
                    raise ValueError("Lo stock richiesto non appartiene a questo magazzino/frigo.")
                serialized_warehouse = self._serialize_homemade_stock_warehouse_row(warehouse_row)
                serialized_session = self._serialize_homemade_stock_session_row(session_row)

                session_item_rows = self._load_homemade_stock_session_items(connection, session_id)
                connection.execute(
                    "DELETE FROM tenant_homemade_stock_items WHERE warehouse_id = ?",
                    (warehouse_id,),
                )

                for session_item_row in session_item_rows:
                    connection.execute(
                        """
                        INSERT INTO tenant_homemade_stock_items (
                            id,
                            warehouse_id,
                            recipe_id,
                            recipe_name,
                            recipe_lookup,
                            measurement_unit,
                            quantity,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"homemade_stock_item_{uuid.uuid4().hex}",
                            warehouse_id,
                            str(session_item_row["recipe_id"] or ""),
                            str(session_item_row["recipe_name"] or ""),
                            str(session_item_row["recipe_lookup"] or ""),
                            HOMEMADE_STOCK_UNIT,
                            float(session_item_row["quantity"] or 0),
                            timestamp,
                            timestamp,
                        ),
                    )

                connection.execute(
                    """
                    UPDATE tenant_homemade_stock_warehouses
                    SET updated_at = ?
                    WHERE id = ?
                    """,
                    (timestamp, warehouse_id),
                )
                connection.commit()

        detail = self.get_homemade_stock_warehouse_detail(session, warehouse_id)
        detail["loaded_session"] = serialized_session
        detail["warehouse"] = serialized_warehouse
        return detail

    def reset_homemade_stock_warehouse_stock(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        usage_scope: str | None = None,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        timestamp = _iso_now()
        normalized_usage_scope = _normalize_homemade_usage_scope(usage_scope) if usage_scope else None
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._read_homemade_stock_warehouse_row(connection, warehouse_id)
                if normalized_usage_scope in {"bar", "restaurant"}:
                    connection.execute(
                        """
                        DELETE FROM tenant_homemade_stock_items
                        WHERE warehouse_id = ?
                          AND lower(COALESCE(
                            (
                                SELECT recipes.usage_scope
                                FROM tenant_homemade_recipes AS recipes
                                WHERE recipes.id = tenant_homemade_stock_items.recipe_id
                                LIMIT 1
                            ),
                            'both'
                          )) IN (?, 'both')
                        """,
                        (warehouse_id, normalized_usage_scope),
                    )
                else:
                    connection.execute(
                        "DELETE FROM tenant_homemade_stock_items WHERE warehouse_id = ?",
                        (warehouse_id,),
                    )
                connection.execute(
                    """
                    UPDATE tenant_homemade_stock_warehouses
                    SET updated_at = ?
                    WHERE id = ?
                    """,
                    (timestamp, warehouse_id),
                )
                connection.commit()

        return self.get_homemade_stock_warehouse_detail(session, warehouse_id)

    def delete_homemade_stock_warehouse_session(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        session_id: str,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)
                session_row = self._read_homemade_stock_session_row(connection, session_id)
                if str(session_row["warehouse_id"] or "") != warehouse_id:
                    raise ValueError("Lo stock richiesto non appartiene a questo magazzino/frigo.")
                serialized_warehouse = self._serialize_homemade_stock_warehouse_row(warehouse_row)
                serialized_session = self._serialize_homemade_stock_session_row(session_row)
                connection.execute(
                    "DELETE FROM tenant_homemade_stock_sessions WHERE id = ?",
                    (session_id,),
                )
                connection.execute(
                    """
                    UPDATE tenant_homemade_stock_warehouses
                    SET updated_at = ?
                    WHERE id = ?
                    """,
                    (timestamp, warehouse_id),
                )
                connection.commit()

        return {
            "deleted": True,
            "warehouse": serialized_warehouse,
            "session": serialized_session,
        }

    def create_homemade_stock_warehouse_session(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        payload: InventorySessionCreatePayload,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        inventory_date = self._normalize_inventory_date(payload.inventory_date)
        timestamp = _iso_now()
        actor_name = (session.user_name or session.username or session.user_email or "Utente locale").strip()
        session_id = f"homemade_stock_session_{uuid.uuid4().hex}"
        replaced_existing = False

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)
                existing_row = connection.execute(
                    """
                    SELECT id
                    FROM tenant_homemade_stock_sessions
                    WHERE warehouse_id = ? AND inventory_date = ?
                    LIMIT 1
                    """,
                    (warehouse_id, inventory_date),
                ).fetchone()
                if existing_row is not None:
                    replaced_existing = True
                    connection.execute(
                        "DELETE FROM tenant_homemade_stock_sessions WHERE id = ?",
                        (str(existing_row["id"]),),
                    )

                item_rows = self._load_homemade_stock_items(connection, warehouse_id)
                total_quantity = round(sum(float(row["quantity"] or 0) for row in item_rows), 2)
                connection.execute(
                    """
                    INSERT INTO tenant_homemade_stock_sessions (
                        id,
                        warehouse_id,
                        warehouse_name,
                        inventory_date,
                        label,
                        created_by_user_id,
                        created_by_name,
                        total_recipes,
                        total_quantity,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        session_id,
                        warehouse_id,
                        str(warehouse_row["name"] or ""),
                        inventory_date,
                        f"Stock {warehouse_row['name']} del {inventory_date}",
                        session.user_id,
                        actor_name,
                        len(item_rows),
                        total_quantity,
                        timestamp,
                        timestamp,
                    ),
                )

                for item_row in item_rows:
                    connection.execute(
                        """
                        INSERT INTO tenant_homemade_stock_session_items (
                            id,
                            session_id,
                            recipe_id,
                            recipe_name,
                            recipe_lookup,
                            measurement_unit,
                            quantity,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"homemade_stock_session_item_{uuid.uuid4().hex}",
                            session_id,
                            str(item_row["recipe_id"] or ""),
                            str(item_row["recipe_name"] or ""),
                            str(item_row["recipe_lookup"] or ""),
                            HOMEMADE_STOCK_UNIT,
                            float(item_row["quantity"] or 0),
                            timestamp,
                            timestamp,
                        ),
                    )

                connection.commit()
                saved_session_row = self._read_homemade_stock_session_row(connection, session_id)

        return {
            "session": self._serialize_homemade_stock_session_row(saved_session_row),
            "replaced_existing": replaced_existing,
        }

    def get_homemade_stock_warehouse_detail(
        self,
        session: SessionIdentity,
        warehouse_id: str,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        with self._connect_tenant_database(session.database_path) as connection:
            warehouse_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)
            current_item_rows = self._load_homemade_stock_items(connection, warehouse_id)
            latest_session_row = self._read_homemade_stock_latest_session_row(connection, warehouse_id)
            latest_inventory = (
                self._serialize_homemade_stock_session_row(latest_session_row)
                if latest_session_row is not None
                else None
            )
            real_item_rows = (
                self._load_homemade_stock_session_items(connection, str(latest_session_row["id"]))
                if latest_session_row is not None
                else []
            )

        current_items = [self._serialize_homemade_stock_item_row(row) for row in current_item_rows]
        real_items = [self._serialize_homemade_stock_item_row(row) for row in real_item_rows]
        return {
            "warehouse": self._serialize_homemade_stock_warehouse_row(warehouse_row),
            "items": current_items,
            "current_items": current_items,
            "real_items": real_items,
            "latest_inventory": latest_inventory,
            "total_recipes": len(current_items),
            "total_quantity": round(sum(float(item["quantity"]) for item in current_items), 2),
        }

    def search_homemade_stock_recipes(
        self,
        session: SessionIdentity,
        *,
        query: str | None,
        usage_scope: str | None = None,
        limit: int = 20,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        safe_limit = max(1, min(int(limit), 100))
        normalized_query = str(query or "").strip().lower()
        normalized_usage_scope = _normalize_homemade_usage_scope(usage_scope) if usage_scope else None

        with self._connect_tenant_database(session.database_path) as connection:
            scope_clause = ""
            scope_params: tuple[object, ...] = ()
            if normalized_usage_scope in {"bar", "restaurant"}:
                scope_clause = " AND lower(COALESCE(usage_scope, 'both')) IN (?, 'both')"
                scope_params = (normalized_usage_scope,)

            if normalized_query:
                like_value = f"%{normalized_query}%"
                prefix_value = f"{normalized_query}%"
                rows = connection.execute(
                    f"""
                    SELECT id, name, measurement_unit, usage_scope, preparation_date, created_at, updated_at
                    FROM tenant_homemade_recipes
                    WHERE lower(name) LIKE ?
                    {scope_clause}
                    ORDER BY
                        CASE
                            WHEN lower(name) = ? THEN 0
                            WHEN lower(name) LIKE ? THEN 1
                            ELSE 2
                        END,
                        lower(name) ASC,
                        created_at ASC
                    LIMIT ?
                    """,
                    (like_value, *scope_params, normalized_query, prefix_value, safe_limit),
                ).fetchall()
            else:
                rows = connection.execute(
                    f"""
                    SELECT id, name, measurement_unit, usage_scope, preparation_date, created_at, updated_at
                    FROM tenant_homemade_recipes
                    WHERE 1 = 1
                    {scope_clause}
                    ORDER BY lower(name) ASC, created_at ASC
                    LIMIT ?
                    """,
                    (*scope_params, safe_limit),
                ).fetchall()

        items = [
            {
                "id": str(row["id"] or ""),
                "name": str(row["name"] or ""),
                "measurement_unit": HOMEMADE_STOCK_UNIT,
                "usage_scope": _normalize_homemade_usage_scope(
                    str(row["usage_scope"] or "both") if "usage_scope" in row.keys() else "both"
                ),
                "usage_scope_label": _homemade_usage_scope_label(
                    str(row["usage_scope"] or "both") if "usage_scope" in row.keys() else "both"
                ),
                "preparation_date": str(row["preparation_date"] or "") or None,
                "created_at": str(row["created_at"] or ""),
                "updated_at": str(row["updated_at"] or ""),
            }
            for row in rows
        ]
        return {"items": items, "total_count": len(items)}

    def add_homemade_stock(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        payload: HomemadeStockWritePayload,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        timestamp = _iso_now()
        operation = str(payload.operation or "set").strip().lower()
        if operation not in {"add", "set"}:
            raise ValueError("Operazione stock non valida.")
        if operation == "add" and payload.quantity <= 0:
            raise ValueError("Per aggiungere stock la quantita deve essere maggiore di zero.")
        if operation == "set" and payload.quantity < 0:
            raise ValueError("La quantita stock non puo essere negativa.")

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_homemade_stock_warehouse_row(connection, warehouse_id)
                recipe_row = self._read_homemade_recipe_row(connection, payload.recipe_id)
                recipe_name = str(recipe_row["name"] or "").strip()
                recipe_lookup = str(recipe_row["name_lookup"] or _normalize_lookup(recipe_name))
                measurement_unit = HOMEMADE_STOCK_UNIT

                item_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_homemade_stock_items
                    WHERE warehouse_id = ? AND (recipe_id = ? OR recipe_lookup = ?)
                    LIMIT 1
                    """,
                    (warehouse_id, str(recipe_row["id"] or ""), recipe_lookup),
                ).fetchone()

                current_quantity = float(item_row["quantity"] or 0) if item_row is not None else 0.0
                next_quantity = float(payload.quantity) if operation == "set" else current_quantity + float(payload.quantity)

                if item_row is None and next_quantity <= 0:
                    return self.get_homemade_stock_warehouse_detail(session, warehouse_id)

                if item_row is None:
                    connection.execute(
                        """
                        INSERT INTO tenant_homemade_stock_items (
                            id,
                            warehouse_id,
                            recipe_id,
                            recipe_name,
                            recipe_lookup,
                            measurement_unit,
                            quantity,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"homemade_stock_item_{uuid.uuid4().hex}",
                            warehouse_id,
                            str(recipe_row["id"] or ""),
                            recipe_name,
                            recipe_lookup,
                            measurement_unit,
                            next_quantity,
                            timestamp,
                            timestamp,
                        ),
                    )
                elif next_quantity <= 0:
                    connection.execute(
                        "DELETE FROM tenant_homemade_stock_items WHERE id = ?",
                        (str(item_row["id"]),),
                    )
                else:
                    connection.execute(
                        """
                        UPDATE tenant_homemade_stock_items
                        SET
                            recipe_id = ?,
                            recipe_name = ?,
                            recipe_lookup = ?,
                            measurement_unit = ?,
                            quantity = ?,
                            updated_at = ?
                        WHERE id = ?
                        """,
                        (
                            str(recipe_row["id"] or ""),
                            recipe_name,
                            recipe_lookup,
                            measurement_unit,
                            next_quantity,
                            timestamp,
                            str(item_row["id"]),
                        ),
                    )

                self._record_homemade_stock_movement(
                    connection,
                    session=session,
                    warehouse_row=warehouse_row,
                    recipe_id=str(recipe_row["id"] or ""),
                    recipe_name=recipe_name,
                    recipe_lookup=recipe_lookup,
                    quantity_before=current_quantity,
                    quantity_after=next_quantity,
                    movement_type=f"stock_{operation}",
                    timestamp=timestamp,
                )

                connection.execute(
                    """
                    UPDATE tenant_homemade_stock_warehouses
                    SET updated_at = ?
                    WHERE id = ?
                    """,
                    (timestamp, warehouse_id),
                )
                connection.commit()

        return self.get_homemade_stock_warehouse_detail(session, warehouse_id)

    def get_inventory_warehouse_consumption(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        *,
        start_session_id: str | None = None,
        end_session_id: str | None = None,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        with self._connect_tenant_database(session.database_path) as connection:
            warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
            session_rows = connection.execute(
                """
                SELECT *
                FROM tenant_inventory_sessions
                WHERE warehouse_id = ?
                ORDER BY inventory_date DESC, created_at DESC
                """,
                (warehouse_id,),
            ).fetchall()
            serialized_sessions = [self._serialize_inventory_session_row(row) for row in session_rows]
            if not session_rows:
                return {
                    "warehouse": self._serialize_inventory_warehouse_row(warehouse_row),
                    "sessions": serialized_sessions,
                    "start_session": None,
                    "end_session": None,
                    "period_days": 0,
                    "total_opening_units": 0,
                    "total_incoming_units": 0,
                    "total_closing_units": 0,
                    "total_consumed_units": 0,
                    "items": [],
                    "calculation_mode": "consumo = inventario iniziale + entrate registrate - inventario finale",
                }

            if start_session_id and end_session_id and start_session_id == end_session_id:
                raise ValueError("Per confrontare i consumi servono due inventari diversi dello stesso magazzino.")

            if start_session_id:
                start_session_row = self._read_inventory_session_row(connection, start_session_id)
            elif len(session_rows) >= 2:
                start_session_row = session_rows[1]
            else:
                start_session_row = None

            if end_session_id:
                end_session_row = self._read_inventory_session_row(connection, end_session_id)
            else:
                end_session_row = session_rows[0]

            if start_session_row is not None and str(start_session_row["warehouse_id"] or "") != warehouse_id:
                raise ValueError("L'inventario iniziale selezionato non appartiene a questo magazzino.")
            if end_session_row is not None and str(end_session_row["warehouse_id"] or "") != warehouse_id:
                raise ValueError("L'inventario finale selezionato non appartiene a questo magazzino.")

            if start_session_row is None or end_session_row is None:
                return {
                    "warehouse": self._serialize_inventory_warehouse_row(warehouse_row),
                    "sessions": serialized_sessions,
                    "start_session": self._serialize_inventory_session_row(start_session_row) if start_session_row is not None else None,
                    "end_session": self._serialize_inventory_session_row(end_session_row) if end_session_row is not None else None,
                    "period_days": 0,
                    "total_opening_units": 0,
                    "total_incoming_units": 0,
                    "total_closing_units": float(end_session_row["total_equivalent_units"] or 0) if end_session_row is not None else 0,
                    "total_consumed_units": 0,
                    "items": [],
                    "calculation_mode": "consumo = inventario iniziale + entrate registrate - inventario finale",
                }

            try:
                start_inventory_date = date.fromisoformat(str(start_session_row["inventory_date"]))
                end_inventory_date = date.fromisoformat(str(end_session_row["inventory_date"]))
            except ValueError as exc:
                raise ValueError("Non riesco a leggere correttamente le date degli inventari magazzino.") from exc

            if start_inventory_date > end_inventory_date:
                start_session_row, end_session_row = end_session_row, start_session_row
                start_inventory_date, end_inventory_date = end_inventory_date, start_inventory_date

            period_days = float(
                len(
                    self._inventory_bar_operational_days_for_periods(
                        connection,
                        [(start_inventory_date.isoformat(), end_inventory_date.isoformat())],
                    )
                )
            )
            opening_map = self._aggregate_inventory_session_items(connection, str(start_session_row["id"]))
            closing_map = self._aggregate_inventory_session_items(connection, str(end_session_row["id"]))

            period_start_dt = self._parse_inventory_datetime(start_session_row["created_at"]) or datetime.combine(
                start_inventory_date,
                time.min,
                tzinfo=timezone.utc,
            )
            period_end_dt = self._parse_inventory_datetime(end_session_row["created_at"]) or datetime.combine(
                end_inventory_date + timedelta(days=1),
                time.min,
                tzinfo=timezone.utc,
            )
            movement_rows = connection.execute(
                """
                SELECT *
                FROM tenant_inventory_movements
                WHERE warehouse_id = ?
                  AND (
                    movement_kind = 'in'
                    OR (movement_kind = 'out' AND source_type = 'warehouse_transfer_out')
                  )
                ORDER BY occurred_at ASC, created_at ASC
                """,
                (warehouse_id,),
            ).fetchall()

            incoming_map: dict[tuple[str, str], dict[str, object]] = {}
            outgoing_transfer_map: dict[tuple[str, str], dict[str, object]] = {}
            for row in movement_rows:
                occurred_at = self._parse_inventory_datetime(row["occurred_at"])
                if occurred_at is None or occurred_at <= period_start_dt or occurred_at > period_end_dt:
                    continue
                key = (str(row["product_lookup"] or ""), str(row["supplier_lookup"] or ""))
                target_map = (
                    outgoing_transfer_map
                    if str(row["source_type"] or "") == "warehouse_transfer_out"
                    else incoming_map
                )
                bucket = target_map.setdefault(
                    key,
                    {
                        "product_name": str(row["product_name"] or ""),
                        "supplier_name": str(row["supplier_name"] or ""),
                        "total_equivalent_units": 0.0,
                    },
                )
                bucket["total_equivalent_units"] = float(bucket["total_equivalent_units"] or 0) + float(row["equivalent_units"] or 0)

        item_keys = set(opening_map) | set(closing_map) | set(incoming_map) | set(outgoing_transfer_map)
        items: list[dict[str, object]] = []
        for key in item_keys:
            opening = opening_map.get(key)
            closing = closing_map.get(key)
            incoming = incoming_map.get(key)
            outgoing_transfer = outgoing_transfer_map.get(key)
            product_name = str((closing or opening or incoming or outgoing_transfer or {}).get("product_name") or "")
            supplier_name = str((closing or opening or incoming or outgoing_transfer or {}).get("supplier_name") or "")
            opening_units = float((opening or {}).get("total_equivalent_units") or 0)
            incoming_units = float((incoming or {}).get("total_equivalent_units") or 0)
            outgoing_transfer_units = float((outgoing_transfer or {}).get("total_equivalent_units") or 0)
            closing_units = float((closing or {}).get("total_equivalent_units") or 0)
            consumed_units = opening_units + incoming_units - outgoing_transfer_units - closing_units
            if opening_units == 0 and incoming_units == 0 and outgoing_transfer_units == 0 and closing_units == 0:
                continue
            items.append(
                {
                    "product_name": product_name,
                    "supplier_name": supplier_name,
                    "opening_units": round(opening_units, 2),
                    "incoming_units": round(incoming_units, 2),
                    "outgoing_transfer_units": round(outgoing_transfer_units, 2),
                    "closing_units": round(closing_units, 2),
                    "consumed_units": round(consumed_units, 2),
                }
            )

        items.sort(
            key=lambda item: (
                -float(item["consumed_units"]),
                str(item["supplier_name"]).lower(),
                str(item["product_name"]).lower(),
            )
        )
        return {
            "warehouse": self._serialize_inventory_warehouse_row(warehouse_row),
            "sessions": serialized_sessions,
            "start_session": self._serialize_inventory_session_row(start_session_row),
            "end_session": self._serialize_inventory_session_row(end_session_row),
            "period_days": period_days,
            "total_opening_units": round(sum(float(item["opening_units"]) for item in items), 2),
            "total_incoming_units": round(sum(float(item["incoming_units"]) for item in items), 2),
            "total_closing_units": round(sum(float(item["closing_units"]) for item in items), 2),
            "total_consumed_units": round(sum(float(item["consumed_units"]) for item in items), 2),
            "items": items,
            "calculation_mode": "consumo = inventario iniziale + entrate registrate - trasferimenti usciti - inventario finale",
        }

    def search_inventory_products(
        self,
        session: SessionIdentity,
        *,
        query: str | None,
        limit: int = 20,
    ) -> dict[str, object]:
        if not self._can_access_inventory_data_for_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Inventario o Consumi.")

        safe_limit = max(1, min(int(limit), 100))
        normalized_query = str(query or "").strip().lower()
        carton_barcode_aliases = tuple(
            alias for alias in _build_carton_barcode_aliases(normalized_query) if alias != normalized_query
        )
        with self._connect_tenant_database(session.database_path) as connection:
            if not self._tenant_table_exists(connection, "ordini_products"):
                return {"catalog_available": False, "items": []}

            product_columns = self._tenant_table_columns(connection, "ordini_products")
            select_units_per_pack = "units_per_pack" if "units_per_pack" in product_columns else "NULL AS units_per_pack"
            select_product_code = "product_code" if "product_code" in product_columns else "NULL AS product_code"
            select_price = "final_price_vat" if "final_price_vat" in product_columns else "NULL AS final_price_vat"

            if normalized_query:
                like_value = f"%{normalized_query}%"
                prefix_value = f"{normalized_query}%"
                carton_alias_placeholders = ", ".join("?" for _ in carton_barcode_aliases)
                carton_alias_filter = ""
                carton_alias_order = ""
                params: list[object] = [
                    like_value,
                    like_value,
                    like_value,
                    like_value,
                ]
                if carton_barcode_aliases:
                    carton_alias_filter = f"""
                        OR (
                            lower(lot_code) IN ('ct', 'cartone', 'cartoni', 'cassa', 'casse')
                            AND lower(COALESCE(product_code, '')) IN ({carton_alias_placeholders})
                        )
                    """
                    carton_alias_order = f"""
                            WHEN lower(lot_code) IN ('ct', 'cartone', 'cartoni', 'cassa', 'casse')
                              AND lower(COALESCE(product_code, '')) IN ({carton_alias_placeholders}) THEN 1
                    """
                    params.extend(carton_barcode_aliases)
                params.extend(
                    [
                        normalized_query,
                        *carton_barcode_aliases,
                        normalized_query,
                        prefix_value,
                        prefix_value,
                        safe_limit,
                    ]
                )
                rows = connection.execute(
                    f"""
                    SELECT
                        id,
                        product_name,
                        lot_code,
                        supplier_name,
                        {select_product_code},
                        {select_units_per_pack},
                        {select_price}
                    FROM ordini_products
                    WHERE active = 1
                      AND (
                        lower(product_name) LIKE ?
                        OR lower(supplier_name) LIKE ?
                        OR lower(lot_code) LIKE ?
                        OR lower(COALESCE(product_code, '')) LIKE ?
                        {carton_alias_filter}
                      )
                    ORDER BY
                        CASE
                            WHEN lower(COALESCE(product_code, '')) = ? THEN 0
                            {carton_alias_order}
                            WHEN lower(product_name) = ? THEN 2
                            WHEN lower(product_name) LIKE ? THEN 3
                            WHEN lower(supplier_name) LIKE ? THEN 4
                            ELSE 5
                        END,
                        lower(supplier_name) ASC,
                        lower(product_name) ASC,
                        lower(lot_code) ASC
                    LIMIT ?
                    """,
                    tuple(params),
                ).fetchall()
            else:
                rows = connection.execute(
                    f"""
                    SELECT
                        id,
                        product_name,
                        lot_code,
                        supplier_name,
                        {select_product_code},
                        {select_units_per_pack},
                        {select_price}
                    FROM ordini_products
                    WHERE active = 1
                    ORDER BY lower(supplier_name) ASC, lower(product_name) ASC, lower(lot_code) ASC
                    LIMIT ?
                    """,
                    (safe_limit,),
                ).fetchall()

        items = [
            {
                "id": int(row["id"]),
                "product_name": str(row["product_name"] or ""),
                "lot_code": str(row["lot_code"] or ""),
                "supplier_name": str(row["supplier_name"] or ""),
                "product_code": str(row["product_code"] or "") or None,
                "units_per_pack": float(row["units_per_pack"]) if row["units_per_pack"] is not None else None,
                "final_price_vat": float(row["final_price_vat"]) if row["final_price_vat"] is not None else None,
                "requires_units_per_pack": self._inventory_lot_requires_units_per_pack(row["lot_code"]),
            }
            for row in rows
        ]
        return {"catalog_available": True, "items": items}

    def list_inventory_product_variants(
        self,
        session: SessionIdentity,
        *,
        product_id: int,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        with self._connect_tenant_database(session.database_path) as connection:
            if not self._tenant_table_exists(connection, "ordini_products"):
                return {"catalog_available": False, "items": []}

            product_columns = self._tenant_table_columns(connection, "ordini_products")
            select_units_per_pack = "units_per_pack" if "units_per_pack" in product_columns else "NULL AS units_per_pack"
            select_product_code = "product_code" if "product_code" in product_columns else "NULL AS product_code"
            select_price = "final_price_vat" if "final_price_vat" in product_columns else "NULL AS final_price_vat"
            seed_row = connection.execute(
                f"""
                SELECT id, product_name, supplier_name
                FROM ordini_products
                WHERE id = ? AND active = 1
                LIMIT 1
                """,
                (product_id,),
            ).fetchone()
            if seed_row is None:
                raise KeyError("Prodotto non trovato nel catalogo attivo.")

            rows = connection.execute(
                f"""
                SELECT
                    id,
                    product_name,
                    lot_code,
                    supplier_name,
                    {select_product_code},
                    {select_units_per_pack},
                    {select_price}
                FROM ordini_products
                WHERE active = 1
                  AND lower(product_name) = lower(?)
                  AND lower(supplier_name) = lower(?)
                ORDER BY
                  CASE
                    WHEN lower(lot_code) IN ('bt', 'bottiglia') THEN 0
                    WHEN lower(lot_code) IN ('ct', 'cartone', 'cartoni', 'cassa', 'casse') THEN 1
                    WHEN lower(lot_code) IN ('pz', 'pezzo', 'pezzi') THEN 2
                    ELSE 3
                  END,
                  lower(lot_code) ASC,
                  id ASC
                """,
                (str(seed_row["product_name"] or ""), str(seed_row["supplier_name"] or "")),
            ).fetchall()

        items = [
            {
                "id": int(row["id"]),
                "product_name": str(row["product_name"] or ""),
                "lot_code": str(row["lot_code"] or ""),
                "supplier_name": str(row["supplier_name"] or ""),
                "product_code": str(row["product_code"] or "") or None,
                "units_per_pack": float(row["units_per_pack"]) if row["units_per_pack"] is not None else None,
                "final_price_vat": float(row["final_price_vat"]) if row["final_price_vat"] is not None else None,
                "requires_units_per_pack": self._inventory_lot_requires_units_per_pack(row["lot_code"]),
            }
            for row in rows
        ]
        return {"catalog_available": True, "items": items}

    def create_inventory_product(self, session: SessionIdentity, payload: InventoryProductCreatePayload) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        product_name = payload.product_name.strip()
        lot_code = payload.lot_code.strip()
        supplier_name = payload.supplier_name.strip()
        if not product_name or not lot_code or not supplier_name:
            raise ValueError("Compila nome prodotto, lotto e fornitore.")

        normalized_product_code = re.sub(r"\s+", "", str(payload.product_code or "").strip()) or None

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                if not self._tenant_table_exists(connection, "ordini_products"):
                    raise ValueError("Catalogo prodotti non disponibile per questo locale.")

                if normalized_product_code:
                    self._ensure_tenant_column(connection, "ordini_products", "product_code", "TEXT")
                product_columns = self._tenant_table_columns(connection, "ordini_products")

                existing_row = connection.execute(
                    """
                    SELECT *
                    FROM ordini_products
                    WHERE lower(product_name) = lower(?)
                      AND lower(lot_code) = lower(?)
                      AND lower(supplier_name) = lower(?)
                    LIMIT 1
                    """,
                    (product_name, lot_code, supplier_name),
                ).fetchone()

                if normalized_product_code and "product_code" in product_columns:
                    target_barcode_candidates = (
                        _build_carton_barcode_aliases(normalized_product_code)
                        if self._inventory_lot_requires_units_per_pack(lot_code)
                        else (normalized_product_code,)
                    )
                    conflicting_rows = connection.execute(
                        f"""
                        SELECT id, product_name, lot_code, supplier_name, product_code
                        FROM ordini_products
                        WHERE active = 1
                          AND lower(COALESCE(product_code, '')) IN ({", ".join("?" for _ in target_barcode_candidates)})
                        """,
                        tuple(target_barcode_candidates),
                    ).fetchall()
                    conflicting_row = next(
                        (
                            row
                            for row in conflicting_rows
                            if (existing_row is None or int(row["id"]) != int(existing_row["id"]))
                            and self._inventory_barcodes_conflict(
                                left_code=normalized_product_code,
                                left_lot_code=lot_code,
                                right_code=row["product_code"],
                                right_lot_code=row["lot_code"],
                            )
                        ),
                        None,
                    )
                    if conflicting_row is not None:
                        raise ValueError(
                            "Questo barcode e gia assegnato a "
                            f"{str(conflicting_row['product_name'] or '').strip()} · "
                            f"{str(conflicting_row['supplier_name'] or '').strip()} · "
                            f"{str(conflicting_row['lot_code'] or '').strip()}."
                        )

                metadata_values: dict[str, object] = {}
                for column_name, value in (
                    ("product_code", normalized_product_code),
                    ("final_price_vat", payload.final_price_vat),
                    ("vat_rate", payload.vat_rate),
                    ("weight_kg", payload.weight_kg),
                    ("unit_price_per_kg", payload.unit_price_per_kg),
                    ("category", payload.category.strip() if payload.category else None),
                    ("notes", payload.notes.strip() if payload.notes else None),
                    ("units_per_pack", payload.units_per_pack),
                    ("liters_per_unit", payload.liters_per_unit),
                ):
                    if column_name in product_columns and value is not None:
                        metadata_values[column_name] = value

                created = False
                if existing_row is None:
                    created = True
                    insert_columns = ["product_name", "lot_code", "supplier_name"]
                    insert_values: list[object] = [product_name, lot_code, supplier_name]
                    for column_name, value in metadata_values.items():
                        insert_columns.append(column_name)
                        insert_values.append(value)
                    if "active" in product_columns:
                        insert_columns.append("active")
                        insert_values.append(1)
                    placeholders = ", ".join("?" for _ in insert_columns)
                    connection.execute(
                        f"INSERT INTO ordini_products ({', '.join(insert_columns)}) VALUES ({placeholders})",
                        tuple(insert_values),
                    )
                else:
                    assignments: list[str] = []
                    values: list[object] = []
                    if "active" in product_columns:
                        assignments.append("active = 1")
                    for column_name, value in metadata_values.items():
                        assignments.append(f"{column_name} = ?")
                        values.append(value)
                    if "updated_at" in product_columns:
                        assignments.append("updated_at = CURRENT_TIMESTAMP")
                    if assignments:
                        connection.execute(
                            f"UPDATE ordini_products SET {', '.join(assignments)} WHERE id = ?",
                            tuple(values + [int(existing_row["id"])]),
                        )
                connection.commit()

                updated_columns = self._tenant_table_columns(connection, "ordini_products")
                select_units_per_pack = "units_per_pack" if "units_per_pack" in updated_columns else "NULL AS units_per_pack"
                select_product_code = "product_code" if "product_code" in updated_columns else "NULL AS product_code"
                select_price = "final_price_vat" if "final_price_vat" in updated_columns else "NULL AS final_price_vat"
                updated_row = connection.execute(
                    f"""
                    SELECT
                        id,
                        product_name,
                        lot_code,
                        supplier_name,
                        {select_product_code},
                        {select_units_per_pack},
                        {select_price}
                    FROM ordini_products
                    WHERE lower(product_name) = lower(?)
                      AND lower(lot_code) = lower(?)
                      AND lower(supplier_name) = lower(?)
                    LIMIT 1
                    """,
                    (product_name, lot_code, supplier_name),
                ).fetchone()
                if updated_row is None:
                    raise KeyError("Prodotto non trovato dopo il salvataggio.")

        return {
            "ok": True,
            "created": created,
            "product": {
                "id": int(updated_row["id"]),
                "product_name": str(updated_row["product_name"] or ""),
                "lot_code": str(updated_row["lot_code"] or ""),
                "supplier_name": str(updated_row["supplier_name"] or ""),
                "product_code": str(updated_row["product_code"] or "") or None,
                "units_per_pack": float(updated_row["units_per_pack"]) if updated_row["units_per_pack"] is not None else None,
                "final_price_vat": float(updated_row["final_price_vat"]) if updated_row["final_price_vat"] is not None else None,
                "requires_units_per_pack": self._inventory_lot_requires_units_per_pack(updated_row["lot_code"]),
            },
        }

    def update_inventory_product_code(
        self,
        session: SessionIdentity,
        *,
        product_id: int,
        payload: InventoryProductCodeWritePayload,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        normalized_product_code = re.sub(r"\s+", "", str(payload.product_code or "").strip())
        if len(normalized_product_code) < 3:
            raise ValueError("Indicami un barcode valido prima di salvarlo.")

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                if not self._tenant_table_exists(connection, "ordini_products"):
                    raise ValueError("Catalogo prodotti non disponibile per questo locale.")

                self._ensure_tenant_column(connection, "ordini_products", "product_code", "TEXT")
                product_columns = self._tenant_table_columns(connection, "ordini_products")
                select_units_per_pack = "units_per_pack" if "units_per_pack" in product_columns else "NULL AS units_per_pack"
                select_price = "final_price_vat" if "final_price_vat" in product_columns else "NULL AS final_price_vat"

                product_row = connection.execute(
                    """
                    SELECT *
                    FROM ordini_products
                    WHERE id = ? AND active = 1
                    LIMIT 1
                    """,
                    (product_id,),
                ).fetchone()
                if product_row is None:
                    raise KeyError("Prodotto non trovato nel catalogo attivo.")

                target_lot_code = str(payload.lot_code or product_row["lot_code"] or "").strip()
                if not target_lot_code:
                    raise ValueError("Seleziona il lotto da associare prima di salvare il barcode.")
                target_barcode_candidates = (
                    _build_carton_barcode_aliases(normalized_product_code)
                    if self._inventory_lot_requires_units_per_pack(target_lot_code)
                    else (normalized_product_code,)
                )

                target_row = connection.execute(
                    """
                    SELECT *
                    FROM ordini_products
                    WHERE lower(product_name) = lower(?)
                      AND lower(supplier_name) = lower(?)
                      AND lower(lot_code) = lower(?)
                    LIMIT 1
                    """,
                    (
                        str(product_row["product_name"] or "").strip(),
                        str(product_row["supplier_name"] or "").strip(),
                        target_lot_code,
                    ),
                ).fetchone()
                target_product_id = int(target_row["id"]) if target_row is not None else product_id

                conflicting_rows = connection.execute(
                    f"""
                    SELECT id, product_name, lot_code, supplier_name, product_code
                    FROM ordini_products
                    WHERE active = 1
                      AND id != ?
                      AND lower(COALESCE(product_code, '')) IN ({", ".join("?" for _ in target_barcode_candidates)})
                    """,
                    (target_product_id, *target_barcode_candidates),
                ).fetchall()
                conflicting_row = next(
                    (
                        row
                        for row in conflicting_rows
                        if self._inventory_barcodes_conflict(
                            left_code=normalized_product_code,
                            left_lot_code=target_lot_code,
                            right_code=row["product_code"],
                            right_lot_code=row["lot_code"],
                        )
                    ),
                    None,
                )
                if conflicting_row is not None:
                    raise ValueError(
                        "Questo barcode e gia assegnato a "
                        f"{str(conflicting_row['product_name'] or '').strip()} · "
                        f"{str(conflicting_row['supplier_name'] or '').strip()} · "
                        f"{str(conflicting_row['lot_code'] or '').strip()}."
                    )

                created_variant = False
                if target_row is None:
                    created_variant = True
                    insert_columns = ["product_name", "lot_code", "supplier_name", "product_code"]
                    insert_values: list[object] = [
                        str(product_row["product_name"] or "").strip(),
                        target_lot_code,
                        str(product_row["supplier_name"] or "").strip(),
                        normalized_product_code,
                    ]
                    if "active" in product_columns:
                        insert_columns.append("active")
                        insert_values.append(1)
                    for column_name in ("vat_rate", "category", "notes", "liters_per_unit"):
                        if column_name in product_columns:
                            insert_columns.append(column_name)
                            insert_values.append(product_row[column_name])
                    if "units_per_pack" in product_columns:
                        inherited_units_per_pack = None
                        if self._inventory_lot_requires_units_per_pack(target_lot_code) and self._inventory_lot_requires_units_per_pack(
                            str(product_row["lot_code"] or "")
                        ):
                            inherited_units_per_pack = product_row["units_per_pack"]
                        insert_columns.append("units_per_pack")
                        insert_values.append(inherited_units_per_pack)
                    placeholders = ", ".join("?" for _ in insert_columns)
                    connection.execute(
                        f"INSERT INTO ordini_products ({', '.join(insert_columns)}) VALUES ({placeholders})",
                        tuple(insert_values),
                    )
                else:
                    assignments = ["product_code = ?"]
                    params: list[object] = [normalized_product_code]
                    if "active" in product_columns:
                        assignments.append("active = 1")
                    if "updated_at" in product_columns:
                        assignments.append("updated_at = CURRENT_TIMESTAMP")
                    connection.execute(
                        f"UPDATE ordini_products SET {', '.join(assignments)} WHERE id = ?",
                        tuple(params + [target_product_id]),
                    )
                connection.commit()

                updated_row = connection.execute(
                    f"""
                    SELECT
                        id,
                        product_name,
                        lot_code,
                        supplier_name,
                        product_code,
                        {select_units_per_pack},
                        {select_price}
                    FROM ordini_products
                    WHERE lower(product_name) = lower(?)
                      AND lower(supplier_name) = lower(?)
                      AND lower(lot_code) = lower(?)
                    LIMIT 1
                    """,
                    (
                        str(product_row["product_name"] or "").strip(),
                        str(product_row["supplier_name"] or "").strip(),
                        target_lot_code,
                    ),
                ).fetchone()
                if updated_row is None:
                    raise KeyError("Prodotto non trovato dopo il salvataggio del barcode.")

        return {
            "ok": True,
            "created_variant": created_variant,
            "product": {
                "id": int(updated_row["id"]),
                "product_name": str(updated_row["product_name"] or ""),
                "lot_code": str(updated_row["lot_code"] or ""),
                "supplier_name": str(updated_row["supplier_name"] or ""),
                "product_code": str(updated_row["product_code"] or "") or None,
                "units_per_pack": float(updated_row["units_per_pack"]) if updated_row["units_per_pack"] is not None else None,
                "final_price_vat": float(updated_row["final_price_vat"]) if updated_row["final_price_vat"] is not None else None,
                "requires_units_per_pack": self._inventory_lot_requires_units_per_pack(updated_row["lot_code"]),
            },
        }

    def list_inventory_snapshots(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        with self._connect_tenant_database(session.database_path) as connection:
            rows = connection.execute(
                """
                SELECT *
                FROM tenant_inventory_snapshots
                ORDER BY snapshot_date DESC, created_at DESC
                """
            ).fetchall()

        snapshots = [self._serialize_inventory_snapshot_row(row) for row in rows]
        return {"snapshots": snapshots, "total_count": len(snapshots)}

    def create_inventory_snapshot(
        self,
        session: SessionIdentity,
        payload: InventorySnapshotCreatePayload,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        snapshot_date = self._normalize_inventory_snapshot_date(payload.snapshot_date)
        timestamp = _iso_now()
        actor_name = (session.user_name or session.username or session.user_email or "Utente locale").strip()
        snapshot_id = f"inventory_snapshot_{uuid.uuid4().hex}"
        replaced_existing = False

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                existing_row = connection.execute(
                    "SELECT id FROM tenant_inventory_snapshots WHERE snapshot_date = ? LIMIT 1",
                    (snapshot_date,),
                ).fetchone()
                if existing_row is not None:
                    replaced_existing = True
                    connection.execute(
                        "DELETE FROM tenant_inventory_snapshots WHERE id = ?",
                        (str(existing_row["id"]),),
                    )

                warehouse_rows = connection.execute(
                    """
                    SELECT *
                    FROM tenant_inventory_warehouses
                    ORDER BY lower(name) ASC, created_at ASC
                    """
                ).fetchall()
                item_rows = connection.execute(
                    """
                    SELECT
                        items.*,
                        warehouses.name AS warehouse_name
                    FROM tenant_inventory_stock_items AS items
                    JOIN tenant_inventory_warehouses AS warehouses
                        ON warehouses.id = items.warehouse_id
                    ORDER BY lower(warehouses.name) ASC, lower(items.supplier_name) ASC, lower(items.product_name) ASC
                    """
                ).fetchall()

                item_ids = [str(row["id"]) for row in item_rows]
                lots_by_item: dict[str, list[sqlite3.Row]] = {item_id: [] for item_id in item_ids}
                if item_ids:
                    placeholders = ", ".join("?" for _ in item_ids)
                    lot_rows = connection.execute(
                        f"""
                        SELECT *
                        FROM tenant_inventory_stock_lots
                        WHERE item_id IN ({placeholders})
                        ORDER BY lower(lot_code) ASC, created_at ASC
                        """,
                        tuple(item_ids),
                    ).fetchall()
                    for row in lot_rows:
                        lots_by_item.setdefault(str(row["item_id"]), []).append(row)

                total_equivalent_units = sum(float(row["total_equivalent_units"] or 0) for row in item_rows)
                connection.execute(
                    """
                    INSERT INTO tenant_inventory_snapshots (
                        id,
                        snapshot_date,
                        label,
                        created_by_user_id,
                        created_by_name,
                        total_warehouses,
                        total_products,
                        total_equivalent_units,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        snapshot_id,
                        snapshot_date,
                        f"Inventario del {snapshot_date}",
                        session.user_id,
                        actor_name,
                        len(warehouse_rows),
                        len(item_rows),
                        total_equivalent_units,
                        timestamp,
                        timestamp,
                    ),
                )

                for item_row in item_rows:
                    snapshot_item_id = f"inventory_snapshot_item_{uuid.uuid4().hex}"
                    connection.execute(
                        """
                        INSERT INTO tenant_inventory_snapshot_items (
                            id,
                            snapshot_id,
                            warehouse_id,
                            warehouse_name,
                            product_lookup,
                            supplier_lookup,
                            product_name,
                            supplier_name,
                            total_equivalent_units,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            snapshot_item_id,
                            snapshot_id,
                            str(item_row["warehouse_id"] or ""),
                            str(item_row["warehouse_name"] or ""),
                            str(item_row["product_lookup"] or ""),
                            str(item_row["supplier_lookup"] or ""),
                            str(item_row["product_name"] or ""),
                            str(item_row["supplier_name"] or ""),
                            float(item_row["total_equivalent_units"] or 0),
                            timestamp,
                            timestamp,
                        ),
                    )
                    for lot_row in lots_by_item.get(str(item_row["id"]), []):
                        connection.execute(
                            """
                            INSERT INTO tenant_inventory_snapshot_lots (
                                id,
                                snapshot_item_id,
                                lot_code,
                                lot_lookup,
                                quantity,
                                units_per_pack,
                                equivalent_units,
                                created_at,
                                updated_at
                            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                            """,
                            (
                                f"inventory_snapshot_lot_{uuid.uuid4().hex}",
                                snapshot_item_id,
                                str(lot_row["lot_code"] or ""),
                                str(lot_row["lot_lookup"] or ""),
                                float(lot_row["quantity"] or 0),
                                float(lot_row["units_per_pack"]) if lot_row["units_per_pack"] is not None else None,
                                float(lot_row["equivalent_units"] or 0),
                                timestamp,
                                timestamp,
                            ),
                        )

                connection.commit()
                snapshot_row = self._read_inventory_snapshot_row(connection, snapshot_id)

        return {
            "snapshot": self._serialize_inventory_snapshot_row(snapshot_row),
            "replaced_existing": replaced_existing,
        }

    def get_inventory_consumption_overview(
        self,
        session: SessionIdentity,
        *,
        start_snapshot_id: str | None = None,
        end_snapshot_id: str | None = None,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        with self._connect_tenant_database(session.database_path) as connection:
            snapshot_rows = connection.execute(
                """
                SELECT *
                FROM tenant_inventory_snapshots
                ORDER BY snapshot_date DESC, created_at DESC
                """
            ).fetchall()
            serialized_snapshots = [self._serialize_inventory_snapshot_row(row) for row in snapshot_rows]
            if not snapshot_rows:
                return {
                    "snapshots": serialized_snapshots,
                    "start_snapshot": None,
                    "end_snapshot": None,
                    "period_days": 0,
                    "total_opening_units": 0,
                    "total_incoming_units": 0,
                    "total_closing_units": 0,
                    "total_consumed_units": 0,
                    "incoming_data_gaps": [],
                    "items": [],
                    "calculation_mode": "consumo = inventario iniziale + ordini confermati - inventario finale",
                }

            if start_snapshot_id and end_snapshot_id and start_snapshot_id == end_snapshot_id:
                raise ValueError("Per confrontare i consumi servono due fotografie diverse.")

            if start_snapshot_id:
                start_snapshot_row = self._read_inventory_snapshot_row(connection, start_snapshot_id)
            elif len(snapshot_rows) >= 2:
                start_snapshot_row = snapshot_rows[1]
            else:
                start_snapshot_row = None

            if end_snapshot_id:
                end_snapshot_row = self._read_inventory_snapshot_row(connection, end_snapshot_id)
            else:
                end_snapshot_row = snapshot_rows[0]

            if start_snapshot_row is None or end_snapshot_row is None:
                return {
                    "snapshots": serialized_snapshots,
                    "start_snapshot": self._serialize_inventory_snapshot_row(start_snapshot_row) if start_snapshot_row is not None else None,
                    "end_snapshot": self._serialize_inventory_snapshot_row(end_snapshot_row) if end_snapshot_row is not None else None,
                    "period_days": 0,
                    "total_opening_units": 0,
                    "total_incoming_units": 0,
                    "total_closing_units": float(end_snapshot_row["total_equivalent_units"] or 0) if end_snapshot_row is not None else 0,
                    "total_consumed_units": 0,
                    "incoming_data_gaps": [],
                    "items": [],
                    "calculation_mode": "consumo = inventario iniziale + ordini confermati - inventario finale",
                }

            try:
                start_snapshot_date = date.fromisoformat(str(start_snapshot_row["snapshot_date"]))
                end_snapshot_date = date.fromisoformat(str(end_snapshot_row["snapshot_date"]))
            except ValueError as exc:
                raise ValueError("Non riesco a leggere correttamente le date delle fotografie inventario.") from exc

            if start_snapshot_date > end_snapshot_date:
                start_snapshot_row, end_snapshot_row = end_snapshot_row, start_snapshot_row
                start_snapshot_date, end_snapshot_date = end_snapshot_date, start_snapshot_date

            period_days = float(
                len(
                    self._inventory_bar_operational_days_for_periods(
                        connection,
                        [(start_snapshot_date.isoformat(), end_snapshot_date.isoformat())],
                    )
                )
            )
            period_start_dt = datetime.combine(start_snapshot_date, time.min, tzinfo=timezone.utc)
            period_end_dt = datetime.combine(end_snapshot_date + timedelta(days=1), time.min, tzinfo=timezone.utc)

            opening_map = self._aggregate_inventory_snapshot_items(connection, str(start_snapshot_row["id"]))
            closing_map = self._aggregate_inventory_snapshot_items(connection, str(end_snapshot_row["id"]))

            incoming_map: dict[tuple[str, str], dict[str, object]] = {}
            incoming_data_gaps: list[dict[str, str]] = []
            has_orders_schema = self._tenant_table_exists(connection, "ordini_items") and self._tenant_table_exists(connection, "ordini_batches")
            if has_orders_schema:
                product_columns = self._tenant_table_columns(connection, "ordini_products")
                select_product_units_per_pack = "products.units_per_pack" if "units_per_pack" in product_columns else "NULL"
                broad_start_date = start_snapshot_date.isoformat()
                broad_end_date = (end_snapshot_date + timedelta(days=1)).isoformat()
                order_rows = connection.execute(
                    f"""
                    SELECT
                        items.product_name,
                        items.supplier_name,
                        items.lot_code,
                        items.quantity,
                        COALESCE(items.units_per_pack, {select_product_units_per_pack}) AS resolved_units_per_pack,
                        batches.confirmed_at
                    FROM ordini_items AS items
                    JOIN ordini_batches AS batches
                        ON batches.id = items.batch_id
                    LEFT JOIN ordini_products AS products
                        ON products.id = items.product_id
                    WHERE batches.confirmed_at >= ? AND batches.confirmed_at < ?
                    ORDER BY batches.confirmed_at ASC, items.id ASC
                    """,
                    (broad_start_date, broad_end_date),
                ).fetchall()

                seen_gap_keys: set[tuple[str, str, str]] = set()
                for row in order_rows:
                    confirmed_at = self._parse_inventory_datetime(row["confirmed_at"])
                    if confirmed_at is None or confirmed_at < period_start_dt or confirmed_at >= period_end_dt:
                        continue
                    product_name = str(row["product_name"] or "").strip()
                    supplier_name = str(row["supplier_name"] or "").strip()
                    lot_code = str(row["lot_code"] or "").strip()
                    quantity = float(row["quantity"] or 0)
                    if quantity <= 0:
                        continue
                    resolved_units_per_pack = (
                        float(row["resolved_units_per_pack"]) if row["resolved_units_per_pack"] is not None else None
                    )
                    try:
                        incoming_units = self._inventory_effective_units(
                            lot_code=lot_code,
                            quantity=quantity,
                            units_per_pack=resolved_units_per_pack,
                        )
                    except ValueError:
                        incoming_units = quantity
                        gap_key = (product_name, supplier_name, lot_code)
                        if gap_key not in seen_gap_keys:
                            seen_gap_keys.add(gap_key)
                            incoming_data_gaps.append(
                                {
                                    "product_name": product_name,
                                    "supplier_name": supplier_name,
                                    "lot_code": lot_code,
                                }
                            )

                    key = (_normalize_lookup(product_name), _normalize_lookup(supplier_name))
                    bucket = incoming_map.setdefault(
                        key,
                        {
                            "product_name": product_name,
                            "supplier_name": supplier_name,
                            "total_equivalent_units": 0.0,
                        },
                    )
                    bucket["total_equivalent_units"] = float(bucket["total_equivalent_units"] or 0) + incoming_units

            item_keys = set(opening_map) | set(closing_map) | set(incoming_map)
            items: list[dict[str, object]] = []
            for key in item_keys:
                opening = opening_map.get(key)
                closing = closing_map.get(key)
                incoming = incoming_map.get(key)
                product_name = str(
                    (closing or opening or incoming or {}).get("product_name") or ""
                )
                supplier_name = str(
                    (closing or opening or incoming or {}).get("supplier_name") or ""
                )
                opening_units = float((opening or {}).get("total_equivalent_units") or 0)
                incoming_units = float((incoming or {}).get("total_equivalent_units") or 0)
                closing_units = float((closing or {}).get("total_equivalent_units") or 0)
                consumed_units = opening_units + incoming_units - closing_units
                if opening_units == 0 and incoming_units == 0 and closing_units == 0:
                    continue
                items.append(
                    {
                        "product_name": product_name,
                        "supplier_name": supplier_name,
                        "opening_units": round(opening_units, 2),
                        "incoming_units": round(incoming_units, 2),
                        "closing_units": round(closing_units, 2),
                        "consumed_units": round(consumed_units, 2),
                    }
                )

        items.sort(
            key=lambda item: (
                -float(item["consumed_units"]),
                str(item["supplier_name"]).lower(),
                str(item["product_name"]).lower(),
            )
        )
        total_opening_units = round(sum(float(item["opening_units"]) for item in items), 2)
        total_incoming_units = round(sum(float(item["incoming_units"]) for item in items), 2)
        total_closing_units = round(sum(float(item["closing_units"]) for item in items), 2)
        total_consumed_units = round(sum(float(item["consumed_units"]) for item in items), 2)
        return {
            "snapshots": serialized_snapshots,
            "start_snapshot": self._serialize_inventory_snapshot_row(start_snapshot_row),
            "end_snapshot": self._serialize_inventory_snapshot_row(end_snapshot_row),
            "period_days": period_days,
            "total_opening_units": total_opening_units,
            "total_incoming_units": total_incoming_units,
            "total_closing_units": total_closing_units,
            "total_consumed_units": total_consumed_units,
            "incoming_data_gaps": incoming_data_gaps,
            "items": items,
            "calculation_mode": "consumo = inventario iniziale + ordini confermati - inventario finale",
        }

    def get_inventory_warehouse_detail(self, session: SessionIdentity, warehouse_id: str) -> dict[str, object]:
        if not self._can_access_inventory_data_for_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Inventario o Consumi.")

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                timestamp = _iso_now()
                warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
                normalized = self._normalize_inventory_warehouse_stock_lots(connection, warehouse_id, timestamp=timestamp)
                if normalized:
                    warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
                    connection.commit()
                current_item_rows, current_lots_by_item = self._load_inventory_stock_items(connection, warehouse_id)
                latest_session_row = self._read_inventory_latest_session_row(connection, warehouse_id)
                latest_inventory = (
                    self._serialize_inventory_session_row(latest_session_row)
                    if latest_session_row is not None
                    else None
                )
                real_item_rows: list[sqlite3.Row] = []
                real_lots_by_item: dict[str, list[dict[str, object]]] = {}
                if latest_session_row is not None:
                    real_item_rows, real_lots_by_item = self._load_inventory_session_items(connection, str(latest_session_row["id"]))
                incoming_since_latest = self._sum_inventory_movements_since(
                    connection,
                    warehouse_id,
                    start_value=latest_session_row["created_at"] if latest_session_row is not None else None,
                ) if latest_session_row is not None else 0.0

        current_items = [
            self._serialize_inventory_stock_item_row(row, current_lots_by_item.get(str(row["id"]), []))
            for row in current_item_rows
        ]
        real_items = [
            self._serialize_inventory_stock_item_row(row, real_lots_by_item.get(str(row["id"]), []))
            for row in real_item_rows
        ]
        comparison_items = self._build_inventory_item_comparison(
            current_items=current_items,
            real_items=real_items,
        )
        return {
            "warehouse": self._serialize_inventory_warehouse_row(warehouse_row),
            "items": current_items,
            "current_items": current_items,
            "real_items": real_items,
            "comparison_items": comparison_items,
            "latest_inventory": latest_inventory,
            "total_products": len(current_items),
            "total_equivalent_units": sum(float(item["total_equivalent_units"]) for item in current_items),
            "theoretical_total_equivalent_units": sum(float(item["total_equivalent_units"]) for item in current_items),
            "real_total_equivalent_units": sum(float(item["total_equivalent_units"]) for item in real_items),
            "incoming_since_latest_inventory_equivalent_units": incoming_since_latest,
        }

    def add_inventory_stock(
        self,
        session: SessionIdentity,
        warehouse_id: str,
        payload: InventoryStockWritePayload,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        timestamp = _iso_now()
        saved_session_row: sqlite3.Row | None = None
        saved_session_replaced = False
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
                if not self._tenant_table_exists(connection, "ordini_products"):
                    raise ValueError("Catalogo prodotti non disponibile per questo locale.")

                product_columns = self._tenant_table_columns(connection, "ordini_products")
                select_units_per_pack = "units_per_pack" if "units_per_pack" in product_columns else "NULL AS units_per_pack"
                product_row = connection.execute(
                    f"""
                    SELECT id, product_name, lot_code, supplier_name, {select_units_per_pack}
                    FROM ordini_products
                    WHERE id = ? AND active = 1
                    LIMIT 1
                    """,
                    (payload.product_id,),
                ).fetchone()
                if product_row is None:
                    raise KeyError("Prodotto non trovato nel catalogo attivo.")

                seed_product_name = str(product_row["product_name"] or "").strip()
                seed_supplier_name = str(product_row["supplier_name"] or "").strip()
                seed_lot_code = str(product_row["lot_code"] or "").strip()
                requested_lot_code = str(payload.lot_code or "").strip()
                lot_code = requested_lot_code or seed_lot_code
                if not lot_code:
                    raise ValueError("Seleziona un lotto per il conteggio inventario.")
                matched_variant_row = connection.execute(
                    f"""
                    SELECT id, product_name, lot_code, supplier_name, {select_units_per_pack}
                    FROM ordini_products
                    WHERE active = 1
                      AND lower(product_name) = lower(?)
                      AND lower(supplier_name) = lower(?)
                      AND lower(lot_code) = lower(?)
                    LIMIT 1
                    """,
                    (seed_product_name, seed_supplier_name, lot_code),
                ).fetchone()
                resolved_product_row = matched_variant_row if matched_variant_row is not None else product_row
                product_name = str(resolved_product_row["product_name"] or "").strip() or seed_product_name
                supplier_name = str(resolved_product_row["supplier_name"] or "").strip() or seed_supplier_name
                product_lookup = _normalize_lookup(product_name)
                supplier_lookup = _normalize_lookup(supplier_name)
                lot_lookup = _normalize_lookup(lot_code)
                if matched_variant_row is not None:
                    catalog_units_per_pack = (
                        float(matched_variant_row["units_per_pack"])
                        if matched_variant_row["units_per_pack"] is not None
                        else None
                    )
                elif _normalize_lookup(lot_code) == _normalize_lookup(seed_lot_code):
                    catalog_units_per_pack = float(product_row["units_per_pack"]) if product_row["units_per_pack"] is not None else None
                else:
                    catalog_units_per_pack = None
                override_units_per_pack = float(payload.units_per_pack) if payload.units_per_pack is not None else None
                units_per_pack = override_units_per_pack if override_units_per_pack is not None else catalog_units_per_pack
                operation = str(payload.operation or "add").strip().lower()
                if operation not in {"add", "set"}:
                    raise ValueError("Operazione inventario non valida.")
                if operation == "add" and payload.quantity <= 0:
                    raise ValueError("Per aggiungere stock la quantita deve essere maggiore di zero.")
                if operation == "set" and payload.quantity < 0:
                    raise ValueError("La quantita rilevata non puo essere negativa.")
                if (
                    payload.quantity > 0
                    and self._inventory_lot_requires_units_per_pack(lot_code)
                    and (units_per_pack is None or units_per_pack <= 0)
                ):
                    raise ValueError("Per questo lotto devo sapere quante unita contiene il pack.")

                target_equivalent_units = self._inventory_effective_units(
                    lot_code=lot_code,
                    quantity=payload.quantity,
                    units_per_pack=units_per_pack,
                )

                if override_units_per_pack is not None and "units_per_pack" in product_columns and matched_variant_row is not None:
                    connection.execute(
                        """
                        UPDATE ordini_products
                        SET units_per_pack = ?
                        WHERE id = ?
                        """,
                        (override_units_per_pack, int(matched_variant_row["id"])),
                    )

                item_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_inventory_stock_items
                    WHERE warehouse_id = ? AND product_lookup = ? AND supplier_lookup = ?
                    LIMIT 1
                    """,
                    (warehouse_id, product_lookup, supplier_lookup),
                ).fetchone()
                current_item_total = float(item_row["total_equivalent_units"] or 0) if item_row is not None else 0.0
                item_id = str(item_row["id"]) if item_row is not None else f"inventory_item_{uuid.uuid4().hex}"

                lot_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_inventory_stock_lots
                    WHERE item_id = ? AND lot_lookup = ?
                    LIMIT 1
                    """,
                    (item_id, lot_lookup),
                ).fetchone()
                current_lot_quantity = float(lot_row["quantity"] or 0) if lot_row is not None else 0.0
                current_lot_equivalent_units = float(lot_row["equivalent_units"] or 0) if lot_row is not None else 0.0

                if operation == "set":
                    next_lot_quantity = float(payload.quantity)
                    next_lot_equivalent_units = target_equivalent_units
                    delta_equivalent_units = next_lot_equivalent_units - current_lot_equivalent_units
                else:
                    next_lot_quantity = current_lot_quantity + float(payload.quantity)
                    next_lot_equivalent_units = current_lot_equivalent_units + target_equivalent_units
                    delta_equivalent_units = target_equivalent_units

                next_item_total = round(current_item_total + delta_equivalent_units, 6)
                if abs(next_item_total) < 1e-6:
                    next_item_total = 0.0

                if item_row is None and next_lot_quantity <= 0 and next_item_total <= 0:
                    return self.get_inventory_warehouse_detail(session, warehouse_id)

                if item_row is None:
                    item_id = f"inventory_item_{uuid.uuid4().hex}"
                    connection.execute(
                        """
                        INSERT INTO tenant_inventory_stock_items (
                            id,
                            warehouse_id,
                            product_id,
                            product_name,
                            product_lookup,
                            supplier_name,
                            supplier_lookup,
                            total_equivalent_units,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            item_id,
                            warehouse_id,
                            int(resolved_product_row["id"]),
                            product_name,
                            product_lookup,
                            supplier_name,
                            supplier_lookup,
                            max(next_item_total, 0.0),
                            timestamp,
                            timestamp,
                        ),
                    )
                else:
                    if next_item_total <= 0:
                        connection.execute(
                            "DELETE FROM tenant_inventory_stock_items WHERE id = ?",
                            (item_id,),
                        )
                    else:
                        connection.execute(
                            """
                            UPDATE tenant_inventory_stock_items
                            SET
                                product_id = ?,
                                product_name = ?,
                                supplier_name = ?,
                                total_equivalent_units = ?,
                                updated_at = ?
                            WHERE id = ?
                            """,
                            (
                                int(resolved_product_row["id"]),
                                product_name,
                                supplier_name,
                                max(next_item_total, 0.0),
                                timestamp,
                                item_id,
                            ),
                        )

                if next_item_total <= 0:
                    connection.execute(
                        "DELETE FROM tenant_inventory_stock_lots WHERE item_id = ?",
                        (item_id,),
                    )
                elif lot_row is None:
                    if next_lot_quantity > 0:
                        connection.execute(
                            """
                            INSERT INTO tenant_inventory_stock_lots (
                                id,
                                item_id,
                                lot_code,
                                lot_lookup,
                                quantity,
                                units_per_pack,
                                equivalent_units,
                                created_at,
                                updated_at
                            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                            """,
                            (
                                f"inventory_lot_{uuid.uuid4().hex}",
                                item_id,
                                lot_code,
                                lot_lookup,
                                next_lot_quantity,
                                units_per_pack,
                                next_lot_equivalent_units,
                                timestamp,
                                timestamp,
                            ),
                        )
                else:
                    if next_lot_quantity <= 0 or next_lot_equivalent_units <= 0:
                        connection.execute(
                            "DELETE FROM tenant_inventory_stock_lots WHERE id = ?",
                            (str(lot_row["id"]),),
                        )
                    else:
                        connection.execute(
                            """
                            UPDATE tenant_inventory_stock_lots
                            SET
                                quantity = ?,
                                units_per_pack = COALESCE(?, units_per_pack),
                                equivalent_units = ?,
                                updated_at = ?
                            WHERE id = ?
                            """,
                            (
                                next_lot_quantity,
                                units_per_pack,
                                next_lot_equivalent_units,
                                timestamp,
                                str(lot_row["id"]),
                            ),
                        )

                self._normalize_inventory_stock_item_lots(connection, item_id, timestamp=timestamp)

                remaining_lots = connection.execute(
                    "SELECT COUNT(*) AS total FROM tenant_inventory_stock_lots WHERE item_id = ?",
                    (item_id,),
                ).fetchone()
                if remaining_lots is not None and int(remaining_lots["total"] or 0) == 0:
                    connection.execute(
                        "DELETE FROM tenant_inventory_stock_items WHERE id = ?",
                        (item_id,),
                    )

                if operation == "add" and target_equivalent_units > 0:
                    connection.execute(
                        """
                        INSERT INTO tenant_inventory_movements (
                            id,
                            warehouse_id,
                            product_id,
                            product_name,
                            product_lookup,
                            supplier_name,
                            supplier_lookup,
                            lot_code,
                            lot_lookup,
                            quantity,
                            units_per_pack,
                            equivalent_units,
                            movement_kind,
                            source_type,
                            source_label,
                            occurred_at,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"inventory_movement_{uuid.uuid4().hex}",
                            warehouse_id,
                            int(resolved_product_row["id"]),
                            product_name,
                            product_lookup,
                            supplier_name,
                            supplier_lookup,
                            lot_code,
                            lot_lookup,
                            float(payload.quantity),
                            units_per_pack,
                            target_equivalent_units,
                            "in",
                            "manual_stock_add",
                            f"Carico manuale {warehouse_row['name']}",
                            timestamp,
                            timestamp,
                            timestamp,
                        ),
                    )

                connection.execute(
                    """
                    UPDATE tenant_inventory_warehouses
                    SET updated_at = ?
                    WHERE id = ?
                    """,
                    (timestamp, warehouse_id),
                )
                if payload.save_inventory_session:
                    saved_session_row, saved_session_replaced = self._save_inventory_warehouse_session_from_stock(
                        connection,
                        session,
                        warehouse_id,
                        inventory_date=self._normalize_inventory_date(None),
                        timestamp=timestamp,
                    )
                connection.commit()

        detail = self.get_inventory_warehouse_detail(session, warehouse_id)
        if saved_session_row is not None:
            detail["inventory_session"] = self._serialize_inventory_session_row(saved_session_row)
            detail["inventory_session_replaced"] = saved_session_replaced
        return detail

    def transfer_inventory_stock(
        self,
        session: SessionIdentity,
        payload: InventoryTransferWritePayload,
    ) -> dict[str, object]:
        if not self._can_access_inventory(session):
            raise ValueError("Il tuo account non puo accedere a Inventario.")

        source_warehouse_id = str(payload.source_warehouse_id or "").strip()
        destination_warehouse_id = str(payload.destination_warehouse_id or "").strip()
        stock_item_id = str(payload.stock_item_id or "").strip()
        requested_lot_code = str(payload.lot_code or "").strip()
        requested_source_lot_code = str(payload.source_lot_code or payload.lot_code or "").strip()
        if not source_warehouse_id:
            raise ValueError("Seleziona il magazzino sorgente.")
        if not destination_warehouse_id:
            raise ValueError("Seleziona il magazzino di destinazione.")
        if source_warehouse_id == destination_warehouse_id:
            raise ValueError("Il magazzino sorgente e quello di destinazione devono essere diversi.")
        if not stock_item_id:
            raise ValueError("Seleziona il prodotto da spostare.")

        timestamp = _iso_now()
        occurred_at = self._normalize_inventory_movement_timestamp(payload.occurred_at) if payload.occurred_at else timestamp
        transfer_inventory_date = self._normalize_inventory_date(str(occurred_at or "")[:10])
        transfer_id = f"inventory_transfer_{uuid.uuid4().hex}"

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                source_warehouse_row = self._read_inventory_warehouse_row(connection, source_warehouse_id)
                destination_warehouse_row = self._read_inventory_warehouse_row(connection, destination_warehouse_id)

                source_item_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_inventory_stock_items
                    WHERE id = ? AND warehouse_id = ?
                    LIMIT 1
                    """,
                    (stock_item_id, source_warehouse_id),
                ).fetchone()
                if source_item_row is None:
                    raise ValueError("Nel magazzino sorgente non trovo questo prodotto.")

                lot_lookup = _normalize_lookup(requested_lot_code)
                source_lot_lookup = _normalize_lookup(requested_source_lot_code)
                if not lot_lookup or not source_lot_lookup:
                    raise ValueError("Seleziona il lotto da spostare.")

                source_lot_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_inventory_stock_lots
                    WHERE item_id = ? AND lot_lookup = ?
                    LIMIT 1
                    """,
                    (stock_item_id, source_lot_lookup),
                ).fetchone()
                if source_lot_row is None:
                    raise ValueError("Nel magazzino sorgente non trovo questo lotto.")

                source_lot_code = str(source_lot_row["lot_code"] or requested_source_lot_code).strip()
                lot_code = requested_lot_code or source_lot_code
                lot_lookup = _normalize_lookup(lot_code)
                product_id = int(source_item_row["product_id"]) if source_item_row["product_id"] is not None else None
                product_name = str(source_item_row["product_name"] or "").strip()
                supplier_name = str(source_item_row["supplier_name"] or "").strip()
                product_lookup = str(source_item_row["product_lookup"] or _normalize_lookup(product_name))
                supplier_lookup = str(source_item_row["supplier_lookup"] or _normalize_lookup(supplier_name))
                current_source_item_total = float(source_item_row["total_equivalent_units"] or 0)
                current_source_lot_quantity = float(source_lot_row["quantity"] or 0)
                stored_source_lot_equivalent_units = float(source_lot_row["equivalent_units"] or 0)
                stored_units_per_pack = (
                    float(source_lot_row["units_per_pack"])
                    if source_lot_row["units_per_pack"] is not None
                    else None
                )
                override_units_per_pack = float(payload.units_per_pack) if payload.units_per_pack is not None else None
                units_per_pack = override_units_per_pack if override_units_per_pack is not None else stored_units_per_pack
                source_requires_units_per_pack = self._inventory_lot_requires_units_per_pack(source_lot_code)
                target_requires_units_per_pack = self._inventory_lot_requires_units_per_pack(lot_code)
                if source_requires_units_per_pack and (units_per_pack is None or units_per_pack <= 0):
                    raise ValueError("Per questo lotto devo sapere quante unita contiene il pack.")
                if target_requires_units_per_pack and (units_per_pack is None or units_per_pack <= 0):
                    raise ValueError("Per questo lotto devo sapere quante unita contiene il pack.")
                current_source_lot_equivalent_units = (
                    current_source_lot_quantity * float(units_per_pack)
                    if source_requires_units_per_pack
                    else stored_source_lot_equivalent_units
                )
                source_equivalent_units_correction = current_source_lot_equivalent_units - stored_source_lot_equivalent_units

                target_equivalent_units = self._inventory_effective_units(
                    lot_code=lot_code,
                    quantity=payload.quantity,
                    units_per_pack=units_per_pack,
                )
                source_quantity_delta = (
                    target_equivalent_units / float(units_per_pack)
                    if source_requires_units_per_pack
                    else target_equivalent_units
                )
                if source_quantity_delta > current_source_lot_quantity + 1e-6:
                    raise ValueError(
                        f"Nel magazzino sorgente hai solo {round(current_source_lot_equivalent_units, 2)} unita disponibili."
                    )
                if target_equivalent_units > current_source_lot_equivalent_units + 1e-6:
                    raise ValueError("Lo spostamento supera lo stock disponibile del lotto selezionato.")

                next_source_lot_quantity = max(current_source_lot_quantity - source_quantity_delta, 0.0)
                next_source_lot_equivalent_units = max(current_source_lot_equivalent_units - target_equivalent_units, 0.0)
                next_source_item_total = max(
                    round(current_source_item_total + source_equivalent_units_correction - target_equivalent_units, 6),
                    0.0,
                )

                if next_source_lot_quantity <= 1e-6 or next_source_lot_equivalent_units <= 1e-6:
                    connection.execute(
                        "DELETE FROM tenant_inventory_stock_lots WHERE id = ?",
                        (str(source_lot_row["id"]),),
                    )
                else:
                    connection.execute(
                        """
                        UPDATE tenant_inventory_stock_lots
                        SET
                            quantity = ?,
                            units_per_pack = COALESCE(?, units_per_pack),
                            equivalent_units = ?,
                            updated_at = ?
                        WHERE id = ?
                        """,
                        (
                            next_source_lot_quantity,
                            units_per_pack,
                            next_source_lot_equivalent_units,
                            timestamp,
                            str(source_lot_row["id"]),
                        ),
                    )

                source_remaining_lots = connection.execute(
                    "SELECT COUNT(*) AS total FROM tenant_inventory_stock_lots WHERE item_id = ?",
                    (stock_item_id,),
                ).fetchone()
                if source_remaining_lots is not None and int(source_remaining_lots["total"] or 0) == 0:
                    connection.execute(
                        "DELETE FROM tenant_inventory_stock_items WHERE id = ?",
                        (stock_item_id,),
                    )
                else:
                    connection.execute(
                        """
                        UPDATE tenant_inventory_stock_items
                        SET
                            product_id = ?,
                            product_name = ?,
                            supplier_name = ?,
                            total_equivalent_units = ?,
                            updated_at = ?
                        WHERE id = ?
                        """,
                        (
                            product_id,
                            product_name,
                            supplier_name,
                            next_source_item_total,
                            timestamp,
                            stock_item_id,
                        ),
                    )
                    self._normalize_inventory_stock_item_lots(connection, stock_item_id, timestamp=timestamp)

                self._restore_inventory_stock_item_from_latest_session(
                    connection,
                    destination_warehouse_id,
                    product_lookup,
                    supplier_lookup,
                    timestamp=timestamp,
                )
                destination_item_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_inventory_stock_items
                    WHERE warehouse_id = ? AND product_lookup = ? AND supplier_lookup = ?
                    LIMIT 1
                    """,
                    (destination_warehouse_id, product_lookup, supplier_lookup),
                ).fetchone()
                destination_item_id = (
                    str(destination_item_row["id"])
                    if destination_item_row is not None
                    else f"inventory_item_{uuid.uuid4().hex}"
                )
                current_destination_item_total = (
                    float(destination_item_row["total_equivalent_units"] or 0)
                    if destination_item_row is not None
                    else 0.0
                )
                destination_lot_row = None
                if destination_item_row is not None:
                    destination_lot_row = connection.execute(
                        """
                        SELECT *
                        FROM tenant_inventory_stock_lots
                        WHERE item_id = ? AND lot_lookup = ?
                        LIMIT 1
                        """,
                        (destination_item_id, lot_lookup),
                    ).fetchone()

                current_destination_lot_quantity = (
                    float(destination_lot_row["quantity"] or 0)
                    if destination_lot_row is not None
                    else 0.0
                )
                current_destination_lot_equivalent_units = (
                    float(destination_lot_row["equivalent_units"] or 0)
                    if destination_lot_row is not None
                    else 0.0
                )
                next_destination_lot_quantity = current_destination_lot_quantity + float(payload.quantity)
                next_destination_lot_equivalent_units = current_destination_lot_equivalent_units + target_equivalent_units
                next_destination_item_total = round(current_destination_item_total + target_equivalent_units, 6)

                if destination_item_row is None:
                    connection.execute(
                        """
                        INSERT INTO tenant_inventory_stock_items (
                            id,
                            warehouse_id,
                            product_id,
                            product_name,
                            product_lookup,
                            supplier_name,
                            supplier_lookup,
                            total_equivalent_units,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            destination_item_id,
                            destination_warehouse_id,
                            product_id,
                            product_name,
                            product_lookup,
                            supplier_name,
                            supplier_lookup,
                            next_destination_item_total,
                            timestamp,
                            timestamp,
                        ),
                    )
                else:
                    connection.execute(
                        """
                        UPDATE tenant_inventory_stock_items
                        SET
                            product_id = ?,
                            product_name = ?,
                            supplier_name = ?,
                            total_equivalent_units = ?,
                            updated_at = ?
                        WHERE id = ?
                        """,
                        (
                            product_id,
                            product_name,
                            supplier_name,
                            next_destination_item_total,
                            timestamp,
                            destination_item_id,
                        ),
                    )

                if destination_lot_row is None:
                    connection.execute(
                        """
                        INSERT INTO tenant_inventory_stock_lots (
                            id,
                            item_id,
                            lot_code,
                            lot_lookup,
                            quantity,
                            units_per_pack,
                            equivalent_units,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"inventory_lot_{uuid.uuid4().hex}",
                            destination_item_id,
                            lot_code,
                            lot_lookup,
                            next_destination_lot_quantity,
                            units_per_pack,
                            next_destination_lot_equivalent_units,
                            timestamp,
                            timestamp,
                        ),
                    )
                else:
                    connection.execute(
                        """
                        UPDATE tenant_inventory_stock_lots
                        SET
                            quantity = ?,
                            units_per_pack = COALESCE(?, units_per_pack),
                            equivalent_units = ?,
                            updated_at = ?
                        WHERE id = ?
                        """,
                        (
                            next_destination_lot_quantity,
                            units_per_pack,
                            next_destination_lot_equivalent_units,
                            timestamp,
                            str(destination_lot_row["id"]),
                        ),
                    )

                self._normalize_inventory_stock_item_lots(connection, destination_item_id, timestamp=timestamp)

                source_label = f"Spostamento {source_warehouse_row['name']} -> {destination_warehouse_row['name']} ({transfer_id})"
                source_movement_id = f"inventory_movement_{uuid.uuid4().hex}"
                destination_movement_id = f"inventory_movement_{uuid.uuid4().hex}"
                movement_values = [
                    (
                        source_movement_id,
                        source_warehouse_id,
                        product_id,
                        product_name,
                        product_lookup,
                        supplier_name,
                        supplier_lookup,
                        lot_code,
                        lot_lookup,
                        float(payload.quantity),
                        units_per_pack,
                        target_equivalent_units,
                        "out",
                        "warehouse_transfer_out",
                        source_label,
                        occurred_at,
                        timestamp,
                        timestamp,
                    ),
                    (
                        destination_movement_id,
                        destination_warehouse_id,
                        product_id,
                        product_name,
                        product_lookup,
                        supplier_name,
                        supplier_lookup,
                        lot_code,
                        lot_lookup,
                        float(payload.quantity),
                        units_per_pack,
                        target_equivalent_units,
                        "in",
                        "warehouse_transfer_in",
                        source_label,
                        occurred_at,
                        timestamp,
                        timestamp,
                    ),
                ]
                connection.executemany(
                    """
                    INSERT INTO tenant_inventory_movements (
                        id,
                        warehouse_id,
                        product_id,
                        product_name,
                        product_lookup,
                        supplier_name,
                        supplier_lookup,
                        lot_code,
                        lot_lookup,
                        quantity,
                        units_per_pack,
                        equivalent_units,
                        movement_kind,
                        source_type,
                        source_label,
                        occurred_at,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    movement_values,
                )
                connection.execute(
                    """
                    UPDATE tenant_inventory_warehouses
                    SET updated_at = ?
                    WHERE id IN (?, ?)
                    """,
                    (timestamp, source_warehouse_id, destination_warehouse_id),
                )
                source_session_row, source_session_replaced = self._save_inventory_warehouse_session_from_stock(
                    connection,
                    session,
                    source_warehouse_id,
                    inventory_date=transfer_inventory_date,
                    timestamp=timestamp,
                )
                destination_session_row, destination_session_replaced = self._save_inventory_warehouse_session_from_stock(
                    connection,
                    session,
                    destination_warehouse_id,
                    inventory_date=transfer_inventory_date,
                    timestamp=timestamp,
                )

                updated_source_warehouse_row = self._read_inventory_warehouse_row(connection, source_warehouse_id)
                updated_destination_warehouse_row = self._read_inventory_warehouse_row(connection, destination_warehouse_id)
                movement_rows = connection.execute(
                    """
                    SELECT
                        movements.*,
                        warehouses.name AS warehouse_name
                    FROM tenant_inventory_movements AS movements
                    JOIN tenant_inventory_warehouses AS warehouses
                        ON warehouses.id = movements.warehouse_id
                    WHERE movements.id IN (?, ?)
                    """,
                    (source_movement_id, destination_movement_id),
                ).fetchall()
                connection.commit()

        movements_by_id = {
            str(row["id"]): self._serialize_inventory_movement_row(row)
            for row in movement_rows
        }
        return {
            "source_entry": movements_by_id[source_movement_id],
            "destination_entry": movements_by_id[destination_movement_id],
            "source_warehouse": self._serialize_inventory_warehouse_row(updated_source_warehouse_row),
            "destination_warehouse": self._serialize_inventory_warehouse_row(updated_destination_warehouse_row),
            "source_inventory_session": self._serialize_inventory_session_row(source_session_row),
            "destination_inventory_session": self._serialize_inventory_session_row(destination_session_row),
            "source_inventory_session_replaced": source_session_replaced,
            "destination_inventory_session_replaced": destination_session_replaced,
            "source_warehouse_detail": self.get_inventory_warehouse_detail(session, source_warehouse_id),
            "destination_warehouse_detail": self.get_inventory_warehouse_detail(session, destination_warehouse_id),
        }

    def list_inventory_consumptions(
        self,
        session: SessionIdentity,
        *,
        warehouse_id: str | None = None,
        limit: int = 120,
    ) -> dict[str, object]:
        if not self._can_access_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Consumi.")

        safe_limit = max(1, min(int(limit), 500))
        with self._connect_tenant_database(session.database_path) as connection:
            serialized_warehouse = None
            params: list[object] = []
            where_clauses = ["movements.movement_kind = 'out'", "movements.source_type = 'manual_consumption'"]
            if warehouse_id:
                warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
                serialized_warehouse = self._serialize_inventory_warehouse_row(warehouse_row)
                where_clauses.append("movements.warehouse_id = ?")
                params.append(warehouse_id)

            rows = connection.execute(
                f"""
                SELECT
                    movements.*,
                    warehouses.name AS warehouse_name
                FROM tenant_inventory_movements AS movements
                JOIN tenant_inventory_warehouses AS warehouses
                    ON warehouses.id = movements.warehouse_id
                WHERE {' AND '.join(where_clauses)}
                ORDER BY movements.occurred_at DESC, movements.created_at DESC
                LIMIT ?
                """,
                tuple(params + [safe_limit]),
            ).fetchall()

        entries = [self._serialize_inventory_movement_row(row) for row in rows]
        return {
            "warehouse": serialized_warehouse,
            "entries": entries,
            "total_count": len(entries),
        }

    def list_inventory_consumption_days(
        self,
        session: SessionIdentity,
        *,
        year: int | None = None,
        query: str | None = None,
        limit: int = 366,
    ) -> dict[str, object]:
        if not self._can_access_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Consumi.")

        safe_limit = max(1, min(int(limit), 1000))
        normalized_query = str(query or "").strip().lower()
        where_clauses = [
            "consumed_units > 0",
            "consumption_date <> ''",
        ]
        params: list[object] = []
        if year is not None:
            safe_year = max(2000, min(int(year), 2100))
            where_clauses.append("substr(consumption_date, 1, 4) = ?")
            params.append(f"{safe_year:04d}")
        if normalized_query:
            like_value = f"%{normalized_query}%"
            where_clauses.append(
                """
                (
                    lower(product_name) LIKE ?
                    OR lower(supplier_name) LIKE ?
                    OR product_lookup LIKE ?
                    OR supplier_lookup LIKE ?
                )
                """
            )
            params.extend([like_value, like_value, like_value, like_value])
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._refresh_all_inventory_estimated_consumptions(connection)
                connection.commit()
                rows = connection.execute(
                    f"""
                    SELECT
                        consumption_date,
                        COUNT(*) AS movement_count,
                        MAX(warehouse_count) AS warehouse_count,
                        COUNT(DISTINCT product_lookup || char(0) || supplier_lookup) AS product_count,
                        ROUND(SUM(consumed_units), 6) AS total_equivalent_units,
                        MIN(updated_at) AS first_occurred_at,
                        MAX(updated_at) AS last_occurred_at
                    FROM tenant_inventory_estimated_consumptions
                    WHERE {' AND '.join(where_clauses)}
                    GROUP BY consumption_date
                    ORDER BY consumption_date DESC
                    LIMIT ?
                    """,
                    tuple(params + [safe_limit]),
                ).fetchall()

        days = [
            {
                "date": str(row["consumption_date"] or ""),
                "movement_count": int(row["movement_count"] or 0),
                "warehouse_count": int(row["warehouse_count"] or 0),
                "product_count": int(row["product_count"] or 0),
                "total_equivalent_units": float(row["total_equivalent_units"] or 0),
                "first_occurred_at": str(row["first_occurred_at"] or "") or None,
                "last_occurred_at": str(row["last_occurred_at"] or "") or None,
            }
            for row in rows
        ]
        return {
            "days": days,
            "total_count": len(days),
            "year": int(year) if year is not None else None,
            "query": normalized_query or None,
            "calculation_mode": "consumo stimato = inventario precedente + ordini confermati - nuovo inventario",
        }

    def list_inventory_consumption_years(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Consumi.")

        current_year = _local_now().year
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._refresh_all_inventory_estimated_consumptions(connection)
                connection.commit()
                rows = connection.execute(
                    """
                    SELECT
                        CAST(substr(consumption_date, 1, 4) AS INTEGER) AS consumption_year,
                        COUNT(*) AS movement_count,
                        COUNT(DISTINCT consumption_date) AS day_count,
                        COUNT(DISTINCT product_lookup || char(0) || supplier_lookup) AS product_count,
                        ROUND(SUM(consumed_units), 6) AS total_equivalent_units
                    FROM tenant_inventory_estimated_consumptions
                    WHERE consumed_units > 0
                      AND consumption_date <> ''
                    GROUP BY substr(consumption_date, 1, 4)
                    ORDER BY consumption_year ASC
                    """
                ).fetchall()
                period_rows = connection.execute(
                    """
                    SELECT DISTINCT period_start_date, period_end_date
                    FROM tenant_inventory_estimated_consumptions
                    WHERE consumed_units > 0
                      AND period_start_date <> ''
                      AND period_end_date <> ''
                    """
                ).fetchall()
                periods = [
                    (str(row["period_start_date"] or ""), str(row["period_end_date"] or ""))
                    for row in period_rows
                ]
                settings = self._read_homemade_stock_settings(connection)
                operational_days_by_year = {
                    int(row["consumption_year"]): len(
                        self._inventory_bar_operational_days_for_periods(
                            connection,
                            periods,
                            year=int(row["consumption_year"]),
                            settings=settings,
                        )
                    )
                    for row in rows
                    if row["consumption_year"] is not None
                }

        summaries_by_year = {
            int(row["consumption_year"]): {
                "year": int(row["consumption_year"]),
                "day_count": operational_days_by_year.get(int(row["consumption_year"]), 0),
                "movement_count": int(row["movement_count"] or 0),
                "product_count": int(row["product_count"] or 0),
                "total_equivalent_units": float(row["total_equivalent_units"] or 0),
                "has_real_data": True,
            }
            for row in rows
            if row["consumption_year"] is not None
        }
        for required_year in {2025, current_year}:
            summaries_by_year.setdefault(
                required_year,
                {
                    "year": required_year,
                    "day_count": 0,
                    "movement_count": 0,
                    "product_count": 0,
                    "total_equivalent_units": 0,
                    "has_real_data": False,
                },
            )
        years = [summaries_by_year[year] for year in sorted(summaries_by_year)]
        return {
            "years": years,
            "current_year": current_year,
            "calculation_mode": "ogni anno include i consumi stimati da inventari e ordini confermati; i giorni sono quelli operativi bar",
        }

    def list_inventory_consumption_product_totals(
        self,
        session: SessionIdentity,
        *,
        year: int,
        query: str | None = None,
        limit: int = 500,
    ) -> dict[str, object]:
        if not self._can_access_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Consumi.")

        safe_year = max(2000, min(int(year), 2100))
        safe_limit = max(1, min(int(limit), 1000))
        normalized_query = str(query or "").strip().lower()
        where_clauses = [
            "consumed_units > 0",
            "consumption_date <> ''",
            "substr(consumption_date, 1, 4) = ?",
        ]
        params: list[object] = [f"{safe_year:04d}"]
        if normalized_query:
            like_value = f"%{normalized_query}%"
            where_clauses.append(
                """
                (
                    lower(product_name) LIKE ?
                    OR lower(supplier_name) LIKE ?
                    OR product_lookup LIKE ?
                    OR supplier_lookup LIKE ?
                )
                """
            )
            params.extend([like_value, like_value, like_value, like_value])

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._refresh_all_inventory_estimated_consumptions(connection)
                connection.commit()
                rows = connection.execute(
                    f"""
                    SELECT
                        product_lookup,
                        supplier_lookup,
                        MIN(product_id) AS product_id,
                        MAX(product_name) AS product_name,
                        MAX(supplier_name) AS supplier_name,
                        COUNT(*) AS movement_count,
                        MAX(warehouse_count) AS warehouse_count,
                        COUNT(DISTINCT consumption_date) AS day_count,
                        ROUND(SUM(consumed_units), 6) AS total_quantity,
                        ROUND(SUM(consumed_units), 6) AS total_equivalent_units,
                        MIN(consumption_date) AS first_consumption_date,
                        MAX(consumption_date) AS last_consumption_date,
                        '' AS lot_codes
                    FROM tenant_inventory_estimated_consumptions
                    WHERE {' AND '.join(where_clauses)}
                    GROUP BY product_lookup, supplier_lookup
                    ORDER BY total_equivalent_units DESC, lower(product_name) ASC, lower(supplier_name) ASC
                    LIMIT ?
                    """,
                    tuple(params + [safe_limit]),
                ).fetchall()
                period_rows = connection.execute(
                    f"""
                    SELECT DISTINCT
                        product_lookup,
                        supplier_lookup,
                        period_start_date,
                        period_end_date
                    FROM tenant_inventory_estimated_consumptions
                    WHERE {' AND '.join(where_clauses)}
                      AND period_start_date <> ''
                      AND period_end_date <> ''
                    """,
                    tuple(params),
                ).fetchall()
                periods_by_product: dict[tuple[str, str], list[tuple[str, str]]] = {}
                for period_row in period_rows:
                    key = (str(period_row["product_lookup"] or ""), str(period_row["supplier_lookup"] or ""))
                    periods_by_product.setdefault(key, []).append(
                        (str(period_row["period_start_date"] or ""), str(period_row["period_end_date"] or ""))
                    )
                settings = self._read_homemade_stock_settings(connection)
                workdays_by_product = {
                    key: len(
                        self._inventory_bar_operational_days_for_periods(
                            connection,
                            periods,
                            year=safe_year,
                            settings=settings,
                        )
                    )
                    for key, periods in periods_by_product.items()
                }

        items = [
            {
                "product_id": int(row["product_id"]) if row["product_id"] is not None else None,
                "product_name": str(row["product_name"] or ""),
                "supplier_name": str(row["supplier_name"] or ""),
                "product_lookup": str(row["product_lookup"] or ""),
                "supplier_lookup": str(row["supplier_lookup"] or ""),
                "movement_count": int(row["movement_count"] or 0),
                "warehouse_count": int(row["warehouse_count"] or 0),
                "day_count": workdays_by_product.get(
                    (str(row["product_lookup"] or ""), str(row["supplier_lookup"] or "")),
                    0,
                ),
                "total_quantity": float(row["total_quantity"] or 0),
                "total_equivalent_units": float(row["total_equivalent_units"] or 0),
                "average_daily_consumed_units": (
                    round(
                        float(row["total_equivalent_units"] or 0)
                        / max(
                            workdays_by_product.get(
                                (str(row["product_lookup"] or ""), str(row["supplier_lookup"] or "")),
                                0,
                            ),
                            1,
                        ),
                        6,
                    )
                ),
                "first_consumption_date": str(row["first_consumption_date"] or "") or None,
                "last_consumption_date": str(row["last_consumption_date"] or "") or None,
                "lot_codes": [
                    value
                    for value in str(row["lot_codes"] or "").split(",")
                    if value
                ],
            }
            for row in rows
        ]
        return {
            "year": safe_year,
            "items": items,
            "total_count": len(items),
            "total_equivalent_units": round(sum(float(item["total_equivalent_units"]) for item in items), 6),
            "query": normalized_query or None,
            "calculation_mode": "totali stimati da inventari e ordini confermati; medie divise per i giorni operativi bar",
        }

    def get_inventory_consumption_day_detail(
        self,
        session: SessionIdentity,
        *,
        consumption_date: str,
    ) -> dict[str, object]:
        if not self._can_access_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Consumi.")

        normalized_date = self._normalize_inventory_date(consumption_date)
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._refresh_all_inventory_estimated_consumptions(connection)
                connection.commit()
                item_rows = connection.execute(
                    """
                    SELECT
                        product_lookup,
                        supplier_lookup,
                        MIN(product_id) AS product_id,
                        MAX(product_name) AS product_name,
                        MAX(supplier_name) AS supplier_name,
                        COUNT(*) AS movement_count,
                        MAX(warehouse_count) AS warehouse_count,
                        ROUND(SUM(consumed_units), 6) AS total_quantity,
                        ROUND(SUM(consumed_units), 6) AS total_equivalent_units,
                        ROUND(SUM(opening_units), 6) AS opening_units,
                        ROUND(SUM(incoming_units), 6) AS incoming_units,
                        ROUND(SUM(closing_units), 6) AS closing_units,
                        MIN(period_start_date) AS period_start_date,
                        MAX(period_end_date) AS period_end_date,
                        '' AS lot_codes,
                        '' AS warehouse_ids
                    FROM tenant_inventory_estimated_consumptions
                    WHERE consumed_units > 0
                      AND consumption_date = ?
                    GROUP BY product_lookup, supplier_lookup
                    ORDER BY total_equivalent_units DESC, lower(product_name) ASC, lower(supplier_name) ASC
                    """,
                    (normalized_date,),
                ).fetchall()

        entries: list[dict[str, object]] = []
        items = [
            {
                "product_id": int(row["product_id"]) if row["product_id"] is not None else None,
                "product_name": str(row["product_name"] or ""),
                "supplier_name": str(row["supplier_name"] or ""),
                "product_lookup": str(row["product_lookup"] or ""),
                "supplier_lookup": str(row["supplier_lookup"] or ""),
                "movement_count": int(row["movement_count"] or 0),
                "warehouse_count": int(row["warehouse_count"] or 0),
                "total_quantity": float(row["total_quantity"] or 0),
                "total_equivalent_units": float(row["total_equivalent_units"] or 0),
                "lot_codes": [
                    value
                    for value in str(row["lot_codes"] or "").split(",")
                    if value
                ],
                "warehouse_ids": [
                    value
                    for value in str(row["warehouse_ids"] or "").split(",")
                    if value
                ],
                "opening_units": float(row["opening_units"] or 0),
                "incoming_units": float(row["incoming_units"] or 0),
                "closing_units": float(row["closing_units"] or 0),
                "consumed_units": float(row["total_equivalent_units"] or 0),
                "period_start_date": str(row["period_start_date"] or "") or None,
                "period_end_date": str(row["period_end_date"] or "") or None,
            }
            for row in item_rows
        ]
        return {
            "date": normalized_date,
            "items": items,
            "entries": entries,
            "total_count": len(items),
            "total_equivalent_units": round(sum(float(item["total_equivalent_units"]) for item in items), 6),
            "calculation_mode": "dettaglio dei consumi stimati da inventari e ordini confermati per la data selezionata",
        }

    def get_inventory_consumption_product_trend(
        self,
        session: SessionIdentity,
        *,
        product_name: str,
        supplier_name: str,
        limit: int = 120,
    ) -> dict[str, object]:
        if not self._can_access_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Consumi.")

        product_lookup = _normalize_lookup(product_name)
        supplier_lookup = _normalize_lookup(supplier_name)
        if not product_lookup:
            raise ValueError("Prodotto non valido per il grafico consumi.")
        safe_limit = max(1, min(int(limit), 366))
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._refresh_all_inventory_estimated_consumptions(connection)
                connection.commit()
                rows = connection.execute(
                    """
                    SELECT
                        consumption_date,
                        MAX(product_name) AS product_name,
                        MAX(supplier_name) AS supplier_name,
                        COUNT(*) AS movement_count,
                        MAX(warehouse_count) AS warehouse_count,
                        ROUND(SUM(consumed_units), 6) AS total_quantity,
                        ROUND(SUM(consumed_units), 6) AS total_equivalent_units
                    FROM tenant_inventory_estimated_consumptions
                    WHERE consumed_units > 0
                      AND product_lookup = ?
                      AND (? = '' OR supplier_lookup = ?)
                      AND consumption_date <> ''
                    GROUP BY consumption_date
                    ORDER BY consumption_date DESC
                    LIMIT ?
                    """,
                    (product_lookup, supplier_lookup, supplier_lookup, safe_limit),
                ).fetchall()

        points = [
            {
                "date": str(row["consumption_date"] or ""),
                "movement_count": int(row["movement_count"] or 0),
                "warehouse_count": int(row["warehouse_count"] or 0),
                "total_quantity": float(row["total_quantity"] or 0),
                "total_equivalent_units": float(row["total_equivalent_units"] or 0),
            }
            for row in reversed(rows)
        ]
        display_product = str(rows[0]["product_name"] or product_name) if rows else product_name
        display_supplier = str(rows[0]["supplier_name"] or supplier_name) if rows else supplier_name
        return {
            "product_name": display_product,
            "supplier_name": display_supplier,
            "points": points,
            "total_count": len(points),
            "total_equivalent_units": round(sum(float(point["total_equivalent_units"]) for point in points), 6),
            "calculation_mode": "somma giornaliera dei consumi stimati da inventari e ordini confermati",
        }

    def get_inventory_consumption_2025_estimate(
        self,
        session: SessionIdentity,
        *,
        year: int = 2025,
        workdays: int = 88,
        query: str | None = None,
        limit: int = 500,
    ) -> dict[str, object]:
        if not self._can_access_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Consumi.")

        target_year = max(2000, min(int(year), 2100))
        safe_workdays = max(1, int(workdays))
        safe_limit = max(1, min(int(limit), 1000))
        normalized_query = _normalize_lookup(str(query or ""))
        with self._connect_tenant_database(session.database_path) as connection:
            if not (
                self._tenant_table_exists(connection, "ordini_items")
                and self._tenant_table_exists(connection, "ordini_batches")
            ):
                return {
                    "year": target_year,
                    "workdays": safe_workdays,
                    "items": [],
                    "total_count": 0,
                    "totals": {
                        "ordered_units": 0,
                        "current_stock_units": 0,
                        "estimated_consumed_units": 0,
                        "average_daily_estimated_units": 0,
                    },
                    "data_gaps": [],
                    "calculation_mode": "storico ordini non disponibile per stimare il consumo",
                }

            product_columns = self._tenant_table_columns(connection, "ordini_products")
            select_product_units_per_pack = "products.units_per_pack" if "units_per_pack" in product_columns else "NULL"
            start_date = f"{target_year:04d}-01-01"
            end_date = f"{target_year + 1:04d}-01-01"
            order_rows = connection.execute(
                f"""
                SELECT
                    items.product_id,
                    items.product_name,
                    items.supplier_name,
                    items.lot_code,
                    items.quantity,
                    COALESCE(items.units_per_pack, {select_product_units_per_pack}) AS resolved_units_per_pack,
                    batches.confirmed_at
                FROM ordini_items AS items
                JOIN ordini_batches AS batches
                    ON batches.id = items.batch_id
                LEFT JOIN ordini_products AS products
                    ON products.id = items.product_id
                WHERE batches.confirmed_at >= ? AND batches.confirmed_at < ?
                ORDER BY batches.confirmed_at ASC, items.id ASC
                """,
                (start_date, end_date),
            ).fetchall()
            stock_rows = connection.execute(
                """
                SELECT
                    MIN(product_id) AS product_id,
                    product_lookup,
                    supplier_lookup,
                    MAX(product_name) AS product_name,
                    MAX(supplier_name) AS supplier_name,
                    ROUND(SUM(total_equivalent_units), 6) AS current_stock_units
                FROM tenant_inventory_stock_items
                WHERE total_equivalent_units <> 0
                GROUP BY product_lookup, supplier_lookup
                """,
            ).fetchall()

        purchase_map: dict[tuple[str, str], dict[str, object]] = {}
        data_gaps: list[dict[str, object]] = []
        seen_gap_keys: set[tuple[str, str, str]] = set()
        for row in order_rows:
            product_name = str(row["product_name"] or "").strip()
            supplier_name = str(row["supplier_name"] or "").strip()
            lot_code = str(row["lot_code"] or "").strip()
            quantity = float(row["quantity"] or 0)
            if not product_name or quantity <= 0:
                continue
            resolved_units_per_pack = float(row["resolved_units_per_pack"]) if row["resolved_units_per_pack"] is not None else None
            try:
                ordered_units = self._inventory_effective_units(
                    lot_code=lot_code,
                    quantity=quantity,
                    units_per_pack=resolved_units_per_pack,
                )
            except ValueError:
                ordered_units = quantity
                gap_key = (_normalize_lookup(product_name), _normalize_lookup(supplier_name), _normalize_lookup(lot_code))
                if gap_key not in seen_gap_keys:
                    seen_gap_keys.add(gap_key)
                    data_gaps.append(
                        {
                            "product_name": product_name,
                            "supplier_name": supplier_name,
                            "lot_code": lot_code,
                            "reason": "units_per_pack mancante: per questa riga ho contato la quantita come unita singole",
                        }
                    )

            key = (_normalize_lookup(product_name), _normalize_lookup(supplier_name))
            bucket = purchase_map.setdefault(
                key,
                {
                    "product_id": int(row["product_id"]) if row["product_id"] is not None else None,
                    "product_name": product_name,
                    "supplier_name": supplier_name,
                    "ordered_units": 0.0,
                    "order_line_count": 0,
                },
            )
            bucket["ordered_units"] = float(bucket["ordered_units"] or 0) + ordered_units
            bucket["order_line_count"] = int(bucket["order_line_count"] or 0) + 1

        stock_map = {
            (str(row["product_lookup"] or ""), str(row["supplier_lookup"] or "")): {
                "product_id": int(row["product_id"]) if row["product_id"] is not None else None,
                "product_name": str(row["product_name"] or ""),
                "supplier_name": str(row["supplier_name"] or ""),
                "current_stock_units": float(row["current_stock_units"] or 0),
            }
            for row in stock_rows
        }
        item_keys = set(purchase_map)
        items: list[dict[str, object]] = []
        for key in item_keys:
            purchase = purchase_map.get(key)
            stock = stock_map.get(key)
            ordered_units = float((purchase or {}).get("ordered_units") or 0)
            current_stock_units = float((stock or {}).get("current_stock_units") or 0)
            raw_estimated_consumed_units = ordered_units - current_stock_units
            estimated_consumed_units = max(raw_estimated_consumed_units, 0.0)
            product_name = str((purchase or stock or {}).get("product_name") or key[0])
            supplier_name = str((purchase or stock or {}).get("supplier_name") or key[1])
            items.append(
                {
                    "product_id": (purchase or stock or {}).get("product_id"),
                    "product_name": product_name,
                    "supplier_name": supplier_name,
                    "ordered_units": round(ordered_units, 6),
                    "current_stock_units": round(current_stock_units, 6),
                    "raw_estimated_consumed_units": round(raw_estimated_consumed_units, 6),
                    "estimated_consumed_units": round(estimated_consumed_units, 6),
                    "average_daily_estimated_units": round(estimated_consumed_units / safe_workdays, 6),
                    "stock_exceeds_purchases_units": round(max(current_stock_units - ordered_units, 0.0), 6),
                    "order_line_count": int((purchase or {}).get("order_line_count") or 0),
                }
            )

        items.sort(
            key=lambda item: (
                -float(item["estimated_consumed_units"]),
                -float(item["ordered_units"]),
                str(item["supplier_name"]).lower(),
                str(item["product_name"]).lower(),
            )
        )
        if normalized_query:
            items = [
                item
                for item in items
                if normalized_query in _normalize_lookup(str(item.get("product_name") or ""))
                or normalized_query in _normalize_lookup(str(item.get("supplier_name") or ""))
            ]
            data_gaps = [
                item
                for item in data_gaps
                if normalized_query in _normalize_lookup(str(item.get("product_name") or ""))
                or normalized_query in _normalize_lookup(str(item.get("supplier_name") or ""))
                or normalized_query in _normalize_lookup(str(item.get("lot_code") or ""))
            ]
        limited_items = items[:safe_limit]
        total_ordered_units = round(sum(float(item["ordered_units"]) for item in items), 6)
        total_current_stock_units = round(sum(float(item["current_stock_units"]) for item in items), 6)
        total_estimated_consumed_units = round(sum(float(item["estimated_consumed_units"]) for item in items), 6)
        return {
            "year": target_year,
            "workdays": safe_workdays,
            "items": limited_items,
            "total_count": len(items),
            "query": normalized_query or None,
            "totals": {
                "ordered_units": total_ordered_units,
                "current_stock_units": total_current_stock_units,
                "estimated_consumed_units": total_estimated_consumed_units,
                "average_daily_estimated_units": round(total_estimated_consumed_units / safe_workdays, 6),
            },
            "data_gaps": data_gaps,
            "calculation_mode": (
                "stima = max(ordini confermati nell'anno - stock attuale, 0) / giorni lavorati; "
                "manca lo storico reale dei consumi 2025"
            ),
        }

    def list_inventory_consumption_product_stats(
        self,
        session: SessionIdentity,
        *,
        query: str | None = None,
        limit: int = 200,
    ) -> dict[str, object]:
        if not self._can_access_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Consumi.")

        safe_limit = max(1, min(int(limit), 500))
        normalized_query = str(query or "").strip().lower()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._refresh_inventory_consumption_product_stats(connection)
                params: list[object] = []
                where_clause = ""
                if normalized_query:
                    like_value = f"%{normalized_query}%"
                    where_clause = """
                    WHERE lower(product_name) LIKE ?
                       OR lower(supplier_name) LIKE ?
                    """
                    params.extend([like_value, like_value])

                rows = connection.execute(
                    f"""
                    SELECT *
                    FROM tenant_inventory_consumption_product_stats
                    {where_clause}
                    ORDER BY
                        total_consumed_units DESC,
                        average_daily_consumed_units DESC,
                        lower(supplier_name) ASC,
                        lower(product_name) ASC
                    LIMIT ?
                    """,
                    tuple(params + [safe_limit]),
                ).fetchall()
                period_rows = connection.execute(
                    """
                    SELECT DISTINCT period_start_date, period_end_date
                    FROM tenant_inventory_estimated_consumptions
                    WHERE consumed_units > 0
                      AND period_start_date <> ''
                      AND period_end_date <> ''
                    """
                ).fetchall()
                workdays_count = len(
                    self._inventory_bar_operational_days_for_periods(
                        connection,
                        [
                            (str(row["period_start_date"] or ""), str(row["period_end_date"] or ""))
                            for row in period_rows
                        ],
                    )
                )
                connection.commit()

        return {
            "items": [self._serialize_inventory_consumption_product_stat_row(row) for row in rows],
            "total_count": len(rows),
            "workdays_count": workdays_count,
            "calculation_mode": (
                "media giornaliera = consumo stimato totale / giorni operativi bar coperti dagli inventari"
            ),
        }

    def create_inventory_consumption(
        self,
        session: SessionIdentity,
        payload: InventoryConsumptionWritePayload,
    ) -> dict[str, object]:
        if not self._can_access_consumptions(session):
            raise ValueError("Il tuo account non puo accedere a Consumi.")

        timestamp = _iso_now()
        occurred_at = self._normalize_inventory_movement_timestamp(payload.occurred_at) if payload.occurred_at else timestamp
        consumption_inventory_date = self._normalize_inventory_date(str(occurred_at or "")[:10])
        warehouse_id = str(payload.warehouse_id or "").strip()
        if not warehouse_id:
            raise ValueError("Seleziona il magazzino da cui stai scaricando la merce.")

        product_consumption_stat: dict[str, object] | None = None
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                warehouse_row = self._read_inventory_warehouse_row(connection, warehouse_id)
                serialized_warehouse = self._serialize_inventory_warehouse_row(warehouse_row)
                if not self._tenant_table_exists(connection, "ordini_products"):
                    raise ValueError("Catalogo prodotti non disponibile per questo locale.")

                product_columns = self._tenant_table_columns(connection, "ordini_products")
                select_units_per_pack = "units_per_pack" if "units_per_pack" in product_columns else "NULL AS units_per_pack"
                product_row = connection.execute(
                    f"""
                    SELECT id, product_name, lot_code, supplier_name, {select_units_per_pack}
                    FROM ordini_products
                    WHERE id = ? AND active = 1
                    LIMIT 1
                    """,
                    (payload.product_id,),
                ).fetchone()
                if product_row is None:
                    raise KeyError("Prodotto non trovato nel catalogo attivo.")

                seed_product_name = str(product_row["product_name"] or "").strip()
                seed_supplier_name = str(product_row["supplier_name"] or "").strip()
                seed_lot_code = str(product_row["lot_code"] or "").strip()
                requested_lot_code = str(payload.lot_code or "").strip()
                lot_code = requested_lot_code or seed_lot_code
                if not lot_code:
                    raise ValueError("Seleziona un lotto per registrare il consumo.")

                matched_variant_row = connection.execute(
                    f"""
                    SELECT id, product_name, lot_code, supplier_name, {select_units_per_pack}
                    FROM ordini_products
                    WHERE active = 1
                      AND lower(product_name) = lower(?)
                      AND lower(supplier_name) = lower(?)
                      AND lower(lot_code) = lower(?)
                    LIMIT 1
                    """,
                    (seed_product_name, seed_supplier_name, lot_code),
                ).fetchone()
                resolved_product_row = matched_variant_row if matched_variant_row is not None else product_row
                product_name = str(resolved_product_row["product_name"] or "").strip() or seed_product_name
                supplier_name = str(resolved_product_row["supplier_name"] or "").strip() or seed_supplier_name
                product_lookup = _normalize_lookup(product_name)
                supplier_lookup = _normalize_lookup(supplier_name)
                lot_lookup = _normalize_lookup(lot_code)
                if matched_variant_row is not None:
                    catalog_units_per_pack = (
                        float(matched_variant_row["units_per_pack"])
                        if matched_variant_row["units_per_pack"] is not None
                        else None
                    )
                elif _normalize_lookup(lot_code) == _normalize_lookup(seed_lot_code):
                    catalog_units_per_pack = float(product_row["units_per_pack"]) if product_row["units_per_pack"] is not None else None
                else:
                    catalog_units_per_pack = None

                item_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_inventory_stock_items
                    WHERE warehouse_id = ? AND product_lookup = ? AND supplier_lookup = ?
                    LIMIT 1
                    """,
                    (warehouse_id, product_lookup, supplier_lookup),
                ).fetchone()
                if item_row is None:
                    raise ValueError("Nel magazzino selezionato non trovo questo prodotto.")

                item_id = str(item_row["id"])
                current_item_total = float(item_row["total_equivalent_units"] or 0)
                lot_rows = connection.execute(
                    """
                    SELECT *
                    FROM tenant_inventory_stock_lots
                    WHERE item_id = ?
                    ORDER BY lower(lot_code) ASC, created_at ASC
                    """,
                    (item_id,),
                ).fetchall()
                lot_row = next((row for row in lot_rows if str(row["lot_lookup"] or "") == lot_lookup), None)
                selected_lot_is_pack = self._inventory_lot_requires_units_per_pack(lot_code)
                if lot_row is None and selected_lot_is_pack:
                    raise ValueError("Nel magazzino selezionato non trovo questo lotto.")
                if lot_row is None and not lot_rows:
                    raise ValueError("Nel magazzino selezionato non trovo lotti disponibili per questo prodotto.")

                override_units_per_pack = float(payload.units_per_pack) if payload.units_per_pack is not None else None
                stored_units_per_pack = (
                    float(lot_row["units_per_pack"])
                    if lot_row is not None and lot_row["units_per_pack"] is not None
                    else None
                )
                units_per_pack = override_units_per_pack if override_units_per_pack is not None else stored_units_per_pack
                if units_per_pack is None:
                    units_per_pack = catalog_units_per_pack

                if selected_lot_is_pack and (units_per_pack is None or units_per_pack <= 0):
                    raise ValueError("Per questo lotto devo sapere quante unita contiene il pack.")

                target_equivalent_units = self._inventory_effective_units(
                    lot_code=lot_code,
                    quantity=payload.quantity,
                    units_per_pack=units_per_pack,
                )

                if target_equivalent_units > current_item_total + 1e-6:
                    raise ValueError(
                        f"Nel magazzino hai solo {round(current_item_total, 2)} unita equivalenti disponibili per questo prodotto."
                    )

                if selected_lot_is_pack:
                    if lot_row is None:
                        raise ValueError("Nel magazzino selezionato non trovo questo lotto.")
                    current_lot_quantity = float(lot_row["quantity"] or 0)
                    current_lot_equivalent_units = float(lot_row["equivalent_units"] or 0)
                    if payload.quantity > current_lot_quantity + 1e-6:
                        raise ValueError(
                            f"Nel magazzino hai solo {round(current_lot_quantity, 2)} {lot_code} disponibili per questo prodotto."
                        )
                    if target_equivalent_units > current_lot_equivalent_units + 1e-6:
                        raise ValueError("Questo consumo supera lo stock disponibile del lotto selezionato.")
                    allocation_rows = [(lot_row, target_equivalent_units)]
                else:
                    def allocation_priority(row: sqlite3.Row) -> tuple[int, int, str]:
                        row_lookup = str(row["lot_lookup"] or "")
                        row_lot_code = str(row["lot_code"] or "")
                        return (
                            0 if row_lookup == lot_lookup else 1,
                            1 if self._inventory_lot_requires_units_per_pack(row_lot_code) else 0,
                            row_lot_code.lower(),
                        )

                    remaining_equivalent_units = target_equivalent_units
                    allocation_rows: list[tuple[sqlite3.Row, float]] = []
                    for candidate_lot_row in sorted(lot_rows, key=allocation_priority):
                        if remaining_equivalent_units <= 1e-6:
                            break
                        candidate_equivalent_units = float(candidate_lot_row["equivalent_units"] or 0)
                        if candidate_equivalent_units <= 0:
                            continue
                        consumed_from_lot = min(candidate_equivalent_units, remaining_equivalent_units)
                        allocation_rows.append((candidate_lot_row, consumed_from_lot))
                        remaining_equivalent_units -= consumed_from_lot
                    if remaining_equivalent_units > 1e-6:
                        raise ValueError("Questo consumo supera lo stock totale disponibile del prodotto.")

                for allocated_lot_row, consumed_equivalent_units in allocation_rows:
                    current_lot_quantity = float(allocated_lot_row["quantity"] or 0)
                    current_lot_equivalent_units = float(allocated_lot_row["equivalent_units"] or 0)
                    allocated_lot_code = str(allocated_lot_row["lot_code"] or "")
                    allocated_units_per_pack = (
                        float(allocated_lot_row["units_per_pack"])
                        if allocated_lot_row["units_per_pack"] is not None
                        else None
                    )
                    if self._inventory_lot_requires_units_per_pack(allocated_lot_code):
                        quantity_divisor = allocated_units_per_pack
                        if (quantity_divisor is None or quantity_divisor <= 0) and current_lot_quantity > 0:
                            quantity_divisor = current_lot_equivalent_units / current_lot_quantity
                        if quantity_divisor is None or quantity_divisor <= 0:
                            raise ValueError(
                                f"Per scalare il lotto {allocated_lot_code} devo sapere quante unita contiene il pack."
                            )
                        consumed_quantity = consumed_equivalent_units / quantity_divisor
                    else:
                        quantity_divisor = allocated_units_per_pack
                        consumed_quantity = consumed_equivalent_units

                    next_lot_quantity = max(current_lot_quantity - consumed_quantity, 0.0)
                    next_lot_equivalent_units = max(current_lot_equivalent_units - consumed_equivalent_units, 0.0)

                    if next_lot_quantity <= 1e-6 or next_lot_equivalent_units <= 1e-6:
                        connection.execute(
                            "DELETE FROM tenant_inventory_stock_lots WHERE id = ?",
                            (str(allocated_lot_row["id"]),),
                        )
                    else:
                        connection.execute(
                            """
                            UPDATE tenant_inventory_stock_lots
                            SET
                                quantity = ?,
                                units_per_pack = COALESCE(?, units_per_pack),
                                equivalent_units = ?,
                                updated_at = ?
                            WHERE id = ?
                            """,
                            (
                                next_lot_quantity,
                                quantity_divisor,
                                next_lot_equivalent_units,
                                timestamp,
                                str(allocated_lot_row["id"]),
                            ),
                        )

                next_item_total = max(round(current_item_total - target_equivalent_units, 6), 0.0)
                self._normalize_inventory_stock_item_lots(connection, item_id, timestamp=timestamp)

                remaining_lots = connection.execute(
                    "SELECT COUNT(*) AS total FROM tenant_inventory_stock_lots WHERE item_id = ?",
                    (item_id,),
                ).fetchone()
                if remaining_lots is not None and int(remaining_lots["total"] or 0) == 0:
                    connection.execute(
                        "DELETE FROM tenant_inventory_stock_items WHERE id = ?",
                        (item_id,),
                    )
                else:
                    connection.execute(
                        """
                        UPDATE tenant_inventory_stock_items
                        SET
                            product_id = ?,
                            product_name = ?,
                            supplier_name = ?,
                            total_equivalent_units = ?,
                            updated_at = ?
                        WHERE id = ?
                        """,
                        (
                            int(resolved_product_row["id"]),
                            product_name,
                            supplier_name,
                            next_item_total,
                            timestamp,
                            item_id,
                        ),
                    )

                movement_id = f"inventory_movement_{uuid.uuid4().hex}"
                connection.execute(
                    """
                    INSERT INTO tenant_inventory_movements (
                        id,
                        warehouse_id,
                        product_id,
                        product_name,
                        product_lookup,
                        supplier_name,
                        supplier_lookup,
                        lot_code,
                        lot_lookup,
                        quantity,
                        units_per_pack,
                        equivalent_units,
                        movement_kind,
                        source_type,
                        source_label,
                        occurred_at,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        movement_id,
                        warehouse_id,
                        int(resolved_product_row["id"]),
                        product_name,
                        product_lookup,
                        supplier_name,
                        supplier_lookup,
                        lot_code,
                        lot_lookup,
                        float(payload.quantity),
                        units_per_pack,
                        target_equivalent_units,
                        "out",
                        "manual_consumption",
                        f"Consumo manuale {warehouse_row['name']}",
                        occurred_at,
                        timestamp,
                        timestamp,
                    ),
                )
                connection.execute(
                    """
                    UPDATE tenant_inventory_warehouses
                    SET updated_at = ?
                    WHERE id = ?
                    """,
                    (timestamp, warehouse_id),
                )
                self._save_inventory_warehouse_session_from_stock(
                    connection,
                    session,
                    warehouse_id,
                    inventory_date=consumption_inventory_date,
                    timestamp=timestamp,
                )
                self._refresh_inventory_consumption_product_stats(connection, timestamp=timestamp)
                stat_row = self._read_inventory_consumption_product_stat_row(
                    connection,
                    product_lookup=product_lookup,
                    supplier_lookup=supplier_lookup,
                )
                if stat_row is not None:
                    product_consumption_stat = self._serialize_inventory_consumption_product_stat_row(stat_row)
                connection.commit()

                movement_row = connection.execute(
                    """
                    SELECT
                        movements.*,
                        warehouses.name AS warehouse_name
                    FROM tenant_inventory_movements AS movements
                    JOIN tenant_inventory_warehouses AS warehouses
                        ON warehouses.id = movements.warehouse_id
                    WHERE movements.id = ?
                    LIMIT 1
                    """,
                    (movement_id,),
                ).fetchone()
                if movement_row is None:
                    raise KeyError("Consumo salvato ma non ricaricabile.")

        return {
            "entry": self._serialize_inventory_movement_row(movement_row),
            "warehouse": serialized_warehouse,
            "warehouse_detail": self.get_inventory_warehouse_detail(session, warehouse_id),
            "product_consumption_stat": product_consumption_stat,
        }

    def _read_homemade_recipe_row(self, connection: sqlite3.Connection, recipe_id: str) -> sqlite3.Row:
        row = connection.execute(
            "SELECT * FROM tenant_homemade_recipes WHERE id = ? LIMIT 1",
            (recipe_id,),
        ).fetchone()
        if row is None:
            raise KeyError("Ricetta homemade non trovata.")
        return row

    def _resolve_homemade_linked_recipe_row(
        self,
        connection: sqlite3.Connection,
        linked_recipe_id: str,
        *,
        current_recipe_id: str | None = None,
    ) -> sqlite3.Row:
        row = connection.execute(
            """
            SELECT id, name, name_lookup
            FROM tenant_homemade_recipes
            WHERE id = ?
            LIMIT 1
            """,
            (linked_recipe_id,),
        ).fetchone()
        if row is None:
            raise ValueError("La prep interna selezionata non esiste piu nel locale.")
        if current_recipe_id and str(row["id"]) == current_recipe_id:
            raise ValueError("Una ricetta non puo usare se stessa come prep interna.")
        return row

    def _normalize_homemade_write_ingredients(
        self,
        connection: sqlite3.Connection,
        ingredients_payload: list[HomemadeRecipeIngredientPayload],
        *,
        current_recipe_id: str | None = None,
    ) -> tuple[list[dict[str, object]], float, bool, Literal["ml", "g"]]:
        normalized_ingredients: list[dict[str, object]] = []
        for index, item in enumerate(ingredients_payload):
            part_amount = float(item.part_amount or 0)
            if part_amount <= 0:
                continue

            measurement_unit = _normalize_homemade_ingredient_unit(getattr(item, "measurement_unit", "ml"))
            linked_recipe_id = str(getattr(item, "linked_recipe_id", "") or "").strip() or None
            ingredient_name = str(item.ingredient_name or "").strip()

            if linked_recipe_id:
                linked_recipe_row = self._resolve_homemade_linked_recipe_row(
                    connection,
                    linked_recipe_id,
                    current_recipe_id=current_recipe_id,
                )
                if measurement_unit not in {"ml", "part"}:
                    raise ValueError("Una prep interna puo essere dosata solo in ml o parti.")
                ingredient_name = str(linked_recipe_row["name"] or "").strip()
                ingredient_lookup = _normalize_lookup(str(linked_recipe_row["name_lookup"] or ingredient_name))
            else:
                if not ingredient_name:
                    continue
                ingredient_lookup = _normalize_lookup(ingredient_name)

            normalized_ingredients.append(
                {
                    "ingredient_name": ingredient_name,
                    "ingredient_lookup": ingredient_lookup,
                    "linked_recipe_id": linked_recipe_id,
                    "part_amount": part_amount,
                    "measurement_unit": measurement_unit,
                    "sort_order": index,
                }
            )

        if not normalized_ingredients:
            raise ValueError("Inserisci almeno un ingrediente con una dose valida.")

        total_parts = _compute_homemade_reference_total(normalized_ingredients)
        has_fixed_per_liter_ingredients = any(
            str(item.get("measurement_unit") or "ml") in {"g_per_liter", "drops_per_liter"}
            for item in normalized_ingredients
        )
        if total_parts <= 0 and not has_fixed_per_liter_ingredients:
            raise ValueError("La ricetta deve avere almeno una dose proporzionale valida.")
        recipe_measurement_unit = _derive_homemade_recipe_measurement_unit(normalized_ingredients)
        return normalized_ingredients, total_parts, has_fixed_per_liter_ingredients, recipe_measurement_unit

    def _normalize_homemade_yield_payload(
        self,
        payload: HomemadeRecipeWritePayload,
        normalized_ingredients: list[dict[str, object]],
    ) -> dict[str, object | None]:
        ingredient_name = str(getattr(payload, "yield_ingredient_name", "") or "").strip()
        input_amount = float(getattr(payload, "yield_input_amount", 0) or 0)
        output_ml = float(getattr(payload, "yield_output_ml", 0) or 0)
        input_unit = str(getattr(payload, "yield_input_unit", "") or "").strip().lower()
        if input_unit not in {"g", "ml"}:
            input_unit = "g"

        if not ingredient_name and input_amount <= 0 and output_ml <= 0:
            return {
                "yield_ingredient_name": None,
                "yield_ingredient_lookup": None,
                "yield_input_amount": None,
                "yield_input_unit": None,
                "yield_output_ml": None,
            }

        if not ingredient_name or input_amount <= 0 or output_ml <= 0:
            raise ValueError(
                "Completa ingrediente guida, quantita base e resa finale stimata, oppure lascia la resa vuota."
            )

        ingredient_lookup = _normalize_lookup(ingredient_name)
        matched_ingredient = next(
            (
                ingredient
                for ingredient in normalized_ingredients
                if str(ingredient.get("ingredient_lookup") or "") == ingredient_lookup
            ),
            None,
        )
        if matched_ingredient is None:
            matched_ingredient = next(
                (
                    ingredient
                    for ingredient in normalized_ingredients
                    if ingredient_lookup
                    and (
                        ingredient_lookup in str(ingredient.get("ingredient_lookup") or "")
                        or str(ingredient.get("ingredient_lookup") or "") in ingredient_lookup
                    )
                ),
                None,
            )
        if matched_ingredient is None:
            raise ValueError("Scegli l'ingrediente guida tra gli ingredienti della ricetta.")

        canonical_ingredient_name = str(matched_ingredient.get("ingredient_name") or "").strip()
        canonical_ingredient_lookup = str(matched_ingredient.get("ingredient_lookup") or "").strip()
        return {
            "yield_ingredient_name": canonical_ingredient_name,
            "yield_ingredient_lookup": canonical_ingredient_lookup,
            "yield_input_amount": input_amount,
            "yield_input_unit": input_unit,
            "yield_output_ml": output_ml,
        }

    def _load_homemade_recipe_ingredients(
        self,
        connection: sqlite3.Connection,
        recipe_ids: list[str],
    ) -> dict[str, list[dict[str, object]]]:
        grouped: dict[str, list[dict[str, object]]] = {recipe_id: [] for recipe_id in recipe_ids}
        if not recipe_ids:
            return grouped
        placeholders = ", ".join("?" for _ in recipe_ids)
        rows = connection.execute(
            f"""
            SELECT
                ingredients.*,
                COALESCE(NULLIF(trim(ingredients.measurement_unit), ''), recipes.measurement_unit, 'ml') AS effective_measurement_unit,
                recipes.yield_ingredient_name AS recipe_yield_ingredient_name,
                recipes.yield_input_amount AS recipe_yield_input_amount,
                recipes.yield_output_ml AS recipe_yield_output_ml,
                linked_recipes.name AS linked_recipe_name
            FROM tenant_homemade_recipe_ingredients AS ingredients
            JOIN tenant_homemade_recipes AS recipes
              ON recipes.id = ingredients.recipe_id
            LEFT JOIN tenant_homemade_recipes AS linked_recipes
              ON linked_recipes.id = ingredients.linked_recipe_id
            WHERE recipe_id IN ({placeholders})
            ORDER BY ingredients.sort_order ASC, lower(ingredients.ingredient_name) ASC
            """,
            tuple(recipe_ids),
        ).fetchall()
        rows_by_recipe: dict[str, list[sqlite3.Row]] = {recipe_id: [] for recipe_id in recipe_ids}
        for row in rows:
            recipe_id = str(row["recipe_id"])
            rows_by_recipe.setdefault(recipe_id, []).append(row)
        for recipe_id, recipe_rows in rows_by_recipe.items():
            yield_ingredient_name = (
                str(recipe_rows[0]["recipe_yield_ingredient_name"] or "").strip() if recipe_rows else ""
            )
            yield_input_amount = float(recipe_rows[0]["recipe_yield_input_amount"] or 0) if recipe_rows else 0.0
            yield_output_ml = float(recipe_rows[0]["recipe_yield_output_ml"] or 0) if recipe_rows else 0.0
            reference_ingredients = [
                {
                    "part_amount": float(row["part_amount"] or 0),
                    "measurement_unit": str(
                        row["effective_measurement_unit"]
                        if "effective_measurement_unit" in row.keys()
                        else row["measurement_unit"] if "measurement_unit" in row.keys() else "ml"
                    ),
                }
                for row in recipe_rows
            ]
            reference_total = _compute_homemade_recipe_reference_total(
                reference_ingredients,
                yield_ingredient_name=yield_ingredient_name,
                yield_input_amount=yield_input_amount,
                yield_output_ml=yield_output_ml,
            )
            grouped[recipe_id] = [
                self._serialize_homemade_recipe_ingredient_row(row, reference_total)
                for row in recipe_rows
            ]
        return grouped

    def _serialize_homemade_recipe_payload(
        self,
        row: sqlite3.Row,
        ingredients: list[dict[str, object]],
    ) -> dict[str, object]:
        recipe_payload = self._serialize_homemade_recipe_row(row)
        recipe_payload["total_parts"] = _compute_homemade_reference_total(ingredients)
        recipe_payload["ingredient_count"] = len(ingredients)
        recipe_payload["ingredients"] = ingredients
        return recipe_payload

    def list_homemade_recipes(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        with self._connect_tenant_database(session.database_path) as connection:
            recipe_rows = connection.execute(
                """
                SELECT *
                FROM tenant_homemade_recipes
                ORDER BY lower(name) ASC, created_at ASC
                """
            ).fetchall()
            recipe_ids = [str(row["id"]) for row in recipe_rows]
            ingredients_by_recipe = self._load_homemade_recipe_ingredients(connection, recipe_ids)

        recipes = [
            self._serialize_homemade_recipe_payload(row, ingredients_by_recipe.get(str(row["id"]), []))
            for row in recipe_rows
        ]
        return {
            "recipes": recipes,
            "total_count": len(recipes),
            "can_manage": self._can_manage_homemade(session),
        }

    def get_homemade_recipe(self, session: SessionIdentity, recipe_id: str) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        with self._connect_tenant_database(session.database_path) as connection:
            recipe_row = self._read_homemade_recipe_row(connection, recipe_id)
            ingredients = self._load_homemade_recipe_ingredients(connection, [recipe_id]).get(recipe_id, [])

        return {"recipe": self._serialize_homemade_recipe_payload(recipe_row, ingredients)}

    def create_homemade_recipe(
        self,
        session: SessionIdentity,
        payload: HomemadeRecipeWritePayload,
    ) -> dict[str, object]:
        if not self._can_manage_homemade(session):
            raise ValueError("Il tuo account non puo modificare Homemade.")

        recipe_name = str(payload.name or "").strip()
        if len(recipe_name) < 2:
            raise ValueError("Inserisci un nome ricetta valido.")
        notes = str(payload.notes or "").strip() or None
        usage_scope = _normalize_homemade_usage_scope(getattr(payload, "usage_scope", "both"))

        timestamp = _iso_now()
        recipe_id = f"homemade_recipe_{uuid.uuid4().hex}"
        recipe_lookup = _normalize_lookup(recipe_name)

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                existing = connection.execute(
                    "SELECT id FROM tenant_homemade_recipes WHERE name_lookup = ? LIMIT 1",
                    (recipe_lookup,),
                ).fetchone()
                if existing is not None:
                    raise ValueError("Esiste gia una ricetta homemade con questo nome.")

                normalized_ingredients, total_parts, _has_fixed_per_liter_ingredients, recipe_measurement_unit = (
                    self._normalize_homemade_write_ingredients(connection, payload.ingredients)
                )
                yield_fields = self._normalize_homemade_yield_payload(payload, normalized_ingredients)
                reference_total = _compute_homemade_recipe_reference_total(
                    normalized_ingredients,
                    yield_ingredient_name=yield_fields["yield_ingredient_name"],
                    yield_input_amount=yield_fields["yield_input_amount"],
                    yield_output_ml=yield_fields["yield_output_ml"],
                )

                connection.execute(
                    """
                    INSERT INTO tenant_homemade_recipes (
                        id,
                        name,
                        name_lookup,
                        measurement_unit,
                        usage_scope,
                        notes,
                        yield_ingredient_name,
                        yield_ingredient_lookup,
                        yield_input_amount,
                        yield_input_unit,
                        yield_output_ml,
                        total_parts,
                        ingredient_count,
                        created_by_user_id,
                        created_by_name,
                        created_at,
                        updated_at
                    )
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        recipe_id,
                        recipe_name,
                        recipe_lookup,
                        recipe_measurement_unit,
                        usage_scope,
                        notes,
                        yield_fields["yield_ingredient_name"],
                        yield_fields["yield_ingredient_lookup"],
                        yield_fields["yield_input_amount"],
                        yield_fields["yield_input_unit"],
                        yield_fields["yield_output_ml"],
                        total_parts,
                        len(normalized_ingredients),
                        session.user_id,
                        session.user_name or session.username or session.user_email,
                        timestamp,
                        timestamp,
                    ),
                )

                for ingredient in normalized_ingredients:
                    ingredient_unit = str(ingredient.get("measurement_unit") or "ml")
                    share_ratio = (
                        float(ingredient["part_amount"]) / reference_total
                        if ingredient_unit not in {"g_per_liter", "drops_per_liter"} and reference_total > 0
                        else 0.0
                    )
                    connection.execute(
                        """
                        INSERT INTO tenant_homemade_recipe_ingredients (
                            id,
                            recipe_id,
                            ingredient_name,
                            ingredient_lookup,
                            linked_recipe_id,
                            part_amount,
                            measurement_unit,
                            share_ratio,
                            percentage,
                            sort_order,
                            created_at,
                            updated_at
                        )
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"homemade_ing_{uuid.uuid4().hex}",
                            recipe_id,
                            ingredient["ingredient_name"],
                            ingredient["ingredient_lookup"],
                            ingredient.get("linked_recipe_id"),
                            float(ingredient["part_amount"]),
                            ingredient_unit,
                            share_ratio,
                            share_ratio * 100,
                            int(ingredient["sort_order"]),
                            timestamp,
                            timestamp,
                        ),
                    )

                connection.commit()
                recipe_row = self._read_homemade_recipe_row(connection, recipe_id)
                ingredients = self._load_homemade_recipe_ingredients(connection, [recipe_id]).get(recipe_id, [])

        return {"recipe": self._serialize_homemade_recipe_payload(recipe_row, ingredients)}

    def update_homemade_recipe(
        self,
        session: SessionIdentity,
        recipe_id: str,
        payload: HomemadeRecipeWritePayload,
    ) -> dict[str, object]:
        if not self._can_manage_homemade(session):
            raise ValueError("Il tuo account non puo modificare Homemade.")

        recipe_name = str(payload.name or "").strip()
        if len(recipe_name) < 2:
            raise ValueError("Inserisci un nome ricetta valido.")
        notes = str(payload.notes or "").strip() or None
        usage_scope = _normalize_homemade_usage_scope(getattr(payload, "usage_scope", "both"))

        timestamp = _iso_now()
        recipe_lookup = _normalize_lookup(recipe_name)

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._read_homemade_recipe_row(connection, recipe_id)
                existing = connection.execute(
                    "SELECT id FROM tenant_homemade_recipes WHERE name_lookup = ? AND id != ? LIMIT 1",
                    (recipe_lookup, recipe_id),
                ).fetchone()
                if existing is not None:
                    raise ValueError("Esiste gia una ricetta homemade con questo nome.")

                normalized_ingredients, total_parts, _has_fixed_per_liter_ingredients, recipe_measurement_unit = (
                    self._normalize_homemade_write_ingredients(
                        connection,
                        payload.ingredients,
                        current_recipe_id=recipe_id,
                    )
                )
                yield_fields = self._normalize_homemade_yield_payload(payload, normalized_ingredients)
                reference_total = _compute_homemade_recipe_reference_total(
                    normalized_ingredients,
                    yield_ingredient_name=yield_fields["yield_ingredient_name"],
                    yield_input_amount=yield_fields["yield_input_amount"],
                    yield_output_ml=yield_fields["yield_output_ml"],
                )

                connection.execute(
                    """
                    UPDATE tenant_homemade_recipes
                    SET name = ?,
                        name_lookup = ?,
                        measurement_unit = ?,
                        usage_scope = ?,
                        notes = ?,
                        yield_ingredient_name = ?,
                        yield_ingredient_lookup = ?,
                        yield_input_amount = ?,
                        yield_input_unit = ?,
                        yield_output_ml = ?,
                        total_parts = ?,
                        ingredient_count = ?,
                        updated_at = ?
                    WHERE id = ?
                    """,
                    (
                        recipe_name,
                        recipe_lookup,
                        recipe_measurement_unit,
                        usage_scope,
                        notes,
                        yield_fields["yield_ingredient_name"],
                        yield_fields["yield_ingredient_lookup"],
                        yield_fields["yield_input_amount"],
                        yield_fields["yield_input_unit"],
                        yield_fields["yield_output_ml"],
                        total_parts,
                        len(normalized_ingredients),
                        timestamp,
                        recipe_id,
                    ),
                )
                connection.execute(
                    "DELETE FROM tenant_homemade_recipe_ingredients WHERE recipe_id = ?",
                    (recipe_id,),
                )
                for ingredient in normalized_ingredients:
                    ingredient_unit = str(ingredient.get("measurement_unit") or "ml")
                    share_ratio = (
                        float(ingredient["part_amount"]) / reference_total
                        if ingredient_unit not in {"g_per_liter", "drops_per_liter"} and reference_total > 0
                        else 0.0
                    )
                    connection.execute(
                        """
                        INSERT INTO tenant_homemade_recipe_ingredients (
                            id,
                            recipe_id,
                            ingredient_name,
                            ingredient_lookup,
                            linked_recipe_id,
                            part_amount,
                            measurement_unit,
                            share_ratio,
                            percentage,
                            sort_order,
                            created_at,
                            updated_at
                        )
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"homemade_ing_{uuid.uuid4().hex}",
                            recipe_id,
                            ingredient["ingredient_name"],
                            ingredient["ingredient_lookup"],
                            ingredient.get("linked_recipe_id"),
                            float(ingredient["part_amount"]),
                            ingredient_unit,
                            share_ratio,
                            share_ratio * 100,
                            int(ingredient["sort_order"]),
                            timestamp,
                            timestamp,
                        ),
                    )

                connection.commit()
                recipe_row = self._read_homemade_recipe_row(connection, recipe_id)
                ingredients = self._load_homemade_recipe_ingredients(connection, [recipe_id]).get(recipe_id, [])

        return {"recipe": self._serialize_homemade_recipe_payload(recipe_row, ingredients)}

    def delete_homemade_recipe(
        self,
        session: SessionIdentity,
        recipe_id: str,
    ) -> dict[str, object]:
        if not self._can_manage_homemade(session):
            raise ValueError("Il tuo account non puo modificare Homemade.")

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                recipe_row = self._read_homemade_recipe_row(connection, recipe_id)
                ingredients = self._load_homemade_recipe_ingredients(connection, [recipe_id]).get(recipe_id, [])
                serialized_recipe = self._serialize_homemade_recipe_payload(recipe_row, ingredients)
                connection.execute("DELETE FROM tenant_homemade_recipes WHERE id = ?", (recipe_id,))
                connection.commit()

        return {"deleted": True, "recipe": serialized_recipe}

    def set_homemade_recipe_preparation(
        self,
        session: SessionIdentity,
        recipe_id: str,
        *,
        prepared_on: str,
        drive_file_id: str | None = None,
        drive_web_url: str | None = None,
        drive_updated_at: str | None = None,
    ) -> dict[str, object]:
        if not self._can_access_homemade(session):
            raise ValueError("Il tuo account non puo accedere a Homemade.")

        timestamp = _iso_now()

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                current_row = self._read_homemade_recipe_row(connection, recipe_id)
                connection.execute(
                    """
                    UPDATE tenant_homemade_recipes
                    SET preparation_date = ?,
                        preparation_drive_file_id = ?,
                        preparation_drive_web_url = ?,
                        preparation_drive_updated_at = ?,
                        updated_at = ?
                    WHERE id = ?
                    """,
                    (
                        prepared_on,
                        drive_file_id or str(current_row["preparation_drive_file_id"] or "") or None,
                        drive_web_url or str(current_row["preparation_drive_web_url"] or "") or None,
                        drive_updated_at or timestamp,
                        timestamp,
                        recipe_id,
                    ),
                )
                connection.commit()
                recipe_row = self._read_homemade_recipe_row(connection, recipe_id)
                ingredients = self._load_homemade_recipe_ingredients(connection, [recipe_id]).get(recipe_id, [])

        return {"recipe": self._serialize_homemade_recipe_payload(recipe_row, ingredients)}

    def _is_super_admin_configured(self, connection: sqlite3.Connection) -> bool:
        row = connection.execute(
            "SELECT password_hash FROM users WHERE id = ? LIMIT 1",
            (SUPER_ADMIN_USER_ID,),
        ).fetchone()
        return bool(row and row["password_hash"].strip())

    def _build_platform_admin_context(self, session: SessionIdentity) -> dict[str, object] | None:
        if session.role != "super_admin":
            return None

        return {
            "is_super_admin": True,
            "is_impersonating": session.tenant_id != session.home_tenant_id,
            "home_tenant_id": session.home_tenant_id,
            "home_tenant_slug": session.home_tenant_slug,
            "home_tenant_name": session.home_tenant_name,
            "acting_tenant_id": session.tenant_id,
            "acting_tenant_slug": session.tenant_slug,
        }

    def build_auth_response(self, session: SessionIdentity) -> dict[str, object]:
        return {
            "success": True,
            "session_token": session.token,
            "current_user": {
                "id": session.user_id,
                "tenant_id": session.tenant_id,
                "name": session.user_name,
                "username": session.username,
                "email": session.user_email,
                "role": session.role,
                "permissions": list(session.permissions),
                "assistant_scopes": list(session.assistant_scopes),
            },
            "tenant_context": self.get_tenant_context(session.tenant_id),
            "platform_admin": self._build_platform_admin_context(session),
        }

    def _create_customer_tenant(
        self,
        connection: sqlite3.Connection,
        payload: RegisterTenantPayload | AdminCreateTenantPayload,
    ) -> tuple[str, str]:
        normalized_payload = self._normalize_register_like_payload(payload)
        locale_name = (normalized_payload["locale_name"] or "").strip()
        admin_name = (normalized_payload["admin_name"] or "").strip()
        username = (normalized_payload["username"] or "").strip()
        email = (normalized_payload["email"] or "").strip()
        address = (normalized_payload["address"] or "").strip()
        phone_number = normalized_payload["phone_number"]
        whatsapp_number = normalized_payload["whatsapp_number"]

        if not locale_name or not admin_name or not username or not email or not payload.password.strip():
            raise ValueError("Compila tutti i campi obbligatori per creare il locale")

        slug = _normalize_slug(locale_name)
        tenant_id = f"tenant_{uuid.uuid4().hex}"
        venue_id = f"venue_{uuid.uuid4().hex}"
        user_id = f"user_{uuid.uuid4().hex}"
        created_at = _iso_now()
        password_hash = _hash_password(payload.password.strip())
        database_path = str(self._tenant_dir / f"{slug}.sqlite3")

        slug_exists = connection.execute("SELECT 1 FROM tenants WHERE slug = ?", (slug,)).fetchone()
        if slug_exists is not None:
            raise ValueError("Esiste gia un locale con questo nome. Scegli un nome differente.")

        user_exists = self._lookup_user_for_login(connection, username) or self._lookup_user_for_login(connection, email)
        if user_exists is not None:
            raise ValueError("Username o email gia registrati.")

        connection.execute(
            """
            INSERT INTO tenants (id, name, slug, database_path, created_at)
            VALUES (?, ?, ?, ?, ?)
            """,
            (tenant_id, locale_name, slug, database_path, created_at),
        )
        connection.execute(
            """
            INSERT INTO venues (id, tenant_id, name, address, phone_number, whatsapp_number, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?)
            """,
            (venue_id, tenant_id, locale_name, address, phone_number, whatsapp_number, created_at),
        )
        connection.execute(
            """
            INSERT INTO users (id, tenant_id, name, username, email, phone_number, password_hash, role, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                user_id,
                tenant_id,
                admin_name,
                username,
                email,
                phone_number,
                password_hash,
                "owner",
                created_at,
            ),
        )

        for module in MODULE_CATALOG:
            enabled = 1 if module.status == "active" else 0
            plan_name = "Platform Active" if enabled else "Planned"
            connection.execute(
                """
                INSERT INTO tenant_modules (id, tenant_id, module_key, enabled, plan_name, activated_at, expires_at)
                VALUES (?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    f"tm_{uuid.uuid4().hex}",
                    tenant_id,
                    module.key,
                    enabled,
                    plan_name,
                    created_at if enabled else None,
                    None,
                ),
            )

        connection.commit()
        self._create_tenant_database(
            database_path=database_path,
            tenant_id=tenant_id,
            tenant_name=locale_name,
            tenant_slug=slug,
            venue_name=locale_name,
            address=address,
            phone_number=phone_number,
            whatsapp_number=whatsapp_number,
            admin_name=admin_name,
            admin_username=username,
            admin_email=email,
            admin_phone_number=phone_number,
            password_hash=password_hash,
            created_at=created_at,
        )
        return tenant_id, user_id

    def has_tenants(self) -> bool:
        with self._connect_registry() as connection:
            row = connection.execute(
                "SELECT COUNT(*) AS total FROM tenants WHERE id != ?",
                (SUPER_ADMIN_TENANT_ID,),
            ).fetchone()
            return bool(row and row["total"])

    def status(self) -> dict[str, object]:
        with self._connect_registry() as connection:
            tenant_count = connection.execute(
                "SELECT COUNT(*) AS total FROM tenants WHERE id != ?",
                (SUPER_ADMIN_TENANT_ID,),
            ).fetchone()["total"]
            user_count = connection.execute(
                "SELECT COUNT(*) AS total FROM users WHERE id != ?",
                (SUPER_ADMIN_USER_ID,),
            ).fetchone()["total"]
            super_admin_configured = self._is_super_admin_configured(connection)
            return {
                "status": "ready",
                "mode": "sqlite-multi-tenant",
                "tenant_count": tenant_count,
                "user_count": user_count,
                "first_run": not super_admin_configured,
                "super_admin_bootstrap_required": not super_admin_configured,
                "super_admin_identifier": SUPER_ADMIN_USERNAME,
                "next_step": (
                    "Imposta la password iniziale del super admin."
                    if not super_admin_configured
                    else "Accedi come super admin e crea o gestisci i locali dal pannello admin."
                ),
            }

    def register(self, payload: RegisterTenantPayload) -> SessionIdentity:
        with self._lock:
            with self._connect_registry() as connection:
                tenant_id, user_id = self._create_customer_tenant(connection, payload)
                return self._create_session(connection, tenant_id=tenant_id, user_id=user_id)

    def login(self, payload: LoginPayload) -> SessionIdentity:
        identifier = payload.identifier.strip()
        password = payload.password.strip()
        if not identifier or not password:
            raise ValueError("Inserisci credenziali valide.")

        with self._lock:
            with self._connect_registry() as connection:
                candidates = self._lookup_login_candidates(connection, identifier)
                if not candidates:
                    raise ValueError("Credenziali non valide.")

                bootstrap_required = False
                for user in candidates:
                    stored_hash = (user["password_hash"] or "").strip()
                    if user["role"] == "super_admin" and not stored_hash:
                        bootstrap_required = True
                        continue
                    if not stored_hash or not _verify_password(password, stored_hash):
                        continue
                    return self._create_session(connection, tenant_id=user["tenant_id"], user_id=user["id"])

                if bootstrap_required and len(candidates) == 1 and candidates[0]["role"] == "super_admin":
                    raise ValueError("Password super admin non ancora impostata. Completa la prima attivazione.")

                raise ValueError("Credenziali non valide.")

    def logout(self, token: str) -> None:
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute("DELETE FROM sessions WHERE token = ?", (token,))
                connection.commit()

    def get_session(self, token: str) -> SessionIdentity | None:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT
                    sessions.token,
                    sessions.tenant_id,
                    sessions.user_id,
                    sessions.expires_at,
                    tenants.slug AS tenant_slug,
                    tenants.name AS tenant_name,
                    home_tenants.id AS home_tenant_id,
                    home_tenants.slug AS home_tenant_slug,
                    home_tenants.name AS home_tenant_name,
                    tenants.database_path AS database_path,
                    users.email AS user_email,
                    users.username AS username,
                    users.name AS user_name,
                    users.role AS role,
                    users.permissions_json AS permissions_json,
                    users.assistant_scopes_json AS assistant_scopes_json
                FROM sessions
                JOIN tenants ON tenants.id = sessions.tenant_id
                JOIN users ON users.id = sessions.user_id
                JOIN tenants AS home_tenants ON home_tenants.id = users.tenant_id
                WHERE sessions.token = ?
                LIMIT 1
                """,
                (token,),
            ).fetchone()

            if row is None:
                return None

            expires_at = datetime.fromisoformat(row["expires_at"])
            if expires_at < _utcnow():
                connection.execute("DELETE FROM sessions WHERE token = ?", (token,))
                connection.commit()
                return None

            return self._session_identity_from_row(row)

    def build_service_session(self, tenant_id: str) -> SessionIdentity | None:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT
                    tenants.id AS tenant_id,
                    tenants.slug AS tenant_slug,
                    tenants.name AS tenant_name,
                    tenants.database_path AS database_path,
                    users.id AS user_id,
                    users.email AS user_email,
                    users.username AS username,
                    users.name AS user_name,
                    users.role AS role,
                    users.permissions_json AS permissions_json,
                    users.assistant_scopes_json AS assistant_scopes_json
                FROM tenants
                JOIN users ON users.tenant_id = tenants.id
                WHERE tenants.id = ?
                ORDER BY
                    CASE users.role
                        WHEN 'owner' THEN 0
                        WHEN 'staff' THEN 1
                        ELSE 2
                    END,
                    users.created_at ASC
                LIMIT 1
                """,
                (tenant_id,),
            ).fetchone()

        if row is None:
            return None

        return SessionIdentity(
            token=f"service:{tenant_id}",
            tenant_id=row["tenant_id"],
            tenant_slug=row["tenant_slug"],
            tenant_name=row["tenant_name"],
            home_tenant_id=row["tenant_id"],
            home_tenant_slug=row["tenant_slug"],
            home_tenant_name=row["tenant_name"],
            user_id=row["user_id"],
            user_email=row["user_email"],
            username=row["username"],
            user_name=row["user_name"],
            role=row["role"],
            permissions=_resolve_role_permissions(row["role"], row["permissions_json"]),
            assistant_scopes=_resolve_user_assistant_scopes(
                row["role"],
                row["permissions_json"],
                row["assistant_scopes_json"],
            ),
            database_path=row["database_path"],
            expires_at=(_utcnow() + timedelta(days=3650)).isoformat(),
        )

    def get_tenant_context(self, tenant_id: str) -> dict[str, object]:
        with self._connect_registry() as connection:
            tenant_row, venue_rows, user_rows, module_rows = self._context_rows(connection, tenant_id)

        return {
            "tenant": Tenant(
                id=tenant_row["id"],
                name=tenant_row["name"],
                slug=tenant_row["slug"],
                created_at=tenant_row["created_at"],
            ),
            "venues": [
                Venue(
                    id=row["id"],
                    tenant_id=row["tenant_id"],
                    name=row["name"],
                    address=row["address"],
                    phone_number=row["phone_number"],
                    whatsapp_number=row["whatsapp_number"],
                    created_at=row["created_at"],
                )
                for row in venue_rows
            ],
            "users": [
                User(
                    id=row["id"],
                    tenant_id=row["tenant_id"],
                    name=row["name"],
                    username=row["username"],
                    email=row["email"],
                    phone_number=row["phone_number"],
                    role=row["role"],
                    created_at=row["created_at"],
                )
                for row in user_rows
            ],
            "tenant_modules": [
                TenantModule(
                    id=row["id"],
                    tenant_id=row["tenant_id"],
                    module_key=row["module_key"],
                    enabled=bool(row["enabled"]),
                    plan_name=row["plan_name"],
                    activated_at=row["activated_at"],
                    expires_at=row["expires_at"],
                )
                for row in module_rows
            ],
        }

    def update_venue_info(
        self,
        session: SessionIdentity,
        *,
        venue_name: str | None = None,
        address: str | None = None,
        phone_number: str | None = None,
        whatsapp_number: str | None = None,
    ) -> None:
        if session.role not in ("owner", "manager", "super_admin"):
            raise ValueError("Operazione riservata all'admin del locale.")

        registry_assignments: list[str] = []
        registry_values: list[object] = []
        tenant_assignments: list[str] = []
        tenant_values: list[object] = []

        if venue_name is not None:
            v = venue_name.strip() or None
            registry_assignments.append("name = ?")
            registry_values.append(v)
            tenant_assignments.append("venue_name = ?")
            tenant_values.append(v)
        if address is not None:
            v = address.strip() or None
            registry_assignments.append("address = ?")
            registry_values.append(v)
            tenant_assignments.append("address = ?")
            tenant_values.append(v)
        if phone_number is not None:
            v = phone_number.strip() or None
            registry_assignments.append("phone_number = ?")
            registry_values.append(v)
            tenant_assignments.append("phone_number = ?")
            tenant_values.append(v)
        if whatsapp_number is not None:
            v = whatsapp_number.strip() or None
            registry_assignments.append("whatsapp_number = ?")
            registry_values.append(v)
            tenant_assignments.append("whatsapp_number = ?")
            tenant_values.append(v)

        if not registry_assignments:
            return

        with self._lock:
            with self._connect_registry() as connection:
                venue_row = connection.execute(
                    "SELECT id FROM venues WHERE tenant_id = ? LIMIT 1",
                    (session.tenant_id,),
                ).fetchone()
                if venue_row is None:
                    raise KeyError("Nessun locale trovato per questo tenant.")
                registry_values.append(venue_row["id"])
                connection.execute(
                    f"UPDATE venues SET {', '.join(registry_assignments)} WHERE id = ?",
                    registry_values,
                )
                connection.commit()

        if session.database_path and tenant_assignments:
            with self._connect_tenant_database(session.database_path) as tenant_conn:
                tenant_values.append(session.tenant_id)
                tenant_conn.execute(
                    f"UPDATE tenant_profile SET {', '.join(tenant_assignments)} WHERE tenant_id = ?",
                    tenant_values,
                )
                tenant_conn.commit()

    def get_context_by_slug(self, tenant_slug: str) -> dict[str, object] | None:
        with self._connect_registry() as connection:
            tenant = connection.execute("SELECT id FROM tenants WHERE slug = ? LIMIT 1", (tenant_slug,)).fetchone()
            if tenant is None:
                return None

        return self.get_tenant_context(tenant["id"])

    def bootstrap_super_admin(self, payload: BootstrapSuperAdminPayload) -> SessionIdentity:
        password = payload.password.strip()
        if not password:
            raise ValueError("Inserisci una password valida per il super admin.")

        with self._lock:
            with self._connect_registry() as connection:
                if self._is_super_admin_configured(connection):
                    raise ValueError("Il super admin e gia stato configurato.")

                password_hash = _hash_password(password)
                connection.execute(
                    "UPDATE users SET password_hash = ? WHERE id = ?",
                    (password_hash, SUPER_ADMIN_USER_ID),
                )
                connection.commit()
                session = self._create_session(connection, tenant_id=SUPER_ADMIN_TENANT_ID, user_id=SUPER_ADMIN_USER_ID)

        admin_db_path = str(self._tenant_dir / f"{SUPER_ADMIN_TENANT_SLUG}.sqlite3")
        with self._connect_tenant_database(admin_db_path) as admin_connection:
            profile = admin_connection.execute(
                "SELECT created_at FROM tenant_profile WHERE tenant_id = ? LIMIT 1",
                (SUPER_ADMIN_TENANT_ID,),
            ).fetchone()
            created_at = profile["created_at"] if profile else _iso_now()
            self._upsert_tenant_profile(
                admin_connection,
                tenant_id=SUPER_ADMIN_TENANT_ID,
                tenant_name=SUPER_ADMIN_TENANT_NAME,
                tenant_slug=SUPER_ADMIN_TENANT_SLUG,
                venue_name=SUPER_ADMIN_TENANT_NAME,
                address="",
                phone_number=None,
                whatsapp_number=None,
                admin_name="Super Admin",
                admin_username=SUPER_ADMIN_USERNAME,
                admin_email=SUPER_ADMIN_EMAIL,
                admin_phone_number=None,
                password_hash=password_hash,
                created_at=created_at,
            )
            admin_connection.commit()

        return session

    def update_super_admin_password(self, session: SessionIdentity, payload: UpdatePasswordPayload) -> None:
        if session.role != "super_admin":
            raise ValueError("Operazione riservata al super admin.")

        new_password = payload.new_password.strip()
        current_password = payload.current_password.strip() if payload.current_password else ""
        if not new_password:
            raise ValueError("Inserisci una nuova password valida.")

        with self._lock:
            with self._connect_registry() as connection:
                row = connection.execute(
                    "SELECT password_hash FROM users WHERE id = ? LIMIT 1",
                    (SUPER_ADMIN_USER_ID,),
                ).fetchone()
                if row is None:
                    raise ValueError("Account super admin non disponibile.")

                stored_hash = (row["password_hash"] or "").strip()
                if stored_hash and not current_password:
                    raise ValueError("Inserisci la password attuale del super admin.")
                if stored_hash and not _verify_password(current_password, stored_hash):
                    raise ValueError("La password attuale non e corretta.")

                new_hash = _hash_password(new_password)
                connection.execute(
                    "UPDATE users SET password_hash = ? WHERE id = ?",
                    (new_hash, SUPER_ADMIN_USER_ID),
                )
                connection.commit()

        admin_db_path = str(self._tenant_dir / f"{SUPER_ADMIN_TENANT_SLUG}.sqlite3")
        with self._connect_tenant_database(admin_db_path) as admin_connection:
            profile = admin_connection.execute(
                "SELECT created_at FROM tenant_profile WHERE tenant_id = ? LIMIT 1",
                (SUPER_ADMIN_TENANT_ID,),
            ).fetchone()
            created_at = profile["created_at"] if profile else _iso_now()
            self._upsert_tenant_profile(
                admin_connection,
                tenant_id=SUPER_ADMIN_TENANT_ID,
                tenant_name=SUPER_ADMIN_TENANT_NAME,
                tenant_slug=SUPER_ADMIN_TENANT_SLUG,
                venue_name=SUPER_ADMIN_TENANT_NAME,
                address="",
                phone_number=None,
                whatsapp_number=None,
                admin_name=session.user_name or "Super Admin",
                admin_username=SUPER_ADMIN_USERNAME,
                admin_email=SUPER_ADMIN_EMAIL,
                admin_phone_number=None,
                password_hash=new_hash,
                created_at=created_at,
            )
            admin_connection.commit()

    def update_current_user_password(self, session: SessionIdentity, payload: UpdatePasswordPayload) -> None:
        if session.role == "super_admin":
            self.update_super_admin_password(session, payload)
            return

        new_password = payload.new_password.strip()
        current_password = payload.current_password.strip() if payload.current_password else ""
        if not new_password:
            raise ValueError("Inserisci una nuova password valida.")

        with self._lock:
            with self._connect_registry() as connection:
                row = connection.execute(
                    """
                    SELECT password_hash
                    FROM users
                    WHERE id = ? AND tenant_id = ?
                    LIMIT 1
                    """,
                    (session.user_id, session.tenant_id),
                ).fetchone()
                if row is None:
                    raise ValueError("Account del locale non disponibile.")

                stored_hash = (row["password_hash"] or "").strip()
                if stored_hash and not current_password:
                    raise ValueError("Inserisci la password attuale.")
                if stored_hash and not _verify_password(current_password, stored_hash):
                    raise ValueError("La password attuale non e corretta.")

                new_hash = _hash_password(new_password)
                connection.execute(
                    "UPDATE users SET password_hash = ? WHERE id = ?",
                    (new_hash, session.user_id),
                )
                connection.commit()

        if session.role == "owner":
            with self._connect_tenant_database(session.database_path) as tenant_connection:
                tenant_connection.execute(
                    """
                    UPDATE tenant_profile
                    SET password_hash = ?, updated_at = ?
                    WHERE tenant_id = ?
                    """,
                    (new_hash, _iso_now(), session.tenant_id),
                )
                tenant_connection.commit()

    def _tenant_summary_from_row(self, row: sqlite3.Row) -> dict[str, object]:
        venue_payload = None
        if row["venue_id"] is not None:
            venue_payload = Venue(
                id=row["venue_id"],
                tenant_id=row["id"],
                name=row["venue_name"],
                address=row["venue_address"],
                phone_number=row["venue_phone_number"],
                whatsapp_number=row["venue_whatsapp_number"],
                created_at=row["venue_created_at"],
            )

        admin_payload = None
        if row["admin_user_id"] is not None:
            admin_payload = User(
                id=row["admin_user_id"],
                tenant_id=row["id"],
                name=row["admin_user_name"],
                username=row["admin_username"],
                email=row["admin_email"],
                phone_number=row["admin_phone_number"],
                role=row["admin_role"],
                created_at=row["admin_created_at"],
            )

        return {
            "tenant": Tenant(
                id=row["id"],
                name=row["name"],
                slug=row["slug"],
                created_at=row["created_at"],
            ),
            "venue": venue_payload,
            "admin_user": admin_payload,
            "enabled_modules": int(row["enabled_modules"] or 0),
            "total_modules": int(row["total_modules"] or 0),
        }

    def list_customer_tenants(self) -> list[dict[str, object]]:
        with self._connect_registry() as connection:
            rows = list(
                connection.execute(
                    """
                    SELECT
                        tenants.*,
                        venues.id AS venue_id,
                        venues.name AS venue_name,
                        venues.address AS venue_address,
                        venues.phone_number AS venue_phone_number,
                        venues.whatsapp_number AS venue_whatsapp_number,
                        venues.created_at AS venue_created_at,
                        users.id AS admin_user_id,
                        users.name AS admin_user_name,
                        users.username AS admin_username,
                        users.email AS admin_email,
                        users.phone_number AS admin_phone_number,
                        users.role AS admin_role,
                        users.created_at AS admin_created_at,
                        COALESCE(module_stats.enabled_modules, 0) AS enabled_modules,
                        COALESCE(module_stats.total_modules, 0) AS total_modules
                    FROM tenants
                    LEFT JOIN venues ON venues.id = (
                        SELECT id
                        FROM venues AS first_venues
                        WHERE first_venues.tenant_id = tenants.id
                        ORDER BY first_venues.created_at ASC
                        LIMIT 1
                    )
                    LEFT JOIN users ON users.id = (
                        SELECT id
                        FROM users AS first_users
                        WHERE first_users.tenant_id = tenants.id
                        ORDER BY first_users.created_at ASC
                        LIMIT 1
                    )
                    LEFT JOIN (
                        SELECT
                            tenant_id,
                            SUM(CASE WHEN enabled = 1 THEN 1 ELSE 0 END) AS enabled_modules,
                            COUNT(*) AS total_modules
                        FROM tenant_modules
                        GROUP BY tenant_id
                    ) AS module_stats ON module_stats.tenant_id = tenants.id
                    WHERE tenants.id != ?
                    ORDER BY tenants.created_at DESC
                    """,
                    (SUPER_ADMIN_TENANT_ID,),
                )
            )

        return [self._tenant_summary_from_row(row) for row in rows]

    def get_customer_tenant_summary(self, tenant_id: str) -> dict[str, object]:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT
                    tenants.*,
                    venues.id AS venue_id,
                    venues.name AS venue_name,
                    venues.address AS venue_address,
                    venues.phone_number AS venue_phone_number,
                    venues.whatsapp_number AS venue_whatsapp_number,
                    venues.created_at AS venue_created_at,
                    users.id AS admin_user_id,
                    users.name AS admin_user_name,
                    users.username AS admin_username,
                    users.email AS admin_email,
                    users.phone_number AS admin_phone_number,
                    users.role AS admin_role,
                    users.created_at AS admin_created_at,
                    COALESCE(module_stats.enabled_modules, 0) AS enabled_modules,
                    COALESCE(module_stats.total_modules, 0) AS total_modules
                FROM tenants
                LEFT JOIN venues ON venues.id = (
                    SELECT id
                    FROM venues AS first_venues
                    WHERE first_venues.tenant_id = tenants.id
                    ORDER BY first_venues.created_at ASC
                    LIMIT 1
                )
                LEFT JOIN users ON users.id = (
                    SELECT id
                    FROM users AS first_users
                    WHERE first_users.tenant_id = tenants.id
                    ORDER BY first_users.created_at ASC
                    LIMIT 1
                )
                LEFT JOIN (
                    SELECT
                        tenant_id,
                        SUM(CASE WHEN enabled = 1 THEN 1 ELSE 0 END) AS enabled_modules,
                        COUNT(*) AS total_modules
                    FROM tenant_modules
                    GROUP BY tenant_id
                ) AS module_stats ON module_stats.tenant_id = tenants.id
                WHERE tenants.id = ? AND tenants.id != ?
                LIMIT 1
                """,
                (tenant_id, SUPER_ADMIN_TENANT_ID),
            ).fetchone()

        if row is None:
            raise KeyError(f"Tenant {tenant_id} non trovato")
        return self._tenant_summary_from_row(row)

    def get_admin_overview(self) -> dict[str, object]:
        with self._connect_registry() as connection:
            super_admin = connection.execute(
                """
                SELECT id, name, username, email, role, created_at
                FROM users
                WHERE id = ?
                LIMIT 1
                """,
                (SUPER_ADMIN_USER_ID,),
            ).fetchone()
            password_configured = self._is_super_admin_configured(connection)

        if super_admin is None:
            raise KeyError("Account super admin non trovato")

        return {
            "super_admin": {
                "id": super_admin["id"],
                "name": super_admin["name"],
                "username": super_admin["username"],
                "email": super_admin["email"],
                "role": super_admin["role"],
                "created_at": super_admin["created_at"],
                "password_configured": password_configured,
            },
            "tenants": self.list_customer_tenants(),
        }

    def create_tenant_as_super_admin(self, payload: AdminCreateTenantPayload) -> dict[str, object]:
        with self._lock:
            with self._connect_registry() as connection:
                tenant_id, _user_id = self._create_customer_tenant(connection, payload)

        return self.get_customer_tenant_summary(tenant_id)

    def update_tenant_admin(self, tenant_id: str, payload: AdminUpdateTenantAdminPayload) -> dict[str, object]:
        normalized_payload = self._normalize_register_like_payload(payload)
        locale_name = (normalized_payload["locale_name"] or "").strip()
        admin_name = (normalized_payload["admin_name"] or "").strip()
        username = (normalized_payload["username"] or "").strip()
        email = (normalized_payload["email"] or "").strip()
        address = (normalized_payload["address"] or "").strip()
        phone_number = normalized_payload["phone_number"]
        whatsapp_number = normalized_payload["whatsapp_number"]
        next_password = payload.password.strip() if payload.password else None

        if tenant_id == SUPER_ADMIN_TENANT_ID:
            raise ValueError("Il tenant di piattaforma non puo essere modificato da questo endpoint.")
        if not locale_name or not admin_name or not username or not email:
            raise ValueError("Compila tutti i campi obbligatori del locale.")

        with self._lock:
            with self._connect_registry() as connection:
                tenant_row = connection.execute("SELECT * FROM tenants WHERE id = ? LIMIT 1", (tenant_id,)).fetchone()
                if tenant_row is None:
                    raise KeyError(f"Tenant {tenant_id} non trovato")

                venue_row = connection.execute(
                    "SELECT * FROM venues WHERE tenant_id = ? ORDER BY created_at ASC LIMIT 1",
                    (tenant_id,),
                ).fetchone()
                user_row = connection.execute(
                    "SELECT * FROM users WHERE tenant_id = ? ORDER BY created_at ASC LIMIT 1",
                    (tenant_id,),
                ).fetchone()
                if venue_row is None or user_row is None:
                    raise ValueError("Tenant incompleto: venue o admin non trovati.")

                username_conflict = connection.execute(
                    "SELECT id FROM users WHERE lower(username) = ? AND id != ? LIMIT 1",
                    (_normalize_lookup(username), user_row["id"]),
                ).fetchone()
                email_conflict = connection.execute(
                    "SELECT id FROM users WHERE lower(email) = ? AND id != ? LIMIT 1",
                    (_normalize_lookup(email), user_row["id"]),
                ).fetchone()
                if username_conflict is not None or email_conflict is not None:
                    raise ValueError("Username o email gia registrati da un altro account.")

                connection.execute(
                    "UPDATE tenants SET name = ? WHERE id = ?",
                    (locale_name, tenant_id),
                )
                connection.execute(
                    """
                    UPDATE venues
                    SET name = ?, address = ?, phone_number = ?, whatsapp_number = ?
                    WHERE id = ?
                    """,
                    (locale_name, address, phone_number, whatsapp_number, venue_row["id"]),
                )

                assignments = [
                    "name = ?",
                    "username = ?",
                    "email = ?",
                    "phone_number = ?",
                ]
                values: list[object] = [admin_name, username, email, phone_number]
                password_hash = user_row["password_hash"]
                if next_password:
                    password_hash = _hash_password(next_password)
                    assignments.append("password_hash = ?")
                    values.append(password_hash)

                values.append(user_row["id"])
                connection.execute(
                    f"UPDATE users SET {', '.join(assignments)} WHERE id = ?",
                    values,
                )
                connection.commit()

                self._create_tenant_database(
                    database_path=tenant_row["database_path"],
                    tenant_id=tenant_id,
                    tenant_name=locale_name,
                    tenant_slug=tenant_row["slug"],
                    venue_name=locale_name,
                    address=address,
                    phone_number=phone_number,
                    whatsapp_number=whatsapp_number,
                    admin_name=admin_name,
                    admin_username=username,
                    admin_email=email,
                    admin_phone_number=phone_number,
                    password_hash=password_hash,
                    created_at=tenant_row["created_at"],
                )

        return self.get_customer_tenant_summary(tenant_id)

    def list_tenant_users(self, session: SessionIdentity) -> list[dict[str, object]]:
        if not self._can_manage_tenant_accounts(session):
            raise ValueError("Operazione riservata all'admin del locale.")

        with self._connect_registry() as connection:
            rows = list(
                connection.execute(
                    """
                    SELECT id, tenant_id, name, username, email, phone_number, role, permissions_json, assistant_scopes_json, created_at
                    FROM users
                    WHERE tenant_id = ?
                    ORDER BY
                        CASE role
                            WHEN 'owner' THEN 0
                            WHEN 'manager' THEN 1
                            WHEN 'staff' THEN 2
                            ELSE 3
                        END,
                        created_at ASC
                    """,
                    (session.tenant_id,),
                )
            )

        return [
            {
                "id": row["id"],
                "tenant_id": row["tenant_id"],
                "name": row["name"],
                "username": row["username"],
                "email": row["email"],
                "phone_number": row["phone_number"],
                "role": row["role"],
                "permissions": list(_resolve_role_permissions(row["role"], row["permissions_json"])),
                "assistant_scopes": list(
                    _resolve_user_assistant_scopes(row["role"], row["permissions_json"], row["assistant_scopes_json"])
                ),
                "created_at": row["created_at"],
            }
            for row in rows
        ]

    def create_tenant_staff_user(self, session: SessionIdentity, payload: TenantStaffUserCreatePayload) -> dict[str, object]:
        if not self._can_manage_tenant_accounts(session):
            raise ValueError("Operazione riservata all'admin del locale.")

        normalized_payload = self._normalize_tenant_staff_payload(payload)
        name = (normalized_payload["name"] or "").strip()
        username = (normalized_payload["username"] or "").strip()
        email = (normalized_payload["email"] or "").strip()
        phone_number = normalized_payload["phone_number"]
        password = payload.password.strip()
        permissions_json = _serialize_role_permissions("staff", payload.permissions)
        assistant_scopes_json = _serialize_user_assistant_scopes("staff", payload.assistant_scopes)

        if not name or not username or not email or not password:
            raise ValueError("Compila tutti i campi obbligatori del sotto-account.")

        with self._lock:
            with self._connect_registry() as connection:
                username_conflict = connection.execute(
                    "SELECT id FROM users WHERE lower(username) = ? LIMIT 1",
                    (_normalize_lookup(username),),
                ).fetchone()
                email_conflict = connection.execute(
                    "SELECT id FROM users WHERE lower(email) = ? LIMIT 1",
                    (_normalize_lookup(email),),
                ).fetchone()
                if username_conflict is not None or email_conflict is not None:
                    raise ValueError("Username o email gia registrati da un altro account.")

                user_id = f"user_{uuid.uuid4().hex}"
                created_at = _iso_now()
                connection.execute(
                    """
                    INSERT INTO users (
                        id,
                        tenant_id,
                        name,
                        username,
                        email,
                        phone_number,
                        password_hash,
                        role,
                        permissions_json,
                        assistant_scopes_json,
                        created_at
                    )
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        user_id,
                        session.tenant_id,
                        name,
                        username,
                        email,
                        phone_number,
                        _hash_password(password),
                        "staff",
                        permissions_json,
                        assistant_scopes_json,
                        created_at,
                    ),
                )
                connection.commit()

        return next(
            user
            for user in self.list_tenant_users(session)
            if user["id"] == user_id
        )

    def update_tenant_staff_user(
        self,
        session: SessionIdentity,
        user_id: str,
        payload: TenantStaffUserUpdatePayload,
    ) -> dict[str, object]:
        if not self._can_manage_tenant_accounts(session):
            raise ValueError("Operazione riservata all'admin del locale.")

        normalized_payload = self._normalize_tenant_staff_payload(payload)
        name = (normalized_payload["name"] or "").strip()
        username = (normalized_payload["username"] or "").strip()
        email = (normalized_payload["email"] or "").strip()
        phone_number = normalized_payload["phone_number"]
        next_password = payload.password.strip() if payload.password else None
        permissions_json = _serialize_role_permissions("staff", payload.permissions)
        assistant_scopes_json = _serialize_user_assistant_scopes("staff", payload.assistant_scopes)

        if not user_id or not name or not username or not email:
            raise ValueError("Compila tutti i campi obbligatori del sotto-account.")

        with self._lock:
            with self._connect_registry() as connection:
                user_row = connection.execute(
                    """
                    SELECT * FROM users
                    WHERE id = ? AND tenant_id = ?
                    LIMIT 1
                    """,
                    (user_id, session.tenant_id),
                ).fetchone()
                if user_row is None:
                    raise KeyError("Sotto-account non trovato.")
                if user_row["role"] != "staff":
                    raise ValueError("Puoi modificare solo i sotto-account del locale.")

                username_conflict = connection.execute(
                    "SELECT id FROM users WHERE lower(username) = ? AND id != ? LIMIT 1",
                    (_normalize_lookup(username), user_id),
                ).fetchone()
                email_conflict = connection.execute(
                    "SELECT id FROM users WHERE lower(email) = ? AND id != ? LIMIT 1",
                    (_normalize_lookup(email), user_id),
                ).fetchone()
                if username_conflict is not None or email_conflict is not None:
                    raise ValueError("Username o email gia registrati da un altro account.")

                assignments = [
                    "name = ?",
                    "username = ?",
                    "email = ?",
                    "phone_number = ?",
                    "permissions_json = ?",
                    "assistant_scopes_json = ?",
                ]
                values: list[object] = [name, username, email, phone_number, permissions_json, assistant_scopes_json]
                if next_password:
                    assignments.append("password_hash = ?")
                    values.append(_hash_password(next_password))

                values.append(user_id)
                connection.execute(
                    f"UPDATE users SET {', '.join(assignments)} WHERE id = ?",
                    values,
                )
                connection.commit()

        return next(
            user
            for user in self.list_tenant_users(session)
            if user["id"] == user_id
        )

    def delete_tenant_staff_user(self, session: SessionIdentity, user_id: str) -> None:
        if not self._can_manage_tenant_accounts(session):
            raise ValueError("Operazione riservata all'admin del locale.")

        if session.user_id == user_id:
            raise ValueError("Non puoi eliminare l'account con cui sei autenticato.")

        with self._lock:
            with self._connect_registry() as connection:
                user_row = connection.execute(
                    """
                    SELECT * FROM users
                    WHERE id = ? AND tenant_id = ?
                    LIMIT 1
                    """,
                    (user_id, session.tenant_id),
                ).fetchone()
                if user_row is None:
                    raise KeyError("Sotto-account non trovato.")
                if user_row["role"] != "staff":
                    raise ValueError("Puoi eliminare solo i sotto-account del locale.")

                connection.execute(
                    "DELETE FROM assistant_runs WHERE tenant_id = ? AND user_id = ?",
                    (session.tenant_id, user_id),
                )
                connection.execute(
                    "DELETE FROM assistant_threads WHERE tenant_id = ? AND user_id = ?",
                    (session.tenant_id, user_id),
                )
                connection.execute("DELETE FROM sessions WHERE user_id = ?", (user_id,))
                connection.execute("DELETE FROM users WHERE id = ?", (user_id,))
                connection.commit()
            with self._connect_tenant_database(session.database_path) as tenant_connection:
                tenant_connection.execute("DELETE FROM tenant_timeclock_entries WHERE user_id = ?", (user_id,))
                tenant_connection.commit()

    def _tips_area_label(self, area: str) -> str:
        normalized_area = _normalize_tips_area(area)
        return "Mance sala" if normalized_area == "sala" else "Mance Bar"

    def _serialize_tips_roster_row(self, row: sqlite3.Row) -> dict[str, object]:
        return {
            "id": str(row["id"]),
            "area": str(row["area"]),
            "name": str(row["staff_name"]),
            "score": float(row["score"] or 0),
            "sort_order": int(row["sort_order"] or 0),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _serialize_tips_run_summary_row(self, row: sqlite3.Row) -> dict[str, object]:
        tip_pos_effective_amount = (
            row["tip_pos_effective_amount"]
            if "tip_pos_effective_amount" in row.keys()
            else row["tip_pos_amount"]
        )
        raw_payout_status = row["payout_status"] if "payout_status" in row.keys() else "pending"
        payout_status = str(raw_payout_status or "pending").strip()
        if payout_status not in {"pending", "carried", "settled"}:
            payout_status = "pending"
        payout_status_labels = {
            "pending": "Da consegnare",
            "carried": "Caricata in altra giornata",
            "settled": "Consegnata",
        }
        payload = {
            "id": str(row["id"]),
            "area": str(row["area"]),
            "tip_date": str(row["tip_date"]),
            "total_tip_amount": float(row["total_tip_amount"] or 0),
            "tip_pos_amount": float(row["tip_pos_amount"] or 0),
            "tip_pos_effective_amount": float(tip_pos_effective_amount or 0),
            "tip_cash_amount": float(row["tip_cash_amount"] or 0),
            "total_score": float(row["total_score"] or 0),
            "historical_total_amount": float(row["historical_total_amount"] or 0),
            "payable_total_amount": float(row["payable_total_amount"] or 0),
            "present_staff_count": int(row["present_staff_count"] or 0),
            "absent_staff_count": int(row["absent_staff_count"] or 0),
            "payout_status": payout_status,
            "payout_status_label": payout_status_labels[payout_status],
            "settled_at": str(row["settled_at"] or "") if "settled_at" in row.keys() and row["settled_at"] else None,
            "settled_by_name": str(row["settled_by_name"] or "") if "settled_by_name" in row.keys() and row["settled_by_name"] else None,
            "saved_by_name": str(row["saved_by_name"] or "") or None,
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }
        if "entry_count" in row.keys():
            payload["entry_count"] = int(row["entry_count"] or 0)
        return payload

    def _serialize_tips_run_entry_row(self, row: sqlite3.Row) -> dict[str, object]:
        return {
            "id": str(row["id"]),
            "run_id": str(row["run_id"]),
            "area": str(row["area"]),
            "name": str(row["staff_name"]),
            "score": float(row["score"] or 0),
            "is_present": bool(row["is_present"]),
            "amount_today": float(row["amount_today"] or 0),
            "historical_amount": float(row["historical_amount"] or 0),
            "total_amount": float(row["total_amount"] or 0),
            "created_at": str(row["created_at"]),
            "updated_at": str(row["updated_at"]),
        }

    def _load_tips_run_entries(
        self,
        connection: sqlite3.Connection,
        run_ids: list[str],
    ) -> dict[str, list[dict[str, object]]]:
        grouped: dict[str, list[dict[str, object]]] = {run_id: [] for run_id in run_ids}
        if not run_ids:
            return grouped

        placeholders = ", ".join("?" for _ in run_ids)
        rows = connection.execute(
            f"""
            SELECT *
            FROM tenant_tips_run_entries
            WHERE run_id IN ({placeholders})
            ORDER BY score DESC, lower(staff_name) ASC, created_at ASC
            """,
            tuple(run_ids),
        ).fetchall()
        for row in rows:
            run_id = str(row["run_id"])
            grouped.setdefault(run_id, []).append(self._serialize_tips_run_entry_row(row))
        return grouped

    def _load_tips_run_history_sources(
        self,
        connection: sqlite3.Connection,
        run_ids: list[str],
    ) -> dict[str, list[dict[str, object]]]:
        grouped: dict[str, list[dict[str, object]]] = {run_id: [] for run_id in run_ids}
        if not run_ids:
            return grouped

        placeholders = ", ".join("?" for _ in run_ids)
        rows = connection.execute(
            f"""
            SELECT *
            FROM tenant_tips_run_history_sources
            WHERE run_id IN ({placeholders})
            ORDER BY source_tip_date DESC, created_at DESC
            """,
            tuple(run_ids),
        ).fetchall()
        for row in rows:
            run_id = str(row["run_id"])
            grouped.setdefault(run_id, []).append(
                {
                    "id": str(row["source_run_id"]),
                    "tip_date": str(row["source_tip_date"]),
                }
            )
        return grouped

    def _read_tips_run_row(
        self,
        connection: sqlite3.Connection,
        area: str,
        run_id: str,
    ) -> sqlite3.Row:
        normalized_area = _normalize_tips_area(area)
        row = connection.execute(
            """
            SELECT
                runs.*,
                (
                    SELECT COUNT(*)
                    FROM tenant_tips_run_entries AS entries
                    WHERE entries.run_id = runs.id
                ) AS entry_count
            FROM tenant_tips_runs AS runs
            WHERE runs.area = ? AND runs.id = ?
            LIMIT 1
            """,
            (normalized_area, run_id),
        ).fetchone()
        if row is None:
            raise KeyError("Salvataggio mance non trovato.")
        return row

    def _allocate_tips_amounts(
        self,
        present_entries: list[dict[str, object]],
        total_amount: float,
    ) -> dict[str, float]:
        allocations = {
            str(entry["lookup"]): 0.0
            for entry in present_entries
        }
        total_cents = max(int(round(float(total_amount or 0) * 100)), 0)
        positive_entries = [
            entry for entry in present_entries
            if float(entry.get("score") or 0) > 0
        ]
        total_score = sum(float(entry.get("score") or 0) for entry in positive_entries)
        if total_cents <= 0 or total_score <= 0 or not positive_entries:
            return allocations

        ranked_entries: list[dict[str, object]] = []
        assigned_cents = 0
        for entry in positive_entries:
            lookup = str(entry["lookup"])
            score = float(entry.get("score") or 0)
            exact_cents = (score / total_score) * total_cents
            base_cents = int(exact_cents)
            assigned_cents += base_cents
            ranked_entries.append(
                {
                    "lookup": lookup,
                    "name": str(entry.get("name") or ""),
                    "base_cents": base_cents,
                    "remainder": exact_cents - base_cents,
                }
            )

        missing_cents = total_cents - assigned_cents
        ranked_entries.sort(
            key=lambda item: (
                -float(item["remainder"]),
                str(item["name"]).casefold(),
                str(item["lookup"]),
            )
        )
        for index in range(max(missing_cents, 0)):
            ranked_entries[index % len(ranked_entries)]["base_cents"] = int(ranked_entries[index % len(ranked_entries)]["base_cents"]) + 1

        for entry in ranked_entries:
            allocations[str(entry["lookup"])] = round(int(entry["base_cents"]) / 100, 2)

        return allocations

    def _build_tips_preview(
        self,
        connection: sqlite3.Connection,
        area: str,
        payload: TipsRunPreviewPayload,
        *,
        exclude_run_id: str | None = None,
    ) -> dict[str, object]:
        normalized_area = _normalize_tips_area(area)
        area_label = self._tips_area_label(normalized_area)
        raw_tip_date = str(payload.tip_date or "").strip() or _utcnow().date().isoformat()
        try:
            tip_date = date.fromisoformat(raw_tip_date).isoformat()
        except ValueError as exc:
            raise ValueError("Inserisci una data valida per le mance.") from exc

        roster_rows = connection.execute(
            """
            SELECT *
            FROM tenant_tips_roster_entries
            WHERE area = ?
            ORDER BY sort_order ASC, lower(staff_name) ASC, created_at ASC
            """,
            (normalized_area,),
        ).fetchall()
        if not roster_rows:
            raise ValueError(f"Configura prima la lista dipendenti di {area_label}.")

        roster_entries = [self._serialize_tips_roster_row(row) for row in roster_rows]
        roster_by_lookup = {
            _normalize_lookup(str(entry["name"])): entry
            for entry in roster_entries
        }

        absent_lookups = {
            _normalize_lookup(name)
            for name in payload.absent_names
            if str(name or "").strip()
        }
        present_entries = [
            {
                "name": str(entry["name"]),
                "lookup": _normalize_lookup(str(entry["name"])),
                "score": float(entry["score"] or 0),
            }
            for entry in roster_entries
            if _normalize_lookup(str(entry["name"])) not in absent_lookups
        ]
        if not present_entries:
            raise ValueError("Seleziona almeno un dipendente presente.")

        total_score = round(sum(float(entry["score"] or 0) for entry in present_entries), 4)
        if total_score <= 0:
            raise ValueError("I dipendenti presenti non hanno un punteggio valido.")

        total_tip_amount = round(float(payload.total_amount or 0), 2)
        tip_pos_amount = round(float(payload.pos_amount or 0), 2)
        if tip_pos_amount > total_tip_amount:
            raise ValueError("Le mance POS non possono superare il totale della giornata.")
        tip_pos_effective_amount = round(
            float(payload.pos_effective_amount if payload.pos_effective_amount is not None else tip_pos_amount),
            2,
        )
        if tip_pos_effective_amount > tip_pos_amount:
            raise ValueError("Le mance POS effettive non possono superare le mance POS inserite.")
        tip_cash_amount = round(total_tip_amount - tip_pos_amount, 2)
        effective_tip_amount = round(tip_cash_amount + tip_pos_effective_amount, 2)

        daily_allocations = self._allocate_tips_amounts(present_entries, effective_tip_amount)

        requested_history_ids: list[str] = []
        seen_history_ids: set[str] = set()
        for raw_run_id in payload.history_run_ids:
            run_id = str(raw_run_id or "").strip()
            if not run_id or run_id in seen_history_ids:
                continue
            requested_history_ids.append(run_id)
            seen_history_ids.add(run_id)

        history_runs: list[dict[str, object]] = []
        history_amounts_by_lookup: dict[str, dict[str, object]] = {}
        if requested_history_ids:
            placeholders = ", ".join("?" for _ in requested_history_ids)
            history_rows = connection.execute(
                f"""
                SELECT
                    runs.*,
                    (
                        SELECT COUNT(*)
                        FROM tenant_tips_run_entries AS entries
                        WHERE entries.run_id = runs.id
                    ) AS entry_count
                FROM tenant_tips_runs AS runs
                WHERE runs.area = ?
                  AND runs.id IN ({placeholders})
                  AND COALESCE(runs.payout_status, 'pending') = 'pending'
                ORDER BY runs.tip_date DESC, runs.updated_at DESC
                """,
                (normalized_area, *requested_history_ids),
            ).fetchall()
            filtered_history_rows = [
                row
                for row in history_rows
                if str(row["id"]) != exclude_run_id and str(row["tip_date"]) != tip_date
            ]
            selected_run_ids = [str(row["id"]) for row in filtered_history_rows]
            history_entries_by_run = self._load_tips_run_entries(connection, selected_run_ids)
            history_runs = [self._serialize_tips_run_summary_row(row) for row in filtered_history_rows]
            for run_row in filtered_history_rows:
                for entry in history_entries_by_run.get(str(run_row["id"]), []):
                    lookup = _normalize_lookup(str(entry["name"]))
                    bucket = history_amounts_by_lookup.setdefault(
                        lookup,
                        {
                            "name": str(entry["name"]),
                            "amount": 0.0,
                        },
                    )
                    bucket["amount"] = round(float(bucket["amount"] or 0) + float(entry["total_amount"] or entry["amount_today"] or 0), 2)

        rows_by_lookup: dict[str, dict[str, object]] = {}
        for entry in present_entries:
            lookup = str(entry["lookup"])
            rows_by_lookup[lookup] = {
                "name": str(entry["name"]),
                "score": float(entry["score"] or 0),
                "is_present": True,
                "amount_today": round(float(daily_allocations.get(lookup, 0)), 2),
                "historical_amount": 0.0,
            }

        for lookup, history_entry in history_amounts_by_lookup.items():
            row = rows_by_lookup.get(lookup)
            if row is None:
                roster_entry = roster_by_lookup.get(lookup)
                row = {
                    "name": str(roster_entry["name"]) if roster_entry else str(history_entry["name"]),
                    "score": float(roster_entry["score"] or 0) if roster_entry else 0.0,
                    "is_present": False,
                    "amount_today": 0.0,
                    "historical_amount": 0.0,
                }
                rows_by_lookup[lookup] = row
            row["historical_amount"] = round(float(row["historical_amount"] or 0) + float(history_entry["amount"] or 0), 2)

        rows = [
            {
                "name": str(row["name"]),
                "score": float(row["score"] or 0),
                "is_present": bool(row["is_present"]),
                "amount_today": round(float(row["amount_today"] or 0), 2),
                "historical_amount": round(float(row["historical_amount"] or 0), 2),
                "total_amount": round(float(row["amount_today"] or 0) + float(row["historical_amount"] or 0), 2),
            }
            for row in rows_by_lookup.values()
        ]
        rows.sort(key=lambda item: (-float(item["score"] or 0), str(item["name"]).casefold()))

        historical_total_amount = round(sum(float(item["historical_amount"] or 0) for item in rows), 2)
        payable_total_amount = round(effective_tip_amount + historical_total_amount, 2)
        if payable_total_amount <= 0:
            raise ValueError("Inserisci un importo mance oppure seleziona almeno una giornata arretrata da consegnare.")

        return {
            "area": normalized_area,
            "area_label": area_label,
            "tip_date": tip_date,
            "total_tip_amount": total_tip_amount,
            "tip_pos_amount": tip_pos_amount,
            "tip_pos_effective_amount": tip_pos_effective_amount,
            "tip_cash_amount": tip_cash_amount,
            "total_score": total_score,
            "historical_total_amount": historical_total_amount,
            "payable_total_amount": payable_total_amount,
            "present_staff_count": len(present_entries),
            "absent_staff_count": len(roster_entries) - len(present_entries),
            "roster_count": len(roster_entries),
            "rows": rows,
            "selected_history_runs": history_runs,
        }

    def get_tips_module(self, session: SessionIdentity, area: str) -> dict[str, object]:
        normalized_area = _normalize_tips_area(area)
        if not self._can_access_tips(session, normalized_area):
            raise ValueError(f"Questo account non puo accedere a {self._tips_area_label(normalized_area)}.")

        with self._connect_tenant_database(session.database_path) as connection:
            roster_rows = connection.execute(
                """
                SELECT *
                FROM tenant_tips_roster_entries
                WHERE area = ?
                ORDER BY sort_order ASC, lower(staff_name) ASC, created_at ASC
                """,
                (normalized_area,),
            ).fetchall()
            run_rows = connection.execute(
                """
                SELECT
                    runs.*,
                    (
                        SELECT COUNT(*)
                        FROM tenant_tips_run_entries AS entries
                        WHERE entries.run_id = runs.id
                    ) AS entry_count
                FROM tenant_tips_runs AS runs
                WHERE runs.area = ?
                ORDER BY runs.tip_date DESC, runs.updated_at DESC
                LIMIT 120
                """,
                (normalized_area,),
            ).fetchall()

        roster = [self._serialize_tips_roster_row(row) for row in roster_rows]
        recent_runs = [self._serialize_tips_run_summary_row(row) for row in run_rows]
        return {
            "area": normalized_area,
            "area_label": self._tips_area_label(normalized_area),
            "can_manage": self._can_manage_tips(session, normalized_area),
            "roster": roster,
            "roster_count": len(roster),
            "recent_runs": recent_runs,
            "run_count": len(recent_runs),
            "current_tip_date": _utcnow().date().isoformat(),
        }

    def replace_tips_roster(
        self,
        session: SessionIdentity,
        area: str,
        payload: TipsRosterWritePayload,
    ) -> dict[str, object]:
        normalized_area = _normalize_tips_area(area)
        if not self._can_manage_tips(session, normalized_area):
            raise ValueError(f"Questo account non puo modificare {self._tips_area_label(normalized_area)}.")

        normalized_entries: list[tuple[str, str, float, int]] = []
        seen_lookups: set[str] = set()
        for index, raw_entry in enumerate(payload.entries):
            name = str(raw_entry.name or "").strip()
            if len(name) < 2:
                raise ValueError("Ogni dipendente deve avere un nome valido.")
            lookup = _normalize_lookup(name)
            if lookup in seen_lookups:
                raise ValueError(f"{name} compare piu di una volta nella lista.")
            seen_lookups.add(lookup)
            normalized_entries.append((name, lookup, round(float(raw_entry.score or 0), 4), index))

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                connection.execute(
                    "DELETE FROM tenant_tips_roster_entries WHERE area = ?",
                    (normalized_area,),
                )
                for name, lookup, score, sort_order in normalized_entries:
                    connection.execute(
                        """
                        INSERT INTO tenant_tips_roster_entries (
                            id,
                            area,
                            staff_name,
                            staff_lookup,
                            score,
                            sort_order,
                            created_at,
                            updated_at
                        )
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"tips_roster_{uuid.uuid4().hex}",
                            normalized_area,
                            name,
                            lookup,
                            score,
                            sort_order,
                            timestamp,
                            timestamp,
                        ),
                    )
                connection.commit()

        return self.get_tips_module(session, normalized_area)

    def preview_tips_distribution(
        self,
        session: SessionIdentity,
        area: str,
        payload: TipsRunPreviewPayload,
    ) -> dict[str, object]:
        normalized_area = _normalize_tips_area(area)
        if not self._can_access_tips(session, normalized_area):
            raise ValueError(f"Questo account non puo accedere a {self._tips_area_label(normalized_area)}.")

        with self._connect_tenant_database(session.database_path) as connection:
            return self._build_tips_preview(connection, normalized_area, payload)

    def save_tips_distribution(
        self,
        session: SessionIdentity,
        area: str,
        payload: TipsRunPreviewPayload,
    ) -> dict[str, object]:
        normalized_area = _normalize_tips_area(area)
        if not self._can_access_tips(session, normalized_area):
            raise ValueError(f"Questo account non puo accedere a {self._tips_area_label(normalized_area)}.")

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                raw_tip_date = str(payload.tip_date or "").strip() or _utcnow().date().isoformat()
                try:
                    normalized_tip_date = date.fromisoformat(raw_tip_date).isoformat()
                except ValueError as exc:
                    raise ValueError("Inserisci una data valida per le mance.") from exc
                existing_run_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_tips_runs
                    WHERE area = ? AND tip_date = ?
                    LIMIT 1
                    """,
                    (normalized_area, normalized_tip_date),
                ).fetchone()
                history_only_request = float(payload.total_amount or 0) <= 0 and any(
                    str(run_id or "").strip()
                    for run_id in payload.history_run_ids
                )
                if (
                    existing_run_row is not None
                    and history_only_request
                    and float(existing_run_row["total_tip_amount"] or 0) > 0
                ):
                    raise ValueError(
                        "Per consegnare solo arretrati scegli una data senza mance giornaliere gia salvate, "
                        "cosi non sovrascrivo una divisione esistente."
                    )

                previous_source_ids: set[str] = set()
                if existing_run_row is not None:
                    previous_source_ids = {
                        str(row["source_run_id"])
                        for row in connection.execute(
                            """
                            SELECT source_run_id
                            FROM tenant_tips_run_history_sources
                            WHERE run_id = ?
                            """,
                            (str(existing_run_row["id"]),),
                        ).fetchall()
                    }

                preview = self._build_tips_preview(
                    connection,
                    normalized_area,
                    payload,
                    exclude_run_id=str(existing_run_row["id"]) if existing_run_row is not None else None,
                )

                run_id = str(existing_run_row["id"]) if existing_run_row is not None else f"tips_run_{uuid.uuid4().hex}"
                payout_status = "settled" if payload.mark_as_delivered else "pending"
                settled_at = timestamp if payload.mark_as_delivered else None
                settled_by_user_id = session.user_id if payload.mark_as_delivered else None
                settled_by_name = (session.user_name or session.username or session.user_email) if payload.mark_as_delivered else None
                if existing_run_row is None:
                    connection.execute(
                        """
                        INSERT INTO tenant_tips_runs (
                            id,
                            area,
                            tip_date,
                            total_tip_amount,
                            tip_pos_amount,
                            tip_pos_effective_amount,
                            tip_cash_amount,
                            total_score,
                            historical_total_amount,
                            payable_total_amount,
                            present_staff_count,
                            absent_staff_count,
                            payout_status,
                            settled_at,
                            settled_by_user_id,
                            settled_by_name,
                            saved_by_user_id,
                            saved_by_name,
                            created_at,
                            updated_at
                        )
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            run_id,
                            normalized_area,
                            preview["tip_date"],
                            preview["total_tip_amount"],
                            preview["tip_pos_amount"],
                            preview["tip_pos_effective_amount"],
                            preview["tip_cash_amount"],
                            preview["total_score"],
                            preview["historical_total_amount"],
                            preview["payable_total_amount"],
                            preview["present_staff_count"],
                            preview["absent_staff_count"],
                            payout_status,
                            settled_at,
                            settled_by_user_id,
                            settled_by_name,
                            session.user_id,
                            session.user_name or session.username or session.user_email,
                            timestamp,
                            timestamp,
                        ),
                    )
                else:
                    connection.execute(
                        """
                        UPDATE tenant_tips_runs
                        SET total_tip_amount = ?,
                            tip_pos_amount = ?,
                            tip_pos_effective_amount = ?,
                            tip_cash_amount = ?,
                            total_score = ?,
                            historical_total_amount = ?,
                            payable_total_amount = ?,
                            present_staff_count = ?,
                            absent_staff_count = ?,
                            payout_status = ?,
                            settled_at = ?,
                            settled_by_user_id = ?,
                            settled_by_name = ?,
                            saved_by_user_id = ?,
                            saved_by_name = ?,
                            updated_at = ?
                        WHERE id = ?
                        """,
                        (
                            preview["total_tip_amount"],
                            preview["tip_pos_amount"],
                            preview["tip_pos_effective_amount"],
                            preview["tip_cash_amount"],
                            preview["total_score"],
                            preview["historical_total_amount"],
                            preview["payable_total_amount"],
                            preview["present_staff_count"],
                            preview["absent_staff_count"],
                            payout_status,
                            settled_at,
                            settled_by_user_id,
                            settled_by_name,
                            session.user_id,
                            session.user_name or session.username or session.user_email,
                            timestamp,
                            run_id,
                        ),
                    )
                    connection.execute("DELETE FROM tenant_tips_run_entries WHERE run_id = ?", (run_id,))
                    connection.execute("DELETE FROM tenant_tips_run_history_sources WHERE run_id = ?", (run_id,))

                for row in preview["rows"]:
                    row_payload = row if isinstance(row, dict) else {}
                    connection.execute(
                        """
                        INSERT INTO tenant_tips_run_entries (
                            id,
                            run_id,
                            area,
                            staff_name,
                            staff_lookup,
                            score,
                            is_present,
                            amount_today,
                            historical_amount,
                            total_amount,
                            created_at,
                            updated_at
                        )
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"tips_entry_{uuid.uuid4().hex}",
                            run_id,
                            normalized_area,
                            str(row_payload.get("name") or "").strip(),
                            _normalize_lookup(str(row_payload.get("name") or "")),
                            round(float(row_payload.get("score") or 0), 4),
                            1 if bool(row_payload.get("is_present")) else 0,
                            round(float(row_payload.get("amount_today") or 0), 2),
                            round(float(row_payload.get("historical_amount") or 0), 2),
                            round(float(row_payload.get("total_amount") or 0), 2),
                            timestamp,
                            timestamp,
                        ),
                    )

                selected_source_ids: set[str] = set()
                for source in preview["selected_history_runs"]:
                    source_payload = source if isinstance(source, dict) else {}
                    source_run_id = str(source_payload.get("id") or "").strip()
                    source_tip_date = str(source_payload.get("tip_date") or "").strip()
                    if not source_run_id or not source_tip_date:
                        continue
                    selected_source_ids.add(source_run_id)
                    connection.execute(
                        """
                        INSERT INTO tenant_tips_run_history_sources (
                            id,
                            run_id,
                            source_run_id,
                            source_tip_date,
                            created_at,
                            updated_at
                        )
                        VALUES (?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"tips_history_{uuid.uuid4().hex}",
                            run_id,
                            source_run_id,
                            source_tip_date,
                            timestamp,
                            timestamp,
                        ),
                    )

                if previous_source_ids:
                    released_source_ids = sorted(previous_source_ids - selected_source_ids)
                    if released_source_ids:
                        placeholders = ", ".join("?" for _ in released_source_ids)
                        connection.execute(
                            f"""
                            UPDATE tenant_tips_runs
                            SET payout_status = 'pending',
                                settled_at = NULL,
                                settled_by_user_id = NULL,
                                settled_by_name = NULL,
                                updated_at = ?
                            WHERE area = ?
                              AND payout_status = 'carried'
                              AND id IN ({placeholders})
                            """,
                            (timestamp, normalized_area, *released_source_ids),
                        )

                if selected_source_ids:
                    placeholders = ", ".join("?" for _ in selected_source_ids)
                    if payload.mark_as_delivered:
                        connection.execute(
                            f"""
                            UPDATE tenant_tips_runs
                            SET payout_status = 'settled',
                                settled_at = COALESCE(settled_at, ?),
                                settled_by_user_id = COALESCE(settled_by_user_id, ?),
                                settled_by_name = COALESCE(settled_by_name, ?),
                                updated_at = ?
                            WHERE area = ?
                              AND id IN ({placeholders})
                            """,
                            (
                                timestamp,
                                session.user_id,
                                session.user_name or session.username or session.user_email,
                                timestamp,
                                normalized_area,
                                *sorted(selected_source_ids),
                            ),
                        )
                    else:
                        connection.execute(
                            f"""
                            UPDATE tenant_tips_runs
                            SET payout_status = 'carried',
                                settled_at = NULL,
                                settled_by_user_id = NULL,
                                settled_by_name = NULL,
                                updated_at = ?
                            WHERE area = ?
                              AND payout_status = 'pending'
                              AND id IN ({placeholders})
                            """,
                            (timestamp, normalized_area, *sorted(selected_source_ids)),
                        )

                connection.commit()

            return self.get_tips_run_detail(session, normalized_area, run_id)

    def set_tips_run_payout_status(
        self,
        session: SessionIdentity,
        area: str,
        run_id: str,
        *,
        delivered: bool,
    ) -> dict[str, object]:
        normalized_area = _normalize_tips_area(area)
        if not self._can_manage_tips(session, normalized_area):
            raise ValueError(f"Questo account non puo modificare {self._tips_area_label(normalized_area)}.")

        timestamp = _iso_now()
        payout_status = "settled" if delivered else "pending"
        settled_at = timestamp if delivered else None
        settled_by_user_id = session.user_id if delivered else None
        settled_by_name = (session.user_name or session.username or session.user_email) if delivered else None

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._read_tips_run_row(connection, normalized_area, run_id)
                connection.execute(
                    """
                    UPDATE tenant_tips_runs
                    SET payout_status = ?,
                        settled_at = ?,
                        settled_by_user_id = ?,
                        settled_by_name = ?,
                        updated_at = ?
                    WHERE area = ? AND id = ?
                    """,
                    (
                        payout_status,
                        settled_at,
                        settled_by_user_id,
                        settled_by_name,
                        timestamp,
                        normalized_area,
                        run_id,
                    ),
                )
                source_ids = [
                    str(row["source_run_id"])
                    for row in connection.execute(
                        """
                        SELECT source_run_id
                        FROM tenant_tips_run_history_sources
                        WHERE run_id = ?
                        """,
                        (run_id,),
                    ).fetchall()
                ]
                if source_ids:
                    placeholders = ", ".join("?" for _ in source_ids)
                    if delivered:
                        connection.execute(
                            f"""
                            UPDATE tenant_tips_runs
                            SET payout_status = 'settled',
                                settled_at = COALESCE(settled_at, ?),
                                settled_by_user_id = COALESCE(settled_by_user_id, ?),
                                settled_by_name = COALESCE(settled_by_name, ?),
                                updated_at = ?
                            WHERE area = ?
                              AND id IN ({placeholders})
                            """,
                            (
                                timestamp,
                                session.user_id,
                                session.user_name or session.username or session.user_email,
                                timestamp,
                                normalized_area,
                                *source_ids,
                            ),
                        )
                    else:
                        connection.execute(
                            f"""
                            UPDATE tenant_tips_runs
                            SET payout_status = 'carried',
                                settled_at = NULL,
                                settled_by_user_id = NULL,
                                settled_by_name = NULL,
                                updated_at = ?
                            WHERE area = ?
                              AND id IN ({placeholders})
                            """,
                            (
                                timestamp,
                                normalized_area,
                                *source_ids,
                            ),
                        )
                connection.commit()

        return self.get_tips_run_detail(session, normalized_area, run_id)

    def get_tips_run_detail(
        self,
        session: SessionIdentity,
        area: str,
        run_id: str,
    ) -> dict[str, object]:
        normalized_area = _normalize_tips_area(area)
        if not self._can_access_tips(session, normalized_area):
            raise ValueError(f"Questo account non puo accedere a {self._tips_area_label(normalized_area)}.")

        with self._connect_tenant_database(session.database_path) as connection:
            run_row = self._read_tips_run_row(connection, normalized_area, run_id)
            entries = self._load_tips_run_entries(connection, [run_id]).get(run_id, [])
            history_sources = self._load_tips_run_history_sources(connection, [run_id]).get(run_id, [])

        return {
            "area": normalized_area,
            "area_label": self._tips_area_label(normalized_area),
            "can_manage": self._can_manage_tips(session, normalized_area),
            "run": self._serialize_tips_run_summary_row(run_row),
            "entries": entries,
            "history_sources": history_sources,
        }

    def list_reports(
        self,
        session: SessionIdentity,
        *,
        status_filter: str | None = None,
        limit: int = 200,
    ) -> dict[str, object]:
        if not self._can_access_reports(session):
            raise ValueError("Questo account non puo accedere a Segnalazioni.")

        can_manage = self._can_manage_reports(session)
        safe_limit = max(1, min(int(limit), 500))
        normalized_status = (status_filter or "").strip().lower()
        allowed_statuses = {"new", "reviewed", "in_progress", "reported_to_owner", "resolved"}
        filters: list[str] = []
        values: list[object] = []
        if normalized_status and normalized_status != "all":
            if normalized_status not in allowed_statuses:
                raise ValueError("Stato segnalazione non valido.")
            filters.append("status = ?")
            values.append(normalized_status)
        if not can_manage:
            filters.append("reporter_user_id = ?")
            values.append(session.user_id)
        where_sql = f"WHERE {' AND '.join(filters)}" if filters else ""

        with self._connect_tenant_database(session.database_path) as connection:
            total_count_row = connection.execute(
                f"""
                SELECT COUNT(*) AS total
                FROM tenant_reports
                {where_sql}
                """,
                tuple(values),
            ).fetchone()
            rows = connection.execute(
                f"""
                SELECT *
                FROM tenant_reports
                {where_sql}
                ORDER BY created_at DESC
                LIMIT ?
                """,
                tuple(values + [safe_limit]),
            ).fetchall()

        reports = [
            self._serialize_report_record(self._report_from_row(row), include_admin_note=can_manage)
            for row in rows
        ]
        return {
            "reports": reports,
            "total_count": int(total_count_row["total"] or 0) if total_count_row is not None else len(reports),
            "can_manage": can_manage,
        }

    def list_reports_for_google_sheet(self, session: SessionIdentity, *, year: int) -> list[dict[str, object]]:
        safe_year = max(2000, min(int(year), 2100))
        with self._connect_tenant_database(session.database_path) as connection:
            rows = connection.execute(
                """
                SELECT *
                FROM tenant_reports
                WHERE substr(created_at, 1, 4) = ?
                ORDER BY created_at ASC, id ASC
                """,
                (f"{safe_year:04d}",),
            ).fetchall()

        return [
            self._serialize_report_record(self._report_from_row(row), include_admin_note=True)
            for row in rows
        ]

    def create_report(self, session: SessionIdentity, payload: ReportCreatePayload) -> dict[str, object]:
        if not self._can_access_reports(session):
            raise ValueError("Questo account non puo accedere a Segnalazioni.")

        title = payload.title.strip()
        description = payload.description.strip()
        location = payload.location.strip() if payload.location else None
        if not title or not description:
            raise ValueError("Titolo e descrizione della segnalazione sono obbligatori.")

        report_id = f"report_{uuid.uuid4().hex}"
        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                connection.execute(
                    """
                    INSERT INTO tenant_reports (
                        id,
                        reporter_user_id,
                        reporter_name,
                        reporter_username,
                        reporter_email,
                        title,
                        description,
                        category,
                        location,
                        priority,
                        status,
                        admin_note,
                        status_updated_by_user_id,
                        status_updated_by_name,
                        resolved_at,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'new', NULL, NULL, NULL, NULL, ?, ?)
                    """,
                    (
                        report_id,
                        session.user_id,
                        session.user_name,
                        session.username,
                        session.user_email,
                        title,
                        description,
                        payload.category,
                        location,
                        payload.priority,
                        timestamp,
                        timestamp,
                    ),
                )
                connection.commit()
                row = self._read_report_row(connection, report_id)

        return {
            "report": self._serialize_report_record(
                self._report_from_row(row),
                include_admin_note=self._can_manage_reports(session),
            )
        }

    def update_report_status(
        self,
        session: SessionIdentity,
        report_id: str,
        payload: ReportStatusUpdatePayload,
    ) -> dict[str, object]:
        if not self._can_manage_reports(session):
            raise ValueError("Solo l'admin del locale puo aggiornare le segnalazioni.")

        normalized_report_id = report_id.strip()
        if not normalized_report_id:
            raise KeyError("Segnalazione non trovata.")

        timestamp = _iso_now()
        admin_note = payload.admin_note.strip() if payload.admin_note else None
        resolved_at = timestamp if payload.status == "resolved" else None
        status_updated_by_name = (session.user_name or session.username or session.user_email or "Admin locale").strip()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                self._read_report_row(connection, normalized_report_id)
                connection.execute(
                    """
                    UPDATE tenant_reports
                    SET
                        status = ?,
                        admin_note = ?,
                        status_updated_by_user_id = ?,
                        status_updated_by_name = ?,
                        resolved_at = ?,
                        updated_at = ?
                    WHERE id = ?
                    """,
                    (
                        payload.status,
                        admin_note,
                        session.user_id,
                        status_updated_by_name,
                        resolved_at,
                        timestamp,
                        normalized_report_id,
                    ),
                )
                connection.commit()
                row = self._read_report_row(connection, normalized_report_id)

        return {
            "report": self._serialize_report_record(
                self._report_from_row(row),
                include_admin_note=True,
            )
        }

    def upsert_push_subscription(self, session: SessionIdentity, payload: PushSubscriptionPayload) -> dict[str, object]:
        if not self._can_manage_reports(session):
            raise ValueError("Solo l'admin del locale puo attivare le notifiche push.")

        endpoint = payload.endpoint.strip()
        p256dh = payload.subscription_keys.p256dh.strip()
        auth = payload.subscription_keys.auth.strip()
        user_agent = payload.user_agent.strip() if payload.user_agent else None
        if not endpoint or not p256dh or not auth:
            raise ValueError("Sottoscrizione push incompleta.")

        timestamp = _iso_now()
        subscription_id = f"push_{uuid.uuid4().hex}"
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                connection.execute(
                    """
                    INSERT INTO tenant_push_subscriptions (
                        id,
                        user_id,
                        endpoint,
                        p256dh,
                        auth,
                        user_agent,
                        enabled,
                        last_error,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, 1, NULL, ?, ?)
                    ON CONFLICT(endpoint) DO UPDATE SET
                        user_id = excluded.user_id,
                        p256dh = excluded.p256dh,
                        auth = excluded.auth,
                        user_agent = excluded.user_agent,
                        enabled = 1,
                        last_error = NULL,
                        updated_at = excluded.updated_at
                    """,
                    (
                        subscription_id,
                        session.user_id,
                        endpoint,
                        p256dh,
                        auth,
                        user_agent,
                        timestamp,
                        timestamp,
                    ),
                )
                connection.commit()
                row = connection.execute(
                    "SELECT * FROM tenant_push_subscriptions WHERE endpoint = ? LIMIT 1",
                    (endpoint,),
                ).fetchone()

        if row is None:
            raise ValueError("Sottoscrizione push non salvata.")
        return {"subscription": self._serialize_push_subscription_row(row)}

    def delete_push_subscription(self, session: SessionIdentity, endpoint: str) -> dict[str, object]:
        normalized_endpoint = endpoint.strip()
        if not normalized_endpoint:
            raise ValueError("Endpoint push mancante.")

        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                cursor = connection.execute(
                    """
                    DELETE FROM tenant_push_subscriptions
                    WHERE endpoint = ? AND user_id = ?
                    """,
                    (normalized_endpoint, session.user_id),
                )
                connection.commit()
        return {"deleted": cursor.rowcount > 0}

    def list_admin_push_subscriptions(self, session: SessionIdentity) -> list[dict[str, object]]:
        admin_user_ids: set[str] = set()
        if self._can_manage_reports(session):
            admin_user_ids.add(session.user_id)

        with self._connect_registry() as connection:
            rows = connection.execute(
                """
                SELECT id
                FROM users
                WHERE tenant_id = ?
                  AND role IN ('owner', 'super_admin')
                """,
                (session.tenant_id,),
            ).fetchall()
        admin_user_ids.update(str(row["id"]) for row in rows if row["id"])
        with self._connect_registry() as connection:
            rows = connection.execute(
                """
                SELECT user_id
                FROM tenant_admin_push_recipients
                WHERE tenant_id = ?
                  AND enabled = 1
                """,
                (session.tenant_id,),
            ).fetchall()
        admin_user_ids.update(str(row["user_id"]) for row in rows if row["user_id"])
        if not admin_user_ids:
            return []

        placeholders = ", ".join("?" for _ in admin_user_ids)
        with self._connect_tenant_database(session.database_path) as connection:
            rows = connection.execute(
                f"""
                SELECT *
                FROM tenant_push_subscriptions
                WHERE enabled = 1
                  AND user_id IN ({placeholders})
                ORDER BY updated_at DESC
                """,
                tuple(admin_user_ids),
            ).fetchall()
        return [self._serialize_push_subscription_row(row) for row in rows]

    def mark_push_subscription_error(
        self,
        session: SessionIdentity,
        endpoint: str,
        error_detail: str,
        *,
        disable: bool = False,
    ) -> None:
        normalized_endpoint = endpoint.strip()
        if not normalized_endpoint:
            return

        timestamp = _iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                connection.execute(
                    """
                    UPDATE tenant_push_subscriptions
                    SET
                        enabled = CASE WHEN ? THEN 0 ELSE enabled END,
                        last_error = ?,
                        updated_at = ?
                    WHERE endpoint = ?
                    """,
                    (1 if disable else 0, error_detail[:1000], timestamp, normalized_endpoint),
                )
                connection.commit()

    def get_timeclock_status(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_timeclock(session):
            raise ValueError("Questo account non puo accedere a Turni.")

        now = _local_now()
        today = now.date().isoformat()
        week_start = (now.date() - timedelta(days=now.weekday())).isoformat()

        recent_entries = self._read_timeclock_entries(session, user_id=session.user_id, limit=20)
        active_entry = next((entry for entry in recent_entries if entry.ended_at is None), None)
        today_entries = self._read_timeclock_entries(session, user_id=session.user_id, start_date=today, end_date=today, limit=500)
        week_entries = self._read_timeclock_entries(
            session,
            user_id=session.user_id,
            start_date=week_start,
            end_date=today,
            limit=1000,
        )

        today_seconds = sum(int(self._serialize_timeclock_entry(entry, reference_now=now)["duration_seconds"] or 0) for entry in today_entries)
        week_seconds = sum(int(self._serialize_timeclock_entry(entry, reference_now=now)["duration_seconds"] or 0) for entry in week_entries)

        return {
            "can_manage_team": session.role in {"owner", "super_admin"},
            "active_entry": self._serialize_timeclock_entry(active_entry, reference_now=now) if active_entry is not None else None,
            "today_seconds": today_seconds,
            "today_hours": round(today_seconds / 3600, 2),
            "week_seconds": week_seconds,
            "week_hours": round(week_seconds / 3600, 2),
            "recent_entries": [self._serialize_timeclock_entry(entry, reference_now=now) for entry in recent_entries],
        }

    def start_timeclock_shift(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_timeclock(session):
            raise ValueError("Questo account non puo accedere a Turni.")

        started_at = _local_iso_now()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                active_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_timeclock_entries
                    WHERE user_id = ? AND ended_at IS NULL
                    ORDER BY started_at DESC
                    LIMIT 1
                    """,
                    (session.user_id,),
                ).fetchone()
                if active_row is not None:
                    raise ValueError("Esiste gia un turno attivo per questo account.")

                entry_id = f"timeclock_{uuid.uuid4().hex}"
                connection.execute(
                    """
                    INSERT INTO tenant_timeclock_entries (
                        id,
                        user_id,
                        user_name,
                        username,
                        user_email,
                        started_at,
                        ended_at,
                        duration_seconds,
                        started_source,
                        ended_source,
                        notes,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, NULL, NULL, 'portal', NULL, NULL, ?, ?)
                    """,
                    (
                        entry_id,
                        session.user_id,
                        session.user_name,
                        session.username,
                        session.user_email,
                        started_at,
                        started_at,
                        started_at,
                    ),
                )
                connection.commit()
                row = connection.execute(
                    "SELECT * FROM tenant_timeclock_entries WHERE id = ? LIMIT 1",
                    (entry_id,),
                ).fetchone()

        if row is None:
            raise ValueError("Turno avviato ma non piu disponibile.")
        return {"entry": self._serialize_timeclock_entry(self._timeclock_entry_from_row(row))}

    def stop_timeclock_shift(self, session: SessionIdentity) -> dict[str, object]:
        if not self._can_access_timeclock(session):
            raise ValueError("Questo account non puo accedere a Turni.")

        stopped_at = _local_now()
        stopped_at_iso = stopped_at.isoformat()
        with self._lock:
            with self._connect_tenant_database(session.database_path) as connection:
                active_row = connection.execute(
                    """
                    SELECT *
                    FROM tenant_timeclock_entries
                    WHERE user_id = ? AND ended_at IS NULL
                    ORDER BY started_at DESC
                    LIMIT 1
                    """,
                    (session.user_id,),
                ).fetchone()
                if active_row is None:
                    raise ValueError("Non vedo un turno attivo da chiudere per questo account.")

                active_entry = self._timeclock_entry_from_row(active_row)
                started_at = self._parse_iso_datetime(active_entry.started_at)
                duration_seconds = 0
                if started_at is not None:
                    duration_seconds = max(int((stopped_at - started_at).total_seconds()), 0)

                connection.execute(
                    """
                    UPDATE tenant_timeclock_entries
                    SET ended_at = ?, duration_seconds = ?, ended_source = 'portal', updated_at = ?
                    WHERE id = ?
                    """,
                    (stopped_at_iso, duration_seconds, stopped_at_iso, active_entry.id),
                )
                connection.commit()
                row = connection.execute(
                    "SELECT * FROM tenant_timeclock_entries WHERE id = ? LIMIT 1",
                    (active_entry.id,),
                ).fetchone()

        if row is None:
            raise ValueError("Turno chiuso ma non piu disponibile.")
        return {"entry": self._serialize_timeclock_entry(self._timeclock_entry_from_row(row))}

    def get_timeclock_overview(self, session: SessionIdentity, query: TimeclockOverviewQuery) -> dict[str, object]:
        if not self._can_access_timeclock(session):
            raise ValueError("Questo account non puo accedere a Turni.")

        effective_user_id = query.user_id.strip() if query.user_id else None
        can_manage_team = session.role in {"owner", "super_admin"}
        if not can_manage_team:
            effective_user_id = session.user_id

        now = _local_now()
        entries = self._read_timeclock_entries(
            session,
            user_id=effective_user_id,
            start_date=query.start_date.strip() if query.start_date else None,
            end_date=query.end_date.strip() if query.end_date else None,
            limit=query.limit,
        )
        active_entries = self._read_timeclock_entries(
            session,
            user_id=effective_user_id,
            include_active_only=True,
            limit=200,
        )
        if not can_manage_team:
            active_entries = [entry for entry in active_entries if entry.user_id == session.user_id]

        return {
            "can_manage_team": can_manage_team,
            "filters": {
                "user_id": effective_user_id,
                "start_date": query.start_date,
                "end_date": query.end_date,
                "limit": query.limit,
            },
            "entries": [self._serialize_timeclock_entry(entry, reference_now=now) for entry in entries],
            "summary_by_user": self._build_timeclock_summary(entries if entries else active_entries, now=now),
            "active_entries": [self._serialize_timeclock_entry(entry, reference_now=now) for entry in active_entries],
        }

    def impersonate_tenant(self, session: SessionIdentity, tenant_id: str) -> SessionIdentity:
        if session.role != "super_admin":
            raise ValueError("Operazione riservata al super admin.")
        if tenant_id == SUPER_ADMIN_TENANT_ID:
            raise ValueError("Usa la sessione piattaforma per il pannello admin.")

        with self._lock:
            with self._connect_registry() as connection:
                tenant = connection.execute(
                    "SELECT id FROM tenants WHERE id = ? LIMIT 1",
                    (tenant_id,),
                ).fetchone()
                if tenant is None:
                    raise ValueError("Locale non trovato.")
                return self._create_session(connection, tenant_id=tenant_id, user_id=SUPER_ADMIN_USER_ID)

    def get_llm_settings(self, tenant_id: str, scope: str) -> tuple[dict[str, object], str | None]:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT config_json, updated_at
                FROM tenant_llm_settings
                WHERE tenant_id = ? AND scope = ?
                LIMIT 1
                """,
                (tenant_id, scope),
            ).fetchone()

        if row is None:
            return {}, None

        try:
            payload = json.loads(row["config_json"])
        except json.JSONDecodeError:
            return {}, row["updated_at"]

        if not isinstance(payload, dict):
            return {}, row["updated_at"]

        return payload, row["updated_at"]

    def upsert_llm_settings(self, tenant_id: str, scope: str, config: dict[str, object]) -> str:
        updated_at = _iso_now()
        serialized = json.dumps(config, ensure_ascii=True)

        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    INSERT INTO tenant_llm_settings (tenant_id, scope, config_json, updated_at)
                    VALUES (?, ?, ?, ?)
                    ON CONFLICT(tenant_id, scope) DO UPDATE SET
                        config_json = excluded.config_json,
                        updated_at = excluded.updated_at
                    """,
                    (tenant_id, scope, serialized, updated_at),
                )
                connection.commit()

        return updated_at

    def menu_assets_directory(self, tenant_id: str) -> Path:
        target = self._menu_assets_dir / tenant_id
        target.mkdir(parents=True, exist_ok=True)
        return target

    def fiscal_documents_directory(self, tenant_id: str) -> Path:
        target = self._fiscal_documents_dir / tenant_id
        target.mkdir(parents=True, exist_ok=True)
        return target

    def create_menu_asset(
        self,
        *,
        asset_id: str,
        tenant_id: str,
        original_name: str,
        display_name: str,
        mime_type: str,
        kind: str,
        file_size_bytes: int,
        storage_path: str,
        status: str,
        extracted_text: str = "",
        analysis_text: str = "",
        error_detail: str | None = None,
    ) -> MenuAssetRecord:
        timestamp = _iso_now()
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    INSERT INTO tenant_menu_assets (
                        id,
                        tenant_id,
                        original_name,
                        display_name,
                        mime_type,
                        kind,
                        file_size_bytes,
                        storage_path,
                        extracted_text,
                        analysis_text,
                        status,
                        error_detail,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        asset_id,
                        tenant_id,
                        original_name,
                        display_name,
                        mime_type,
                        kind,
                        file_size_bytes,
                        storage_path,
                        extracted_text,
                        analysis_text,
                        status,
                        error_detail,
                        timestamp,
                        timestamp,
                    ),
                )
                connection.commit()

        record = self.get_menu_asset(tenant_id, asset_id)
        if record is None:
            raise KeyError(f"Asset menu {asset_id} non trovato")
        return record

    def get_menu_asset(self, tenant_id: str, asset_id: str) -> MenuAssetRecord | None:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT *
                FROM tenant_menu_assets
                WHERE tenant_id = ? AND id = ?
                LIMIT 1
                """,
                (tenant_id, asset_id),
            ).fetchone()

        if row is None:
            return None

        return self._menu_asset_from_row(row)

    def list_menu_assets(self, tenant_id: str) -> list[MenuAssetRecord]:
        with self._connect_registry() as connection:
            rows = list(
                connection.execute(
                    """
                    SELECT *
                    FROM tenant_menu_assets
                    WHERE tenant_id = ?
                    ORDER BY created_at DESC, id DESC
                    """,
                    (tenant_id,),
                )
            )

        return [self._menu_asset_from_row(row) for row in rows]

    def update_menu_asset(
        self,
        tenant_id: str,
        asset_id: str,
        *,
        display_name: str | None = None,
        extracted_text: str | None = None,
        analysis_text: str | None = None,
        status: str | None = None,
        error_detail: str | None = None,
    ) -> MenuAssetRecord:
        assignments: list[str] = ["updated_at = ?"]
        values: list[object] = [_iso_now()]

        if display_name is not None:
            assignments.append("display_name = ?")
            values.append(display_name)

        if extracted_text is not None:
            assignments.append("extracted_text = ?")
            values.append(extracted_text)

        if analysis_text is not None:
            assignments.append("analysis_text = ?")
            values.append(analysis_text)

        if status is not None:
            assignments.append("status = ?")
            values.append(status)

        if error_detail is not None:
            assignments.append("error_detail = ?")
            values.append(error_detail)

        values.extend([tenant_id, asset_id])

        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    f"""
                    UPDATE tenant_menu_assets
                    SET {", ".join(assignments)}
                    WHERE tenant_id = ? AND id = ?
                    """,
                    values,
                )
                connection.commit()

        record = self.get_menu_asset(tenant_id, asset_id)
        if record is None:
            raise KeyError(f"Asset menu {asset_id} non trovato")
        return record

    def clear_menu_asset_error(self, tenant_id: str, asset_id: str) -> MenuAssetRecord:
        timestamp = _iso_now()
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    UPDATE tenant_menu_assets
                    SET error_detail = NULL, updated_at = ?
                    WHERE tenant_id = ? AND id = ?
                    """,
                    (timestamp, tenant_id, asset_id),
                )
                connection.commit()

        record = self.get_menu_asset(tenant_id, asset_id)
        if record is None:
            raise KeyError(f"Asset menu {asset_id} non trovato")
        return record

    def delete_menu_asset(self, tenant_id: str, asset_id: str) -> MenuAssetRecord | None:
        existing = self.get_menu_asset(tenant_id, asset_id)
        if existing is None:
            return None

        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    "DELETE FROM tenant_menu_assets WHERE tenant_id = ? AND id = ?",
                    (tenant_id, asset_id),
                )
                connection.commit()

        return existing

    def create_fiscal_document(
        self,
        *,
        document_id: str,
        tenant_id: str,
        original_name: str,
        display_name: str,
        mime_type: str,
        kind: str,
        file_size_bytes: int,
        file_hash: str | None = None,
        storage_path: str,
        document_type: str,
        document_number: str | None = None,
        document_date: str | None = None,
        supplier_name: str | None = None,
        total_amount: float | None = None,
        currency: str = "EUR",
        summary_text: str = "",
        extracted_text: str = "",
        preview_text: str = "",
        status: str = "processing",
        matching_status: str = "pending",
        review_status: str = "to_review",
        error_detail: str | None = None,
    ) -> FiscalDocumentRecord:
        timestamp = _iso_now()
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    INSERT INTO tenant_fiscal_documents (
                        id,
                        tenant_id,
                        original_name,
                        display_name,
                        mime_type,
                        kind,
                        file_size_bytes,
                        file_hash,
                        storage_path,
                        document_type,
                        document_number,
                        document_date,
                        supplier_name,
                        total_amount,
                        currency,
                        summary_text,
                        extracted_text,
                        preview_text,
                        status,
                        matching_status,
                        review_status,
                        error_detail,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        document_id,
                        tenant_id,
                        original_name,
                        display_name,
                        mime_type,
                        kind,
                        file_size_bytes,
                        file_hash,
                        storage_path,
                        document_type,
                        document_number,
                        document_date,
                        supplier_name,
                        total_amount,
                        currency,
                        summary_text,
                        extracted_text,
                        preview_text,
                        status,
                        matching_status,
                        review_status,
                        error_detail,
                        timestamp,
                        timestamp,
                    ),
                )
                connection.commit()

        record = self.get_fiscal_document(tenant_id, document_id)
        if record is None:
            raise KeyError(f"Documento fiscale {document_id} non trovato")
        return record

    def get_fiscal_document(self, tenant_id: str, document_id: str) -> FiscalDocumentRecord | None:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT *
                FROM tenant_fiscal_documents
                WHERE tenant_id = ? AND id = ?
                LIMIT 1
                """,
                (tenant_id, document_id),
            ).fetchone()

        if row is None:
            return None
        return self._fiscal_document_from_row(row)

    def get_fiscal_document_by_hash(self, tenant_id: str, file_hash: str) -> FiscalDocumentRecord | None:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT *
                FROM tenant_fiscal_documents
                WHERE tenant_id = ? AND file_hash = ?
                ORDER BY created_at DESC
                LIMIT 1
                """,
                (tenant_id, file_hash),
            ).fetchone()

        if row is None:
            return None
        return self._fiscal_document_from_row(row)

    def list_fiscal_documents(self, tenant_id: str) -> list[FiscalDocumentRecord]:
        with self._connect_registry() as connection:
            rows = list(
                connection.execute(
                    """
                    SELECT *
                    FROM tenant_fiscal_documents
                    WHERE tenant_id = ?
                    ORDER BY created_at DESC, id DESC
                    """,
                    (tenant_id,),
                )
            )
        records = [self._fiscal_document_from_row(row) for row in rows]
        deduped: list[FiscalDocumentRecord] = []
        seen_semantic_keys: set[tuple[object, ...]] = set()
        seen_hashes: set[str] = set()
        for record in records:
            semantic_key = self._fiscal_document_dedup_key(record)
            if semantic_key in seen_semantic_keys:
                continue
            file_hash = (record.file_hash or "").strip()
            if file_hash and file_hash in seen_hashes:
                continue
            seen_semantic_keys.add(semantic_key)
            if file_hash:
                seen_hashes.add(file_hash)
            deduped.append(record)
        return deduped

    def list_fiscal_document_items(self, tenant_id: str, document_id: str) -> list[FiscalDocumentLineItemRecord]:
        with self._connect_registry() as connection:
            rows = list(
                connection.execute(
                    """
                    SELECT *
                    FROM tenant_fiscal_document_items
                    WHERE tenant_id = ? AND document_id = ?
                    ORDER BY line_index ASC, created_at ASC, id ASC
                    """,
                    (tenant_id, document_id),
                )
            )
        return [self._fiscal_document_line_item_from_row(row) for row in rows]

    def replace_fiscal_document_items(
        self,
        tenant_id: str,
        document_id: str,
        items: list[dict[str, object]],
    ) -> list[FiscalDocumentLineItemRecord]:
        timestamp = _iso_now()
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    "DELETE FROM tenant_fiscal_document_items WHERE tenant_id = ? AND document_id = ?",
                    (tenant_id, document_id),
                )
                for index, item in enumerate(items):
                    connection.execute(
                        """
                        INSERT INTO tenant_fiscal_document_items (
                            id,
                            tenant_id,
                            document_id,
                            line_index,
                            product_code,
                            iso_code,
                            description,
                            category_code,
                            unit_code,
                            pack_count,
                            quantity,
                            gross_quantity,
                            tare_quantity,
                            net_quantity,
                            unit_price,
                            line_total,
                            vat_code,
                            raw_row_text,
                            created_at,
                            updated_at
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                        """,
                        (
                            f"fdocitem_{uuid.uuid4().hex}",
                            tenant_id,
                            document_id,
                            int(item.get("line_index") or index),
                            item.get("product_code"),
                            item.get("iso_code"),
                            str(item.get("description") or "").strip(),
                            item.get("category_code"),
                            item.get("unit_code"),
                            item.get("pack_count"),
                            item.get("quantity"),
                            item.get("gross_quantity"),
                            item.get("tare_quantity"),
                            item.get("net_quantity"),
                            item.get("unit_price"),
                            item.get("line_total"),
                            item.get("vat_code"),
                            str(item.get("raw_row_text") or "").strip(),
                            timestamp,
                            timestamp,
                        ),
                    )
                connection.commit()
        return self.list_fiscal_document_items(tenant_id, document_id)

    def get_fiscal_document_settings(self, tenant_id: str) -> FiscalDocumentSettingsRecord:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT tenant_id, inbound_email, updated_at
                FROM tenant_fiscal_document_settings
                WHERE tenant_id = ?
                LIMIT 1
                """,
                (tenant_id,),
            ).fetchone()

        if row is None:
            return FiscalDocumentSettingsRecord(tenant_id=tenant_id, inbound_email=None, updated_at=None)

        return self._fiscal_document_settings_from_row(row)

    def upsert_fiscal_document_settings(self, tenant_id: str, *, inbound_email: str | None) -> FiscalDocumentSettingsRecord:
        updated_at = _iso_now()
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    INSERT INTO tenant_fiscal_document_settings (tenant_id, inbound_email, updated_at)
                    VALUES (?, ?, ?)
                    ON CONFLICT(tenant_id) DO UPDATE SET
                        inbound_email = excluded.inbound_email,
                        updated_at = excluded.updated_at
                    """,
                    (tenant_id, inbound_email, updated_at),
                )
                connection.commit()

        return self.get_fiscal_document_settings(tenant_id)

    def update_fiscal_document(
        self,
        tenant_id: str,
        document_id: str,
        *,
        display_name: str | None = None,
        file_hash: str | None = None,
        document_type: str | None = None,
        document_number: str | None = None,
        document_date: str | None = None,
        supplier_name: str | None = None,
        total_amount: float | None = None,
        currency: str | None = None,
        summary_text: str | None = None,
        extracted_text: str | None = None,
        preview_text: str | None = None,
        drive_file_id: str | None = None,
        drive_web_url: str | None = None,
        drive_uploaded_at: str | None = None,
        status: str | None = None,
        matching_status: str | None = None,
        review_status: str | None = None,
        error_detail: str | None = None,
    ) -> FiscalDocumentRecord:
        assignments: list[str] = ["updated_at = ?"]
        values: list[object] = [_iso_now()]

        for column, value in (
            ("display_name", display_name),
            ("file_hash", file_hash),
            ("document_type", document_type),
            ("document_number", document_number),
            ("document_date", document_date),
            ("supplier_name", supplier_name),
            ("total_amount", total_amount),
            ("currency", currency),
            ("summary_text", summary_text),
            ("extracted_text", extracted_text),
            ("preview_text", preview_text),
            ("drive_file_id", drive_file_id),
            ("drive_web_url", drive_web_url),
            ("drive_uploaded_at", drive_uploaded_at),
            ("status", status),
            ("matching_status", matching_status),
            ("review_status", review_status),
            ("error_detail", error_detail),
        ):
            if value is not None:
                assignments.append(f"{column} = ?")
                values.append(value)

        values.extend([tenant_id, document_id])
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    f"""
                    UPDATE tenant_fiscal_documents
                    SET {", ".join(assignments)}
                    WHERE tenant_id = ? AND id = ?
                    """,
                    values,
                )
                connection.commit()

        record = self.get_fiscal_document(tenant_id, document_id)
        if record is None:
            raise KeyError(f"Documento fiscale {document_id} non trovato")
        return record

    def delete_fiscal_document(self, tenant_id: str, document_id: str) -> bool:
        record = self.get_fiscal_document(tenant_id, document_id)
        if record is None:
            return False

        storage_path = Path(record.storage_path) if (record.storage_path or "").strip() else None

        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    UPDATE tenant_fiscal_document_inbox_items
                    SET document_id = NULL,
                        error_detail = ?,
                        updated_at = ?
                    WHERE tenant_id = ? AND document_id = ?
                    """,
                    ("Documento eliminato dall'archivio fiscale.", _iso_now(), tenant_id, document_id),
                )
                connection.execute(
                    "DELETE FROM tenant_fiscal_document_items WHERE tenant_id = ? AND document_id = ?",
                    (tenant_id, document_id),
                )
                connection.execute(
                    "DELETE FROM tenant_fiscal_documents WHERE tenant_id = ? AND id = ?",
                    (tenant_id, document_id),
                )
                connection.commit()

        if storage_path is not None:
            try:
                if storage_path.exists():
                    storage_path.unlink()
            except OSError:
                pass

        return True

    def clear_fiscal_documents_archive(self, tenant_id: str) -> tuple[int, int]:
        documents = self.list_fiscal_documents(tenant_id)
        inbox_items = self.list_fiscal_document_inbox_items(tenant_id, limit=5000)
        storage_paths = [
            Path(document.storage_path)
            for document in documents
            if (document.storage_path or "").strip()
        ]

        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    "DELETE FROM tenant_fiscal_document_items WHERE tenant_id = ?",
                    (tenant_id,),
                )
                connection.execute(
                    "DELETE FROM tenant_fiscal_document_inbox_items WHERE tenant_id = ?",
                    (tenant_id,),
                )
                connection.execute(
                    "DELETE FROM tenant_fiscal_documents WHERE tenant_id = ?",
                    (tenant_id,),
                )
                connection.commit()

        for storage_path in storage_paths:
            try:
                if storage_path.exists():
                    storage_path.unlink()
            except OSError:
                continue

        return len(documents), len(inbox_items)

    def get_fiscal_document_inbox_item(
        self,
        tenant_id: str,
        *,
        message_id: str,
        attachment_id: str,
    ) -> FiscalDocumentInboxItemRecord | None:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT *
                FROM tenant_fiscal_document_inbox_items
                WHERE tenant_id = ? AND message_id = ? AND attachment_id = ?
                LIMIT 1
                """,
                (tenant_id, message_id, attachment_id),
            ).fetchone()

        if row is None:
            return None
        return self._fiscal_document_inbox_item_from_row(row)

    def list_fiscal_document_inbox_items(self, tenant_id: str, *, limit: int = 40) -> list[FiscalDocumentInboxItemRecord]:
        raw_limit = min(max(max(1, limit) * 100, max(1, limit)), 5000)
        with self._connect_registry() as connection:
            rows = list(
                connection.execute(
                    """
                    SELECT *
                    FROM tenant_fiscal_document_inbox_items
                    WHERE tenant_id = ?
                    ORDER BY created_at DESC, id DESC
                    LIMIT ?
                    """,
                    (tenant_id, raw_limit),
                )
            )
        records = [self._fiscal_document_inbox_item_from_row(row) for row in rows]
        deduped_by_key: dict[tuple[object, ...], FiscalDocumentInboxItemRecord] = {}
        for record in records:
            dedup_key = self._fiscal_document_inbox_dedup_key(record)
            current = deduped_by_key.get(dedup_key)
            if current is not None:
                current_priority = self._fiscal_document_inbox_dedup_priority(current)
                record_priority = self._fiscal_document_inbox_dedup_priority(record)
                if current_priority < record_priority:
                    continue
                if current_priority == record_priority and (current.created_at or "", current.id) >= (record.created_at or "", record.id):
                    continue
            deduped_by_key[dedup_key] = record

        deduped = sorted(
            deduped_by_key.values(),
            key=lambda record: (record.created_at or "", record.id),
            reverse=True,
        )
        return deduped[: max(1, limit)]

    def get_fiscal_document_inbox_item_by_hash(self, tenant_id: str, file_hash: str) -> FiscalDocumentInboxItemRecord | None:
        normalized_hash = (file_hash or "").strip()
        if not normalized_hash:
            return None
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT *
                FROM tenant_fiscal_document_inbox_items
                WHERE tenant_id = ? AND file_hash = ?
                ORDER BY created_at DESC
                LIMIT 1
                """,
                (tenant_id, normalized_hash),
            ).fetchone()

        if row is None:
            return None
        return self._fiscal_document_inbox_item_from_row(row)

    def get_fiscal_document_inbox_item_by_message_attachment_name(
        self,
        tenant_id: str,
        *,
        message_id: str,
        attachment_name: str,
    ) -> FiscalDocumentInboxItemRecord | None:
        normalized_message_id = (message_id or "").strip()
        normalized_attachment_name = (attachment_name or "").strip().casefold()
        if not normalized_message_id or not normalized_attachment_name:
            return None
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT *
                FROM tenant_fiscal_document_inbox_items
                WHERE tenant_id = ?
                    AND message_id = ?
                    AND lower(attachment_name) = ?
                ORDER BY
                    CASE
                        WHEN sync_status = 'imported' AND document_id IS NOT NULL AND document_id <> '' THEN 0
                        WHEN sync_status = 'imported' THEN 1
                        WHEN file_hash IS NOT NULL AND file_hash <> '' THEN 2
                        WHEN sync_status = 'unsupported' THEN 3
                        WHEN sync_status = 'error' THEN 4
                        ELSE 5
                    END,
                    created_at DESC,
                    id DESC
                LIMIT 1
                """,
                (tenant_id, normalized_message_id, normalized_attachment_name),
            ).fetchone()

        if row is None:
            return None
        return self._fiscal_document_inbox_item_from_row(row)

    def create_fiscal_document_inbox_item(
        self,
        *,
        item_id: str,
        tenant_id: str,
        message_id: str,
        attachment_id: str,
        file_hash: str | None = None,
        subject: str,
        sender: str | None,
        received_at: str | None,
        attachment_name: str,
        mime_type: str,
        sync_status: str,
        document_id: str | None = None,
        error_detail: str | None = None,
    ) -> FiscalDocumentInboxItemRecord:
        timestamp = _iso_now()
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    INSERT INTO tenant_fiscal_document_inbox_items (
                        id,
                        tenant_id,
                        message_id,
                        attachment_id,
                        file_hash,
                        subject,
                        sender,
                        received_at,
                        attachment_name,
                        mime_type,
                        sync_status,
                        document_id,
                        error_detail,
                        created_at,
                        updated_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        item_id,
                        tenant_id,
                        message_id,
                        attachment_id,
                        file_hash,
                        subject,
                        sender,
                        received_at,
                        attachment_name,
                        mime_type,
                        sync_status,
                        document_id,
                        error_detail,
                        timestamp,
                        timestamp,
                    ),
                )
                connection.commit()

        record = self.get_fiscal_document_inbox_item(tenant_id, message_id=message_id, attachment_id=attachment_id)
        if record is None:
            raise KeyError(f"Elemento inbox fiscale {item_id} non trovato")
        return record

    def update_fiscal_document_inbox_item(
        self,
        tenant_id: str,
        *,
        message_id: str,
        attachment_id: str,
        file_hash: str | None = None,
        subject: str | None = None,
        sender: str | None = None,
        received_at: str | None = None,
        attachment_name: str | None = None,
        mime_type: str | None = None,
        sync_status: str | None = None,
        document_id: str | None = None,
        error_detail: str | None = None,
    ) -> FiscalDocumentInboxItemRecord:
        assignments: list[str] = ["updated_at = ?"]
        values: list[object] = [_iso_now()]

        for column, value in (
            ("file_hash", file_hash),
            ("subject", subject),
            ("sender", sender),
            ("received_at", received_at),
            ("attachment_name", attachment_name),
            ("mime_type", mime_type),
            ("sync_status", sync_status),
            ("document_id", document_id),
            ("error_detail", error_detail),
        ):
            if value is not None:
                assignments.append(f"{column} = ?")
                values.append(value)

        values.extend([tenant_id, message_id, attachment_id])
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    f"""
                    UPDATE tenant_fiscal_document_inbox_items
                    SET {", ".join(assignments)}
                    WHERE tenant_id = ? AND message_id = ? AND attachment_id = ?
                    """,
                    values,
                )
                connection.commit()

        record = self.get_fiscal_document_inbox_item(tenant_id, message_id=message_id, attachment_id=attachment_id)
        if record is None:
            raise KeyError(f"Elemento inbox fiscale {message_id}/{attachment_id} non trovato")
        return record

    def get_assistant_thread(self, thread_id: str) -> AssistantThreadRecord | None:
        with self._connect_registry() as connection:
            row = connection.execute(
                "SELECT * FROM assistant_threads WHERE id = ? LIMIT 1",
                (thread_id,),
            ).fetchone()
        if row is None:
            return None
        return self._assistant_thread_from_row(row)

    def get_active_assistant_thread(self, session: SessionIdentity, surface: str = "home") -> AssistantThreadRecord | None:
        with self._connect_registry() as connection:
            row = connection.execute(
                """
                SELECT *
                FROM assistant_threads
                WHERE tenant_id = ? AND user_id = ? AND surface = ? AND status = 'active'
                ORDER BY updated_at DESC, created_at DESC
                LIMIT 1
                """,
                (session.tenant_id, session.user_id, surface),
            ).fetchone()
        if row is None:
            return None
        return self._assistant_thread_from_row(row)

    def list_assistant_messages(self, thread_id: str, *, limit: int = 80) -> list[AssistantStoredMessage]:
        with self._connect_registry() as connection:
            rows = list(
                connection.execute(
                    """
                    SELECT *
                    FROM (
                        SELECT *
                        FROM assistant_messages
                        WHERE thread_id = ?
                        ORDER BY sort_index DESC
                        LIMIT ?
                    )
                    ORDER BY sort_index ASC
                    """,
                    (thread_id, limit),
                )
            )
        return [self._assistant_message_from_row(row) for row in rows]

    def create_assistant_thread(self, session: SessionIdentity, surface: str = "home") -> AssistantThreadRecord:
        thread_id = f"ath_{uuid.uuid4().hex}"
        timestamp = _iso_now()
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    INSERT INTO assistant_threads (id, tenant_id, user_id, surface, status, state_json, created_at, updated_at)
                    VALUES (?, ?, ?, ?, 'active', ?, ?, ?)
                    """,
                    (thread_id, session.tenant_id, session.user_id, surface, "{}", timestamp, timestamp),
                )
                connection.commit()
        record = self.get_assistant_thread(thread_id)
        if record is None:
            raise KeyError(f"Thread assistente {thread_id} non creato")
        return record

    def ensure_assistant_thread(
        self,
        session: SessionIdentity,
        *,
        surface: str = "home",
        thread_id: str | None = None,
    ) -> AssistantThreadRecord:
        if thread_id:
            current = self.get_assistant_thread(thread_id)
            if (
                current is not None
                and current.status == "active"
                and current.tenant_id == session.tenant_id
                and current.user_id == session.user_id
                and current.surface == surface
            ):
                return current
        current = self.get_active_assistant_thread(session, surface)
        if current is not None:
            return current
        return self.create_assistant_thread(session, surface)

    def append_assistant_messages(
        self,
        thread_id: str,
        messages: list[dict[str, str]],
    ) -> tuple[AssistantThreadRecord, list[AssistantStoredMessage]]:
        sanitized = [
            {
                "role": str(message.get("role") or "").strip(),
                "content": str(message.get("content") or "").strip(),
            }
            for message in messages
            if str(message.get("role") or "").strip() in {"user", "assistant"} and str(message.get("content") or "").strip()
        ]
        if not sanitized:
            thread = self.get_assistant_thread(thread_id)
            if thread is None:
                raise KeyError(f"Thread assistente {thread_id} non trovato")
            return thread, []

        timestamp = _iso_now()
        created: list[AssistantStoredMessage] = []
        with self._lock:
            with self._connect_registry() as connection:
                thread_row = connection.execute(
                    "SELECT * FROM assistant_threads WHERE id = ? LIMIT 1",
                    (thread_id,),
                ).fetchone()
                if thread_row is None:
                    raise KeyError(f"Thread assistente {thread_id} non trovato")

                max_sort_index = connection.execute(
                    "SELECT COALESCE(MAX(sort_index), 0) FROM assistant_messages WHERE thread_id = ?",
                    (thread_id,),
                ).fetchone()[0]
                sort_index = int(max_sort_index or 0)

                for message in sanitized:
                    sort_index += 1
                    message_id = f"ams_{uuid.uuid4().hex}"
                    connection.execute(
                        """
                        INSERT INTO assistant_messages (id, thread_id, role, content, created_at, sort_index)
                        VALUES (?, ?, ?, ?, ?, ?)
                        """,
                        (message_id, thread_id, message["role"], message["content"], timestamp, sort_index),
                    )
                    created.append(
                        AssistantStoredMessage(
                            id=message_id,
                            thread_id=thread_id,
                            role=message["role"],
                            content=message["content"],
                            created_at=timestamp,
                            sort_index=sort_index,
                        )
                    )

                connection.execute(
                    """
                    UPDATE assistant_threads
                    SET updated_at = ?, status = 'active'
                    WHERE id = ?
                    """,
                    (timestamp, thread_id),
                )
                connection.commit()

        thread = self.get_assistant_thread(thread_id)
        if thread is None:
            raise KeyError(f"Thread assistente {thread_id} non trovato")
        return thread, created

    def archive_assistant_thread(
        self,
        session: SessionIdentity,
        *,
        surface: str = "home",
        thread_id: str | None = None,
    ) -> None:
        with self._lock:
            with self._connect_registry() as connection:
                if thread_id:
                    connection.execute(
                        """
                        UPDATE assistant_threads
                        SET status = 'archived', updated_at = ?
                        WHERE id = ? AND tenant_id = ? AND user_id = ? AND surface = ?
                        """,
                        (_iso_now(), thread_id, session.tenant_id, session.user_id, surface),
                    )
                else:
                    connection.execute(
                        """
                        UPDATE assistant_threads
                        SET status = 'archived', updated_at = ?
                        WHERE id IN (
                            SELECT id
                            FROM assistant_threads
                            WHERE tenant_id = ? AND user_id = ? AND surface = ? AND status = 'active'
                            ORDER BY updated_at DESC, created_at DESC
                            LIMIT 1
                        )
                        """,
                        (_iso_now(), session.tenant_id, session.user_id, surface),
                    )
                connection.commit()

    def get_assistant_thread_state(self, thread_id: str) -> dict[str, object]:
        thread = self.get_assistant_thread(thread_id)
        if thread is None or not thread.state_json:
            return {}
        try:
            payload = json.loads(thread.state_json)
        except json.JSONDecodeError:
            return {}
        return payload if isinstance(payload, dict) else {}

    def update_assistant_thread_state(self, thread_id: str, state: dict[str, object]) -> AssistantThreadRecord:
        serialized_state = json.dumps(state or {}, ensure_ascii=False, default=str)
        timestamp = _iso_now()
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    UPDATE assistant_threads
                    SET state_json = ?, updated_at = ?
                    WHERE id = ?
                    """,
                    (serialized_state, timestamp, thread_id),
                )
                connection.commit()

        record = self.get_assistant_thread(thread_id)
        if record is None:
            raise KeyError(f"Thread assistente {thread_id} non trovato")
        return record

    def create_assistant_run(
        self,
        *,
        thread_id: str,
        session: SessionIdentity,
        surface: str,
        route: str,
        model: str,
        user_message: str,
        assistant_reply: str,
        trace: dict[str, object],
    ) -> AssistantRunRecord:
        run_id = f"arn_{uuid.uuid4().hex}"
        created_at = _iso_now()
        serialized_trace = json.dumps(trace, ensure_ascii=False, default=str)
        with self._lock:
            with self._connect_registry() as connection:
                connection.execute(
                    """
                    INSERT INTO assistant_runs (
                        id, thread_id, tenant_id, user_id, surface, route, model,
                        user_message, assistant_reply, trace_json, created_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                    """,
                    (
                        run_id,
                        thread_id,
                        session.tenant_id,
                        session.user_id,
                        surface,
                        route,
                        model,
                        user_message,
                        assistant_reply,
                        serialized_trace,
                        created_at,
                    ),
                )
                connection.commit()
        with self._connect_registry() as connection:
            record = connection.execute("SELECT * FROM assistant_runs WHERE id = ? LIMIT 1", (run_id,)).fetchone()
        if record is None:
            raise KeyError(f"Run assistente {run_id} non trovato")
        return self._assistant_run_from_row(record)

    def list_assistant_runs(self, thread_id: str, *, limit: int = 20) -> list[AssistantRunRecord]:
        with self._connect_registry() as connection:
            rows = list(
                connection.execute(
                    """
                    SELECT *
                    FROM assistant_runs
                    WHERE thread_id = ?
                    ORDER BY created_at DESC
                    LIMIT ?
                    """,
                    (thread_id, limit),
                )
            )
        return [self._assistant_run_from_row(row) for row in rows]


_tenant_store: TenantStore | None = None


def get_tenant_store() -> TenantStore:
    global _tenant_store
    if _tenant_store is None:
        _tenant_store = TenantStore()
    return _tenant_store
