Source code for ferrosoft.apps.emiflow.models.settings

"""Per-tenant emiflow configuration models.

:class:`Settings` is a singleton (created lazily on first read) that holds
references to storage directories and the user-defined emissions catalog;
:class:`TransportChainDefaults` keeps default transport-chain attributes
per vehicle type; :class:`BusinessCentralSettings` holds OAuth credentials
for the Dynamics 365 Business Central integration.
"""
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from ferrosoft.apps.dataimport.convert import create_or_get_model
from ferrosoft.apps.emiflow.models.catalog import EmissionsCatalog, VehicleType
from ferrosoft.apps.emiflow.models.transport import DistanceType, TransportOperation
from ferrosoft.apps.ferrobase.models import UUIDModel, Directory


[docs] class Settings(UUIDModel): """User configurable settings for Emiflow.""" class Meta: verbose_name = _("Emiflow settings") report_directory = models.ForeignKey( Directory, on_delete=models.SET_NULL, related_name="+", null=True, blank=True, verbose_name=_("Storage directory for emission report documents"), ) company_logo_directory = models.ForeignKey( Directory, on_delete=models.SET_NULL, related_name="+", null=True, blank=True, verbose_name=_("Storage directory for company logos"), ) user_catalog = models.ForeignKey( EmissionsCatalog, on_delete=models.SET_NULL, related_name="+", null=True, blank=True, verbose_name=_("Emissions catalog for user defined values"), )
[docs] @classmethod def get_instance(cls) -> "Settings": """Return the singleton ``Settings`` row, creating it on first call. Prefetches the related :class:`TransportChainDefaults` rows so subsequent ``get_transport_chain_defaults`` lookups don't hit the database. """ it = cls.objects.prefetch_related("transport_chain_defaults").first() if it is None: return cls.objects.create() return it
[docs] def get_company_logo_directory(self) -> Directory: """Resolve the company-logo directory, creating it on first call. Falls back to creating (or fetching) a directory named *Company Logos* and persisting the FK when the field was unset. """ if self.company_logo_directory is not None: return self.company_logo_directory directory = create_or_get_model( Directory, lambda: Directory.objects.create(name=_("Company Logos")) ) if self.company_logo_directory is None: self.company_logo_directory = directory self.save(update_fields=["company_logo_directory"]) return directory
[docs] def get_transport_chain_defaults( self, vehicle_type: VehicleType ) -> "TransportChainDefaults": """Return the defaults row for ``vehicle_type``, inserting an empty one if absent.""" defaults = self.transport_chain_defaults.filter( vehicle_type=vehicle_type.value ).first() if defaults is not None: return defaults return TransportChainDefaults.objects.create( settings=self, vehicle_type=vehicle_type )
[docs] class TransportChainDefaults(UUIDModel): """Per-vehicle-type defaults applied when creating a new transport chain.""" class Meta: verbose_name = _("Defaults for transport chains scoped by vehicle type") constraints = [ models.UniqueConstraint( name="emiflow_uniq_tc_defaults", fields=[ "settings", "vehicle_type", ], ) ] settings = models.ForeignKey( Settings, on_delete=models.CASCADE, related_name="transport_chain_defaults", ) vehicle_type = models.PositiveIntegerField( verbose_name=_("Vehicle type"), choices=VehicleType.choices, ) distance_type = models.PositiveIntegerField( verbose_name=_("Distance type"), choices=DistanceType.choices, null=True, blank=True, ) transport_operation = models.ForeignKey( TransportOperation, blank=True, null=True, on_delete=models.SET_NULL, related_name="+", verbose_name=_("Transport Operation"), ) @property def are_defaults_set(self) -> bool: """Return ``True`` when at least a distance type has been configured.""" return self.distance_type is not None
[docs] class BusinessCentralSettings(UUIDModel): """Microsoft Dynamics 365 Business Central connection settings (per tenant). Stores the BC tenant ID, environment, API base URL, and OAuth client-credentials parameters used by the BC integration in :mod:`ferrosoft.apps.emiflow.services.businesscentral`. """ class Meta: verbose_name = _("Business Central Settings") verbose_name_plural = _("Business Central Settings") tenant_id = models.UUIDField( null=True, verbose_name=_("Business Central Tenant ID"), ) environment = models.CharField( default="", verbose_name=_("Business Central Environment Name"), ) api_base_url = models.CharField( default="https://api.businesscentral.dynamics.com", verbose_name=_("Business Central API Base URL"), ) oauth_auth_url = models.CharField( default="https://login.microsoftonline.com/{{TENANT_ID}}/oauth2/v2.0/token", verbose_name=_("OAuth Authorization URL"), help_text=_( "URL for Client Credentials grant type. Optional placeholder {{TENANT_ID}} is replaced with Business Central tenant ID." ), ) oauth_client_id = models.CharField( default="", verbose_name=_("OAuth Client ID"), ) oauth_client_secret = models.CharField( default="", verbose_name=_("OAuth Client Secret"), ) oauth_scope = models.CharField( default="https://api.businesscentral.dynamics.com/.default", verbose_name=_("OAuth Scope"), )
[docs] @classmethod def get_instance(cls): """Return the singleton BC settings row, creating it on first call.""" it = cls.objects.first() if it is None: return cls.objects.create() return it
[docs] def get_absolute_url(self): return reverse("emiflow:businesscentralsettings_update")