from django.db import models from django.utils import timezone from django.core.validators import MinValueValidator, MaxValueValidator class Institute(models.Model): name = models.CharField(max_length=100) def __str__(self): return self.name class Client(models.Model): name = models.CharField(max_length=100) address = models.TextField() institute = models.ForeignKey(Institute, on_delete=models.CASCADE) def __str__(self): return f"{self.name} ({self.institute.name})" class MonthlySheet(models.Model): """Represents one monthly page""" year = models.IntegerField() month = models.IntegerField() # 1-12 created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: unique_together = ['year', 'month'] ordering = ['year', 'month'] def __str__(self): return f"{self.year}-{self.month:02d}" class Cell(models.Model): """A single cell in the spreadsheet""" sheet = models.ForeignKey(MonthlySheet, on_delete=models.CASCADE, related_name='cells') client = models.ForeignKey(Client, on_delete=models.CASCADE) table_type = models.CharField(max_length=20, choices=[ ('top_left', 'Top Left Table'), ('top_right', 'Top Right Table'), ('bottom_1', 'Bottom Table 1'), ('bottom_2', 'Bottom Table 2'), ('bottom_3', 'Bottom Table 3'), ]) row_index = models.IntegerField() # 0-23 for top tables, 0-9 for bottom column_index = models.IntegerField() # Actually client index (0-5) is_formula = models.BooleanField(default=False) # Cell content value = models.DecimalField(max_digits=15, decimal_places=6, null=True, blank=True) formula = models.TextField(blank=True) # Metadata data_type = models.CharField(max_length=20, default='number', choices=[ ('number', 'Number'), ('text', 'Text'), ('date', 'Date'), ]) class Meta: unique_together = ['sheet', 'client', 'table_type', 'row_index', 'column_index'] indexes = [ models.Index(fields=['sheet', 'table_type', 'row_index']), ] def __str__(self): return f"{self.sheet} - {self.client.name} - {self.table_type}[{self.row_index}][{self.column_index}]" class CellReference(models.Model): """Track dependencies between cells for calculations""" source_cell = models.ForeignKey(Cell, on_delete=models.CASCADE, related_name='dependents') target_cell = models.ForeignKey(Cell, on_delete=models.CASCADE, related_name='dependencies') class Meta: unique_together = ['source_cell', 'target_cell'] from decimal import Decimal from django.db import models class Betriebskosten(models.Model): KOSTENTYP_CHOICES = [ ('sach', 'Sach'), ('helium', 'Helium'), ] gegenstand = models.CharField("Gegenstand", max_length=200) buchungsdatum = models.DateField('Zahlungsdatum') rechnungsnummer = models.CharField('Firma', max_length=50) kostentyp = models.CharField('Kostentyp', max_length=10, choices=KOSTENTYP_CHOICES) # IMPORTANT: now this field stores m³ (not liters) gas_volume = models.DecimalField('Gasvolumen (m³)', max_digits=10, decimal_places=2, null=True, blank=True) betrag = models.DecimalField('Betrag (€)', max_digits=10, decimal_places=2) beschreibung = models.TextField('Beschreibung', blank=True) @property def gas_volume_liter(self): # Liter = m³ / 0.75 if self.kostentyp == 'helium' and self.gas_volume: return self.gas_volume / Decimal("0.75") return None @property def price_per_m3(self): if self.kostentyp == 'helium' and self.gas_volume: return self.betrag / self.gas_volume return None @property def price_per_liter(self): # Preis(Liter) = betrag / liter liters = self.gas_volume_liter if self.kostentyp == 'helium' and liters: return self.betrag / liters return None # Or simpler: Just store row calculations in a JSONField class TableConfig(models.Model): """Configuration for table calculations""" table_type = models.CharField(max_length=20, unique=True, choices=[ ('top_left', 'Top Left Table'), ('top_right', 'Top Right Table'), ('bottom_1', 'Bottom Table 1'), ('bottom_2', 'Bottom Table 2'), ('bottom_3', 'Bottom Table 3'), ]) calculations = models.JSONField(default=dict) # {11: "10 + 9", 15: "14 - 13"} def __str__(self): return f"{self.get_table_type_display()} Config" class ExcelEntry(models.Model): client = models.ForeignKey(Client, on_delete=models.CASCADE) date = models.DateField(default=timezone.now) pressure = models.DecimalField( max_digits=10, decimal_places=2, validators=[MinValueValidator(0)], default=0.00 ) purity = models.DecimalField( max_digits=5, decimal_places=2, validators=[MinValueValidator(0), MaxValueValidator(100)], default=0.00 ) notes = models.TextField(blank=True, null=True) date_joined = models.DateField(auto_now_add=True) # Manual input lhe_zus = models.DecimalField( max_digits=10, decimal_places=3, validators=[MinValueValidator(0)], default=0.0 ) druckkorrektur = models.DecimalField( max_digits=10, decimal_places=3, validators=[MinValueValidator(0)], default=1.0 ) # Auto-calculated values (saved) constant_300 = models.DecimalField( max_digits=10, decimal_places=3, default=300.0 ) korrig_druck = models.DecimalField( max_digits=12, decimal_places=6, default=0.0 ) nm3 = models.DecimalField( max_digits=12, decimal_places=6, default=0.0 ) lhe = models.DecimalField( max_digits=12, decimal_places=6, default=0.0 ) lhe_ges = models.DecimalField( max_digits=12, decimal_places=6, default=0.0 ) def __str__(self): return f"{self.client.name} - {self.date}" class SecondTableEntry(models.Model): client = models.ForeignKey(Client, on_delete=models.CASCADE) date = models.DateField(default=timezone.now) is_warm = models.BooleanField(default=False) lhe_delivery = models.CharField(max_length=100, blank=True, null=True) vor = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) nach = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True) lhe_output = models.DecimalField( max_digits=10, decimal_places=2, validators=[MinValueValidator(0)], blank=True, null=True ) notes = models.TextField(blank=True, null=True) date_joined = models.DateField(auto_now_add=True) class Meta: ordering = ["date", "id"] def __str__(self): return f"{self.client.name} - {self.date}" class MonthlySummary(models.Model): """ Stores per-month summary values that we need in other months or in the half-year overview. """ sheet = models.OneToOneField( MonthlySheet, on_delete=models.CASCADE, related_name='summary', ) # K44: Gesamtbestand neu (Lit. LHe) from Bottom Table 2 gesamtbestand_neu_lhe = models.DecimalField( max_digits=18, decimal_places=6, null=True, blank=True, ) # Gasbestand (Lit. LHe) from Bottom Table 1 gasbestand_lhe = models.DecimalField( max_digits=18, decimal_places=6, null=True, blank=True, ) # Verbraucherverluste (Lit. L-He) from overall summary verbraucherverlust_lhe = models.DecimalField( max_digits=18, decimal_places=6, null=True, blank=True, ) def __str__(self): return f"Summary {self.sheet.year}-{self.sheet.month:02d}" class BetriebskostenSummary(models.Model): personalkosten = models.DecimalField(max_digits=12, decimal_places=2, default=0) instandhaltung = models.DecimalField(max_digits=12, decimal_places=2, default=0) heliumkosten = models.DecimalField(max_digits=12, decimal_places=2, default=0) bezugskosten_gashe = models.DecimalField(max_digits=12, decimal_places=4, default=0) umlage_personal = models.DecimalField(max_digits=12, decimal_places=2, default=0) def recalculate(self): from django.db.models import Sum from django.db.models.functions import Coalesce from django.db.models import DecimalField, Value from .models import Betriebskosten items = Betriebskosten.objects.all() sach_sum = items.filter(kostentyp='sach').aggregate( total=Coalesce(Sum('betrag'), Value(0, output_field=DecimalField())) )['total'] or Decimal('0') helium_sum = items.filter(kostentyp='helium').aggregate( total=Coalesce(Sum('betrag'), Value(0, output_field=DecimalField())) )['total'] or Decimal('0') helium_m3_sum = items.filter(kostentyp='helium').aggregate( total=Coalesce(Sum('gas_volume'), Value(0, output_field=DecimalField())) )['total'] or Decimal('0') bezug = (helium_sum / helium_m3_sum) if helium_m3_sum not in (None, 0, Decimal('0')) else Decimal('0') self.instandhaltung = sach_sum self.heliumkosten = helium_sum self.bezugskosten_gashe = bezug self.umlage_personal = self.personalkosten / 2 self.save() # models.py from django.db import models class AbrechnungCell(models.Model): """ Storage for the 'Abrechnung' page where columns are custom (not 1:1 with Client). Values are saved by: (interval_year, interval_start_month, row_key, col_key). """ interval_year = models.IntegerField() interval_start_month = models.IntegerField() # first month of the 6-month window row_key = models.CharField(max_length=60) col_key = models.CharField(max_length=60) value = models.DecimalField(max_digits=18, decimal_places=6, null=True, blank=True) class Meta: unique_together = ("interval_year", "interval_start_month", "row_key", "col_key") def __str__(self): return f"Abrechnung {self.interval_year}/{self.interval_start_month} {self.row_key}:{self.col_key}={self.value}"