Merge branch 'main' into container

This commit is contained in:
2026-04-14 11:03:36 +00:00
20 changed files with 220 additions and 14583 deletions
BIN
View File
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
[tool.ruff]
line-length = 100
target-version = "py312"
exclude = [
"migrations",
".venv",
"venv",
"__pycache__",
]
[tool.ruff.lint]
select = ["E", "F", "I"]
ignore = []
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
+1 -1
View File
@@ -15,7 +15,7 @@ Including another URLconftesting test
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.urls import include, path
urlpatterns = [
path('admin/', admin.site.urls), # Admin site
-48
View File
@@ -1,48 +0,0 @@
from decimal import Decimal, ROUND_HALF_UP
import django
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "excel_mimic.settings")
django.setup()
from sheets.models import ExcelEntry
TWO_DEC = Decimal("0.00")
def q2(x):
return Decimal(str(x)).quantize(TWO_DEC, rounding=ROUND_HALF_UP)
fields = ["korrig_druck", "nm3", "lhe", "lhe_ges"]
to_update = []
total = 0
changed_count = 0
qs = ExcelEntry.objects.all().only("id", *fields)
for e in qs.iterator(chunk_size=500):
total += 1
changed = False
for f in fields:
val = getattr(e, f)
if val is not None:
new = q2(val)
if new != val:
setattr(e, f, new)
changed = True
if changed:
to_update.append(e)
changed_count += 1
if len(to_update) >= 500:
ExcelEntry.objects.bulk_update(to_update, fields)
to_update = []
print(f"processed={total}, changed={changed_count}")
if to_update:
ExcelEntry.objects.bulk_update(to_update, fields)
print(f"DONE. processed={total}, changed={changed_count}")
+1 -1
View File
@@ -5,4 +5,4 @@ class SheetsConfig(AppConfig):
name = 'sheets'
def ready(self):
import sheets.signals
pass
+1 -1
View File
@@ -1,5 +1,5 @@
from django import forms
from .models import ExcelEntry, Betriebskosten, Institute
from .models import ExcelEntry, Betriebskosten
+7 -8
View File
@@ -1,7 +1,10 @@
from django.db import models
from django.utils import timezone
from django.core.validators import MinValueValidator, MaxValueValidator
from decimal import Decimal
from django.db.models import Sum
from django.db.models.functions import Coalesce
from django.db.models import DecimalField, Value
class Institute(models.Model):
name = models.CharField(max_length=100)
@@ -73,8 +76,7 @@ class CellReference(models.Model):
class Meta:
unique_together = ['source_cell', 'target_cell']
from decimal import Decimal
from django.db import models
class Betriebskosten(models.Model):
KOSTENTYP_CHOICES = [
@@ -263,10 +265,7 @@ class BetriebskostenSummary(models.Model):
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()
@@ -291,7 +290,7 @@ class BetriebskostenSummary(models.Model):
self.save()
# models.py
from django.db import models
class AbrechnungCell(models.Model):
"""
-5041
View File
File diff suppressed because it is too large Load Diff
-353
View File
@@ -1,353 +0,0 @@
{% extends "base.html" %}
{% block content %}
<div class="spreadsheet-container">
<style>
.spreadsheet-container {
padding: 20px;
}
.sheet-navigation {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
}
.sheet-navigation h2 {
margin: 0;
font-size: 20px;
font-weight: bold;
}
.sheet-navigation a,
.sheet-navigation span {
text-decoration: none;
color: #007bff;
font-weight: bold;
}
.sheet-navigation .disabled-link {
color: #aaa;
cursor: default;
}
.indent {
padding-left: 18px;
font-weight: bold;
text-align: center;
width: 60px;
}
.top-tables {
display: flex;
gap: 20px;
align-items: flex-start;
}
.table-container {
flex: 1;
background-color: #ffffff;
border-radius: 5px;
padding: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.table-container h3 {
margin-top: 0;
margin-bottom: 10px;
font-size: 16px;
}
.sheet-table,
.spreadsheet-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.sheet-table th,
.sheet-table td,
.spreadsheet-table th,
.spreadsheet-table td {
border: 1px solid #dee2e6;
padding: 4px 6px;
}
.sheet-table th,
.spreadsheet-table th {
background-color: #f2f2f2;
font-weight: bold;
text-align: center;
}
.row-label {
background-color: #f9f9f9;
font-weight: bold;
white-space: nowrap;
}
.number-cell {
text-align: right;
white-space: nowrap;
}
.sheet-table tbody tr:nth-child(even),
.spreadsheet-table tbody tr:nth-child(even) {
background-color: #fcfcfc;
}
.sheet-table tbody tr:hover,
.spreadsheet-table tbody tr:hover {
background-color: #f1f3f5;
}
.summary-row {
font-weight: bold;
background-color: #e8f4f8;
}
</style>
<div class="sheet-navigation">
<a href="{% url 'clients_list' %}">&larr; Helium Output Übersicht</a>
<h2>
{% with first=window.0 last=window|last %}
Halbjahres-Bilanz ({{ first.1 }}/{{ first.0 }} {{ last.1 }}/{{ last.0 }})
{% endwith %}
</h2>
{% with first=window.0 %}
<a href="{% url 'monthly_sheet' first.0 first.1 %}">Monatsblätter</a>
{% endwith %}
</div>
<div class="top-tables">
<!-- LEFT TABLE (Top Left AG Vogel / AG Halfm / IKP) -->
<div class="table-container">
<h3>Top Left Halbjahresbilanz</h3>
<table class="sheet-table spreadsheet-table">
<thead>
<tr>
<th>Bezeichnung</th>
{% for c in clients_left %}
<th>{{ c }}</th>
{% endfor %}
<th>&Sigma;</th>
</tr>
</thead>
<tbody>
{% for row in rows_left %}
<tr>
<td>{{ row.label }}</td>
{% for v in row.values %}
<td class="number-cell">
{% if row.is_percent and v is not None %}
{{ v|floatformat:4 }}
{% elif v is not None %}
{{ v|floatformat:2 }}
{% endif %}
</td>
{% endfor %}
<td class="number-cell">
{% if row.is_percent and row.total %}
{{ row.total|floatformat:4 }}
{% elif row.total is not None %}
{{ row.total|floatformat:2 }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- RIGHT TABLE (Top Right Dr. Fohrer / AG Buntk. / etc.) -->
<div class="table-container">
<h3>Top Right Halbjahresbilanz</h3>
<table class="sheet-table spreadsheet-table">
<thead>
<tr>
<th>Bezeichnung</th>
{% for c in clients_right %}
<th>{{ c }}</th>
{% endfor %}
<th>&Sigma;</th>
</tr>
</thead>
<tbody>
{% for row in rows_right %}
<tr>
<td>{{ row.label }}</td>
{% for v in row.values %}
<td class="number-cell">
{% if row.is_text_row %}
{{ v }}
{% elif row.is_percent and v is not None %}
{{ v|floatformat:4 }}
{% elif v is not None %}
{{ v|floatformat:2 }}
{% endif %}
</td>
{% endfor %}
<td class="number-cell">
{% if row.is_text_row %}
{{ row.total }}
{% elif row.is_percent and row.total %}
{{ row.total|floatformat:4 }}
{% elif row.total is not None %}
{{ row.total|floatformat:2 }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="table-container overall-summary-table">
<h3>Summe</h3>
<table class="sheet-table spreadsheet-table">
<thead>
<tr>
<th>Bezeichnung</th>
<th>Σ</th>
<th>Licht-wiese</th>
<th>Chemie</th>
<th>MaWi</th>
<th>M3</th>
</tr>
</thead>
<tbody>
{% for r in rows_sum %}
<tr data-row-index="{{ r.row_index }}">
<tr>
<td>{{ r.label }}</td>
<td class="sum-col sum-cell">
{% if r.is_percent %}
{{ r.total|floatformat:2 }}%
{% else %}
{{ r.total|floatformat:2 }}
{% endif %}
</td>
<td class="sum-col">
{% if r.is_percent %}
{{ r.lichtwiese|floatformat:2 }}%
{% else %}
{{ r.lichtwiese|floatformat:2 }}
{% endif %}
</td>
<td class="sum-col">
{% if r.is_percent %}
{{ r.chemie|floatformat:2 }}%
{% else %}
{{ r.chemie|floatformat:2 }}
{% endif %}
</td>
<td class="sum-col">
{% if r.is_percent %}
{{ r.mawi|floatformat:2 }}%
{% else %}
{{ r.mawi|floatformat:2 }}
{% endif %}
</td>
<td class="sum-col">
{% if r.is_percent %}
{{ r.m3|floatformat:2 }}%
{% else %}
{{ r.m3|floatformat:2 }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="table-container" style="margin-top: 20px;">
<h3>Bottom Table 1 Bilanz (read-only)</h3>
<table class="sheet-table spreadsheet-table">
<thead>
<tr>
<th>Gasspeicher</th>
<th>Volumen</th>
<th>bar</th>
<th>korrigiert</th>
<th>Nm³</th>
<th>Lit. LHe</th>
</tr>
</thead>
<tbody>
{% for r in bottom1_rows %}
<tr class="{% if r.is_total %}summary-row{% endif %}">
<td class="row-label {% if r.indent %}indent{% endif %}">{{ r.label }}</td>
<td class="number-cell">{{ r.volume|floatformat:1 }}</td>
<td class="number-cell">{{ r.bar|floatformat:0 }}</td>
<td class="number-cell">{{ r.korr|floatformat:1 }}</td>
<td class="number-cell">{{ r.nm3|floatformat:0 }}</td>
<td class="number-cell">{{ r.lhe|floatformat:0 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="table-container bottom-table-2">
<h3>Bottom Table 2 Verbraucherbestand L-He (read-only)</h3>
<table class="sheet-table spreadsheet-table">
<tbody>
<!-- Row 38 -->
<tr>
<td class="row-label" colspan="5">Verbraucherbestand</td>
<td class="readonly-cell">{{ bottom2.j38|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom2.k38|floatformat:2 }}</td>
</tr>
<!-- Row 39 -->
<tr>
<td class="row-label">+ Anlage:</td>
<td>Gefäss 2,5:</td>
<td class="readonly-cell">{{ bottom2.g39|floatformat:2 }}</td>
<td>Gefäss 1,0:</td>
<td class="readonly-cell">{{ bottom2.i39|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom2.j39|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom2.k39|floatformat:2 }}</td>
</tr>
<!-- Row 40 -->
<tr>
<td class="row-label">+ Kaltgas:</td>
<td>Gefäss 2,5:</td>
<td class="readonly-cell">{{ bottom2.g40|default_if_none:""|floatformat:2 }}</td>
<td>Gefäss 1,0:</td>
<td class="readonly-cell">{{ bottom2.i40|default_if_none:""|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom2.j40|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom2.k40|floatformat:2 }}</td>
</tr>
<!-- Row 43 -->
<tr>
<td class="row-label" colspan="5">Bestand flüssig He</td>
<td class="readonly-cell">{{ bottom2.j43|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom2.k43|floatformat:2 }}</td>
</tr>
<!-- Row 44 -->
<tr>
<td class="row-label" colspan="5">Gesamtbestand neu:</td>
<td class="readonly-cell">{{ bottom2.j44|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom2.k44|floatformat:2 }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div> {# closes .spreadsheet-container #}
{% endblock %}
-4401
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -10
View File
@@ -1,14 +1,10 @@
# sheets/services/halfyear_calc.py
from __future__ import annotations
from django.db.models.functions import Coalesce
from decimal import Decimal
from typing import Dict, Any
from django.shortcuts import redirect, render
from decimal import Decimal
from sheets.models import (
Client, SecondTableEntry, Institute, ExcelEntry,
Betriebskosten, MonthlySheet, Cell, CellReference, MonthlySummary ,BetriebskostenSummary,AbrechnungCell
)
Client, SecondTableEntry, ExcelEntry,
MonthlySheet, Cell, MonthlySummary)
from django.db.models import Sum
from django.db.models.functions import Coalesce
from django.db.models import DecimalField, Value
@@ -172,7 +168,6 @@ def build_halfyear_window(interval_year: int, start_month: int):
window.append((y, m))
return window
# Import ONLY models + pure helpers here
from sheets.models import MonthlySheet, Cell, Client, SecondTableEntry, BetriebskostenSummary
def sum_right_row_without_duplicates(label: str, clients_right: list[str], values: list):
"""
For specific rows, clients are logically merged, so we must only count one copy.
@@ -268,8 +263,7 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
chosen_sheet_bottom2, bottom2_inputs = pick_bottom2_from_window(window, sheets_by_ym, prev_sheet)
bottom2_g39 = bottom2_inputs["g39"]
bottom2_i39 = bottom2_inputs["i39"]
# ----------------------------
# HALF-YEAR BOTTOM TABLE 1 (Bilanz) - Read only
# ----------------------------
@@ -351,7 +345,6 @@ def compute_halfyear_context(interval_year: int, interval_start: int) -> Dict[st
"nm3": nm3_sum_27_35,
"lhe": lhe_sum_27_35,
})
start_sheet = sheets_by_ym.get((start_year, start_month))
# ------------------------------------------------------------
# Bottom Table 2 (Halbjahres Bilanz) server-side recalcBottom2()
# ------------------------------------------------------------
+1 -1
View File
@@ -146,7 +146,7 @@
<body>
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
&#8678; Go to Clients
&#8678; Zur Startseite
</a>
<h2>Betriebskosten</h2>
+9 -9
View File
@@ -8,7 +8,7 @@
{% csrf_token %}
<h3>Allgemeines 6-Monats-Intervall</h3>
<label>Year:</label>
<label>Jahr:</label>
<select name="year">
{% for y in available_years %}
<option value="{{ y }}" {% if y == interval_year %}selected{% endif %}>
@@ -17,7 +17,7 @@
{% endfor %}
</select>
<label>Start Month:</label>
<label>Startmonat:</label>
<select name="start_month">
<option value="1" {% if interval_start_month == 1 %}selected{% endif %}>01</option>
<option value="2" {% if interval_start_month == 2 %}selected{% endif %}>02</option>
@@ -33,13 +33,13 @@
<option value="12" {% if interval_start_month == 12 %}selected{% endif %}>12</option>
</select>
<button type="submit">Apply Interval</button>
<button type="submit">Intervall anwenden</button>
</form>
<!-- Year Filter -->
<div class="year-filter">
<label for="year-select">Year:</label>
<label for="year-select">Jahr:</label>
<select id="year-select" onchange="window.location.href='?year='+this.value">
{% for year in available_years %}
<option value="{{ year }}" {% if year == current_year %}selected{% endif %}>
@@ -73,7 +73,7 @@
</tr>
{% endfor %}
<tr class="total">
<td><strong>Σ (all clients, selected 6-month interval)</strong></td>
<td><strong>Σ (alle Kunden, ausgewähltes 6-Monats-Intervall)</strong></td>
{% for month in months %}
<td>-</td>
@@ -86,12 +86,12 @@
<!-- Navigation Buttons -->
<div class="navigation-buttons">
<a href="{% url 'table_one' %}" class="nav-button">Go to Helium Input</a>
<a href="{% url 'table_two' %}" class="nav-button">Go to Helium Output</a>
<a href="/admin/" class="nav-button admin-button">Go to Admin Panel</a>
<a href="{% url 'table_one' %}" class="nav-button">Heliumrückgabe</a>
<a href="{% url 'table_two' %}" class="nav-button">Heliumausgabe</a>
<a href="/admin/" class="nav-button admin-button">Admin</a>
<a href="{% url 'betriebskosten_list' %}" class="nav-button">Betriebskosten</a>
<a href="{% url 'monthly_sheet' year=2024 month=1 %}" class="nav-button">Monthly Sheets</a>
<a href="{% url 'monthly_sheet' year=2024 month=1 %}" class="nav-button">Monatsbilanz</a>
<a href="{% url 'halfyear_balance' %}" class="nav-button">Halbjahres Bilanz</a>
<!-- ✅ NEW -->
+92 -38
View File
@@ -4,27 +4,43 @@
<div class="spreadsheet-container">
<!-- Navigation Header -->
<div class="sheet-navigation">
{# Previous month link #}
{% with pm=prev_month %}
{% if pm.year and pm.month %}
<a href="{% url 'monthly_sheet' pm.year pm.month %}">← Previous</a>
{% else %}
<span class="disabled-link">← Previous</span>
{% endif %}
{% endwith %}
<div class="nav-left">
<a href="{% url 'clients_list' %}" class="main-page-link">🏠 Hauptseite</a>
<h2>{{ year }} - {{ month_name }} (Sheet {{ month }}/6)</h2>
{% with pm=prev_month %}
{% if pm.year and pm.month %}
<a href="{% url 'monthly_sheet' pm.year pm.month %}">← Vorherigen Monat</a>
{% else %}
<span class="disabled-link">← Vorherigen Monat</span>
{% endif %}
{% endwith %}
</div>
{# Next month link #}
{% with nm=next_month %}
{% if nm.year and nm.month %}
<a href="{% url 'monthly_sheet' nm.year nm.month %}">Next →</a>
{% else %}
<span class="disabled-link">Next →</span>
{% endif %}
{% endwith %}
<div class="nav-center">
<h2>{{ year }} - {{ month_name }} (Sheet {{ month }}/6)</h2>
</div>
<a href="{% url 'summary_sheet' year month %}">Go to Summary</a>
<div class="nav-right">
{% with nm=next_month %}
{% if nm.year and nm.month %}
<a href="{% url 'monthly_sheet' nm.year nm.month %}">Nächsten Monat →</a>
{% else %}
<span class="disabled-link">Nächsten Monat →</span>
{% endif %}
{% endwith %}
<div class="sheet-jump-box">
<label for="jump-year">Jahr:</label>
<input type="number" id="jump-year" min="2000" max="2100" value="{{ year }}">
<label for="jump-month">Monat:</label>
<input type="number" id="jump-month" min="1" max="12" value="{{ month }}">
<button type="button" id="jump-sheet-btn" class="btn btn-primary">Anwenden</button>
</div>
</div>
</div>
<!-- Top Tables Container -->
@@ -32,11 +48,11 @@
<div class="top-tables">
<!-- Left Table (18 rows × clients) -->
<div class="table-container top-left-table">
<h3>Table 1: Top Left</h3>
<h3>Table 1: Oben Links</h3>
<table class="spreadsheet-table">
<thead>
<tr>
<th>Row Label</th>
<th>Zeilenbezeichnung</th>
{% for header in top_left_headers %}
<th>{{ header }}</th>
{% endfor %}
@@ -129,11 +145,11 @@
<!-- Right Table (24 rows × 6 clients) -->
<!-- Update the top-right table section in monthly_sheet.html -->
<div class="table-container top-right-table">
<h3>Table 2: Top Right</h3>
<h3>Table 2: Oben Rechts</h3>
<table class="spreadsheet-table">
<thead>
<tr>
<th>Row Label</th>
<th>Zeilenbezeichnung</th>
{% for header in top_right_headers %}
<th>{{ header }}</th>
{% endfor %}
@@ -236,11 +252,11 @@
</table>
</div>
<div class="table-container overall-summary-table">
<h3>Gesamtsumme (Top Left + Top Right)</h3>
<h3>Gesamtsumme (Oben Links+Rechts)</h3>
<table class="spreadsheet-table">
<thead>
<tr>
<th>Row Label</th>
<th>Zeilenbezeichnung</th>
<th>Σ</th>
</tr>
</thead>
@@ -264,7 +280,7 @@
<!-- Bottom Tables -->
<div class="bottom-tables">
<div class="table-container bottom-table-1">
<h3>Bottom Table 1 Gasspeicher</h3>
<h3>Untere Tabelle 1 Gasspeicher</h3>
@@ -373,7 +389,7 @@
<div class="table-container bottom-table-2">
<h3>Bottom Table 2 Verbraucherbestand L-He</h3>
<h3>Untere Tabelle 2 Verbraucherbestand L-He</h3>
<table class="spreadsheet-table">
<tbody>
@@ -450,7 +466,7 @@
</div>
<div class="table-container bottom-table-3">
<h3>Bottom Table 3 Bilanz</h3>
<h3>Untere Tabelle 3 Bilanz</h3>
<table class="spreadsheet-table">
<thead>
@@ -621,17 +637,7 @@
</div>
<!-- Save Button -->
<div class="action-bar">
<button id="save-all-btn" class="btn btn-primary">Save All Cells</button>
<div id="save-status"></div>
<div class="legend">
<small>
<span style="background-color: #d1ecf1; padding: 2px 5px;">Saved cells</span>
<span style="background-color: #d4edda; padding: 2px 5px;">Calculated cells</span>
</small>
</div>
</div>
</div>
<!-- Hidden form for cell data -->
@@ -645,16 +651,48 @@
padding: 20px;
}
.nav-left,
.nav-center,
.nav-right {
display: flex;
align-items: center;
gap: 12px;
}
.nav-center h2 {
margin: 0;
}
.main-page-link {
font-weight: bold;
text-decoration: none;
color: #007bff;
}
.sheet-jump-box {
display: flex;
align-items: center;
gap: 8px;
margin-left: 10px;
}
.sheet-jump-box input {
width: 80px;
padding: 6px;
}
.sheet-navigation {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
margin-bottom: 20px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
flex-wrap: wrap;
}
.sheet-navigation h2 {
margin: 0;
}
@@ -821,6 +859,22 @@
<script>
$(document).ready(function() {
$('#jump-sheet-btn').on('click', function() {
const year = parseInt($('#jump-year').val(), 10);
const month = parseInt($('#jump-month').val(), 10);
if (isNaN(year) || isNaN(month)) {
alert('Bitte Jahr und Monat eingeben.');
return;
}
if (month < 1 || month > 12) {
alert('Monat muss zwischen 1 und 12 liegen.');
return;
}
window.location.href = '/sheet/' + year + '/' + month + '/';
});
// ---------- Helpers ----------
+25 -25
View File
@@ -286,21 +286,21 @@
<!-- This link should ONLY wrap the text below -->
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
&#8678; Go to Clients
&#8678; Zur Startseite
</a>
<!-- 6-Month overview card (OUTSIDE any <a>) -->
<div class="overview-card">
<div class="overview-title">Helium Input 6 Month Overview</div>
<div class="overview-title">Heliumrückgabe Überblick über 6 Monate</div>
{% if overview %}
<div class="overview-subtitle">
Period:
Interval
<strong>
{{ overview.start_month }}/{{ overview.start_year }}
{{ overview.end_month }}/{{ overview.end_year }}
</strong>
(selected on the main page)
(ausgewählt am Start Seite)
</div>
<table class="overview-table">
@@ -310,7 +310,7 @@
{% for g in overview.groups %}
<th>{{ g.label }}</th>
{% endfor %}
<th>Month total</th>
<th>Monat total</th>
</tr>
</thead>
<tbody>
@@ -342,7 +342,7 @@
<h2>Helium Input</h2>
<div class="table-container">
<button class="add-row-btn" id="add-row-one">Add Row</button>
<button class="add-row-btn" id="add-row-one">Eingabe hinzufügen</button>
<table id="table-one">
<colgroup>
<col style="width: 3%"> <!-- # -->
@@ -367,7 +367,7 @@
<th>#</th>
<th>ID</th>
<th>Institute</th>
<th>Client</th>
<th>Kunde</th>
<th>Druck</th>
<th>Reinheit</th>
<th>Druckkorrektur</th>
@@ -377,8 +377,8 @@
<th>L-He</th>
<th>L-He zus.</th>
<th>L-He ges.</th>
<th>Date</th>
<th>Month</th>
<th>Datum</th>
<th>Monat</th>
<th>Actions</th>
</tr>
</thead>
@@ -409,8 +409,8 @@
{% endif %}
</td>
<td class="actions">
<button class="edit-btn-one">Edit</button>
<button class="delete-btn-one">Delete</button>
<button class="edit-btn-one">Bearbeiten</button>
<button class="delete-btn-one">Löschen</button>
</td>
</tr>
{% endfor %}
@@ -522,7 +522,7 @@
<!-- Client Selection -->
<label for="edit-client-id">Kunde:</label>
<select id="edit-client-id" disabled>
<option value="">Select Institute first</option>
<option value="">Institut erstmal auswählen</option>
{% for client in clients %}
<option value="{{ client.id }}" data-institute="{{ client.institute.id }}">{{ client.name }}</option>
{% endfor %}
@@ -708,19 +708,19 @@
const instituteId = $('#add-institute-id').val();
if (!instituteId) {
alert('Please select an institute');
alert('Institut erstmal auswählen');
return;
}
if (!clientId) {
alert('Please select a client');
alert('Kunde erstmal auswählen');
return;
}
// Validate date first
let dateInput = $('#add-date').val();
if (!dateInput) {
alert('Please select a date');
alert('Datum erstmal auswählen');
return;
}
@@ -732,17 +732,17 @@
let gesFlaschInhalt = parseFloat($('#add-constant-300').val()) || 300.0;
if (isNaN(pressure) || pressure < 0) {
alert('Please enter a valid pressure value');
alert('Bitte geben Sie einen gültigen Druckwert ein.');
return;
}
if (isNaN(purity) || purity < 0 || purity > 100) {
alert('Please enter a valid purity value (0-100)');
alert('Bitte geben Sie einen gültigen Reinheitswert ein (0-100).');
return;
}
if (isNaN(druckkorrektur) || druckkorrektur < 0) {
alert('Please enter a valid Druckkorrektur value');
alert('Bitte geben Sie einen gültigen Druckkorrekturwert ein');
return;
}
@@ -875,12 +875,12 @@
const instituteId = $('#edit-institute-id').val();
if (!instituteId) {
alert('Please select an institute');
alert('Institut erstmal auswählen');
return;
}
if (!clientId) {
alert('Please select a client');
alert('Kunde erstmal auswählen');
return;
}
@@ -892,17 +892,17 @@
let gesFlaschInhalt = parseFloat($('#edit-constant-300').val()) || 300.0;
if (isNaN(pressure) || pressure < 0) {
alert('Please enter a valid pressure value');
alert('Bitte geben Sie einen gültigen Druckwert ein.');
return;
}
if (isNaN(purity) || purity < 0 || purity > 100) {
alert('Please enter a valid purity value (0-100)');
alert('Bitte geben Sie einen gültigen Reinheitswert ein (0-100).');
return;
}
if (isNaN(druckkorrektur) || druckkorrektur < 0) {
alert('Please enter a valid Druckkorrektur value');
alert('Bitte geben Sie einen gültigen Druckkorrekturwert ein');
return;
}
@@ -937,7 +937,7 @@
// Validate inputs
if (!formData.date) {
alert('Please select a date');
alert('Bitte wählen Sie ein Datum.');
return;
}
@@ -993,7 +993,7 @@
}
},
error: function () {
alert('Failed to delete entry. Please try again.');
alert('Eintrag konnte nicht gelöscht werden. Bitte versuchen Sie es erneut.');
}
});
});
+27 -27
View File
@@ -190,28 +190,28 @@
<div class="d-flex justify-content-start mb-2">
<!-- "Go to Clients" button at top-left -->
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
&#8678; Go to Clients
&#8678; Zur Startseite
</a>
</div>
<h2>LHe Dewar Output</h2>
<div class="table-container">
<button class="add-row-btn" id="add-row-two">Add Output</button>
<button class="add-row-btn" id="add-row-two">Eingabe hinzufügen</button>
<table id="table-two">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th>Institute</th>
<th>Client</th>
<th>Date</th>
<th>Institut</th>
<th>Kunde</th>
<th>Datum</th>
<th>Warm</th>
<th>LHe Delivery</th>
<th>LHe Anlieferung</th>
<th>Vor</th>
<th>Nach</th>
<th>LHe Output</th>
<th>Notes</th>
<th>LHe Ausgabe</th>
<th>Notizen</th>
<th>Actions</th>
</tr>
</thead>
@@ -230,8 +230,8 @@
<td>{% if entry.lhe_output is not None %}{{ entry.lhe_output|floatformat:1 }}{% endif %}</td>
<td>{{ entry.notes }}</td>
<td class="actions">
<button class="edit-btn-two">Edit</button>
<button class="delete-btn-two">Delete</button>
<button class="edit-btn-two">Bearbeiten</button>
<button class="delete-btn-two">Löschen</button>
</td>
</tr>
{% endfor %}
@@ -245,24 +245,24 @@
<h3>LHe Dewar Output</h3>
<!-- Institute Selection -->
<label for="add-institute-id">Institute:</label>
<label for="add-institute-id">Institut:</label>
<select id="add-institute-id">
<option value="">Select Institute</option>
<option value="">Institut auswählen</option>
{% for institute in institutes %}
<option value="{{ institute.id }}">{{ institute.name }}</option>
{% endfor %}
</select>
<!-- Client Selection (will be populated based on institute) -->
<label for="add-client-id">Client:</label>
<label for="add-client-id">Kunde:</label>
<select id="add-client-id" disabled>
<option value="">Select Institute first</option>
<option value="">Institut erstaml auswählen</option>
{% for client in clients %}
<option value="{{ client.id }}" data-institute="{{ client.institute.id }}">{{ client.name }}</option>
{% endfor %}
</select>
<label for="add-date">Date:</label>
<label for="add-date">Datum:</label>
<input type="date" id="add-date">
<!-- Changed from checkbox to number input -->
@@ -273,7 +273,7 @@
<div class="input-with-label">
<label for="add-lhe-delivery">LHe Anlieferung:</label>
<input type="text" id="add-lhe-delivery" placeholder="Enter delivery amount">
<input type="text" id="add-lhe-delivery" placeholder="Wert eingeben">
</div>
<div class="input-with-label">
@@ -291,8 +291,8 @@
<input type="number" id="add-lhe-output" readonly>
</div>
<label for="add-notes">Notes:</label>
<textarea id="add-notes" placeholder="Additional notes"></textarea>
<label for="add-notes">Notizen:</label>
<textarea id="add-notes" placeholder="Notizen"></textarea>
<div class="popup-buttons">
<button class="save-btn" id="save-add-two">Save</button>
@@ -310,16 +310,16 @@
<!-- Institute Selection -->
<label for="edit-institute-id">Institute:</label>
<select id="edit-institute-id">
<option value="">Select Institute</option>
<option value="">Institut Auswälen</option>
{% for institute in institutes %}
<option value="{{ institute.id }}">{{ institute.name }}</option>
{% endfor %}
</select>
<!-- Client Selection (will be populated based on institute) -->
<label for="edit-client-id">Client:</label>
<label for="edit-client-id">Kunde:</label>
<select id="edit-client-id" disabled>
<option value="">Select Institute first</option>
<option value="">Institut erstmal Auswälen</option>
{% for client in clients %}
<option value="{{ client.id }}" data-institute="{{ client.institute.id }}">{{ client.name }}</option>
{% endfor %}
@@ -336,7 +336,7 @@
<div class="input-with-label">
<label for="edit-lhe-delivery">LHe Anlieferung:</label>
<input type="text" id="edit-lhe-delivery" placeholder="Enter delivery amount">
<input type="text" id="edit-lhe-delivery" placeholder="Wert eingeben">
</div>
<div class="input-with-label">
@@ -399,7 +399,7 @@
function filterClients(instituteId, targetSelect, allOptions) {
if (!instituteId) {
// Show only the default option if no institute selected
targetSelect.html('<option value="">Select Institute first</option>');
targetSelect.html('<option value="">erstmal Institut auswählen</option>');
targetSelect.prop('disabled', true);
} else {
// Restore all options first
@@ -412,7 +412,7 @@
targetSelect.find('option').hide();
// Always show the "Select Client" option
targetSelect.find('option[value=""]').show().text('Select Client');
targetSelect.find('option[value=""]').show().text('Kunde Auswählen');
// Show only clients from selected institute
const clientsFromInstitute = targetSelect.find(`option[data-institute="${instituteId}"]`);
@@ -473,7 +473,7 @@
$('#add-row-two').on('click', function () {
// Reset form
$('#add-institute-id').val('');
$('#add-client-id').html('<option value="">Select Institute first</option>');
$('#add-client-id').html('<option value="">Erstaml institut auswählen</option>');
$('#add-client-id').prop('disabled', true);
$('#add-date').val('');
$('#add-is-warm').val('0');
@@ -645,12 +645,12 @@
const instituteId = $('#edit-institute-id').val();
if (!instituteId) {
alert('Please select an institute');
alert('Institut erstmal auswählen');
return;
}
if (!clientId) {
alert('Please select a client');
alert('Kunde erstmal auswählen');
return;
}
-1
View File
@@ -1,3 +1,2 @@
from django.test import TestCase
# Create your tests here.
+2 -2
View File
@@ -27,10 +27,10 @@ urlpatterns = [
path('set-halfyear-interval/', set_halfyear_interval, name='set_halfyear_interval'),
path('halfyear-bilanz/', views.halfyear_balance_view, name='halfyear_balance'),
path('sheet/<int:year>/<int:month>/', MonthlySheetView.as_view(), name='monthly_sheet'),
path('settings/halfyear/', halfyear_settings, name='halfyear_settings'),
path('sheet/<int:year>/<int:month>/', views.MonthlySheetView.as_view(), name='monthly_sheet'),
path('summary/<int:year>/<int:start_month>/', views.SummarySheetView.as_view(), name='summary_sheet'),
path("save-cells/", SaveCellsView.as_view(), name="save_cells"), path("save-cells/", SaveCellsView.as_view(), name="save_cells"),path('save-month-summary/', SaveMonthSummaryView.as_view(), name='save_month_summary'),
path("save-cells/", SaveCellsView.as_view(), name="save_cells"),
path('save-month-summary/', SaveMonthSummaryView.as_view(), name='save_month_summary'),
path('calculate/', views.CalculateView.as_view(), name='calculate'),
+34 -96
View File
@@ -4,24 +4,21 @@ from django.views.decorators.csrf import csrf_exempt
from django.db.models import Sum, Value, DecimalField
from django.http import JsonResponse
from django.db.models import Q
from django.db.models import Count
from decimal import Decimal, ROUND_HALF_UP
from decimal import Decimal, InvalidOperation
from django.apps import apps
from datetime import date, datetime
from decimal import InvalidOperation
from datetime import datetime
from sheets.services.halfyear_calc import compute_halfyear_context
import calendar
from django.utils import timezone
import re
from django.views.generic import TemplateView, View
from .models import (
Client, SecondTableEntry, Institute, ExcelEntry,
Betriebskosten, MonthlySheet, Cell, CellReference, MonthlySummary ,BetriebskostenSummary,AbrechnungCell
Betriebskosten, MonthlySheet, Cell, MonthlySummary ,BetriebskostenSummary,AbrechnungCell
)
from django.db.models import Sum
from django.urls import reverse
from django.db.models.functions import Coalesce
from .forms import BetriebskostenForm
from django.utils.dateparse import parse_date
from django.contrib.auth.mixins import LoginRequiredMixin
import json
FIRST_SHEET_YEAR = 2025
FIRST_SHEET_MONTH = 1
@@ -368,13 +365,7 @@ def get_bestand_kannen_for_month(sheet, client_name: str) -> Decimal:
"""
return get_top_left_value(sheet, client_name, row_index=BESTAND_KANNEN_ROW_INDEX)
from decimal import Decimal
from django.db.models import Sum
from django.db.models.functions import Coalesce
from django.db.models import DecimalField, Value
from .models import MonthlySheet, SecondTableEntry, Client, Cell
from django.shortcuts import redirect, render
# You already have HALFYEAR_CLIENTS for the left table (AG Vogel, AG Halfm, IKP)
HALFYEAR_CLIENTS = ["AG Vogel", "AG Halfm", "IKP"]
@@ -463,7 +454,7 @@ def get_top_left_value(sheet, client_name: str, row_index: int) -> Decimal:
return Decimal('0')
def get_group_clients(group_key):
"""Return queryset of clients that belong to a logical group."""
from .models import Client # local import to avoid circulars
group = CLIENT_GROUPS.get(group_key)
if not group:
@@ -473,9 +464,7 @@ def get_group_clients(group_key):
def calculate_summation(sheet, table_type, row_index, sum_column_index):
"""Calculate summation for a row, with special handling for % row"""
from decimal import Decimal
from .models import Cell
try:
# Special case: top_left, % row (Excel B20 -> row_index 19)
if table_type == 'top_left' and row_index == 19:
@@ -533,9 +522,7 @@ def evaluate_formula(formula, values_dict):
Safely evaluate a formula like "10 + 9" where numbers are row indices
values_dict: {row_index: decimal_value}
"""
from decimal import Decimal
import re
try:
# Create a copy of the formula to work with
expr = formula
@@ -568,9 +555,7 @@ class MonthlySheetView(TemplateView):
def populate_helium_input_to_top_right(self, sheet):
"""Populate bezug data from SecondTableEntry to top-right table (row 8 = Excel row 12)"""
from .models import SecondTableEntry, Cell, Client
from django.db.models.functions import Coalesce
from decimal import Decimal
year = sheet.year
month = sheet.month
@@ -632,10 +617,7 @@ class MonthlySheetView(TemplateView):
return True
def calculate_bezug_from_entries(self, sheet, year, month):
"""Calculate B11 (Bezug) from SecondTableEntry for all clients - ONLY for non-start sheets"""
from .models import SecondTableEntry, Cell, Client
from django.db.models import Sum
from django.db.models.functions import Coalesce
from decimal import Decimal
# Check if this is the start sheet
if year == 2025 and month == 1:
@@ -664,7 +646,7 @@ class MonthlySheetView(TemplateView):
b11_cell.save()
# Also trigger dependent calculations
from .views import SaveCellsView
save_view = SaveCellsView()
save_view.calculate_top_left_dependents(sheet, b11_cell)
# In MonthlySheetView.get_context_data() method, update the TOP_RIGHT_CLIENTS and row count:
@@ -673,7 +655,7 @@ class MonthlySheetView(TemplateView):
return True
def get_context_data(self, **kwargs):
from decimal import Decimal
context = super().get_context_data(**kwargs)
year = self.kwargs.get('year', datetime.now().year)
month = self.kwargs.get('month', datetime.now().month)
@@ -702,9 +684,7 @@ class MonthlySheetView(TemplateView):
# Recalculate dependents once when opening the sheet
# ----------------------------------------------------
from .views import SaveCellsView
from .models import Cell
save_view = SaveCellsView()
# ---- TOP LEFT ----
@@ -768,8 +748,7 @@ class MonthlySheetView(TemplateView):
# Update row counts in build_group_rows function
def build_group_rows(sheet, table_type, client_names):
"""Build rows for display in monthly sheet."""
from decimal import Decimal
from .models import Cell
MERGED_ROWS = {2, 3, 5, 6, 7, 9, 10, 12, 14, 15}
MERGED_PAIRS = [
("Dr. Fohrer", "AG Buntk."),
@@ -1085,7 +1064,7 @@ class MonthlySheetView(TemplateView):
prev_year = year
prev_month = month - 1
from .models import MonthlySheet, Cell, Client
prev_sheet = MonthlySheet.objects.filter(
year=prev_year,
@@ -1146,8 +1125,7 @@ class MonthlySheetView(TemplateView):
- AG Alff + AG Gutfl. share the SAME value (from previous month's AG Alff or AG Gutfl.)
M3 clients just copy their own value.
"""
from .models import MonthlySheet, Cell, Client
from decimal import Decimal
# Do nothing on first sheet
if year == FIRST_SHEET_YEAR and month == FIRST_SHEET_MONTH:
@@ -1284,7 +1262,7 @@ def get_factor_value(table_type, row_index):
def recalculate_stand_der_gaszahler(self, sheet):
"""Recalculate Stand der Gaszähler for all client pairs"""
from decimal import Decimal
# For Dr. Fohrer and AG Buntk. (L & M columns)
try:
@@ -1456,7 +1434,7 @@ class AbrechnungView(TemplateView):
EDITABLE_ROW_KEYS = {"ghe_bezug", "betrag", "gutschriften"}
def get_context_data(self, **kwargs):
from sheets.services.halfyear_calc import compute_halfyear_context
context = super().get_context_data(**kwargs)
interval_year = self.request.session.get("halfyear_year")
@@ -1496,22 +1474,6 @@ class AbrechnungView(TemplateView):
umlage_personal_total = d(bs.umlage_personal) if bs else Decimal("0")
# ---- helper: sum lhe_output in the 6-month window for a list of client names ----
def sum_output_for_clients_exact(client_names):
total = Decimal("0")
if not client_names:
return total
for (y, m) in window:
total += SecondTableEntry.objects.filter(
client__name__in=client_names,
date__year=y,
date__month=m,
).aggregate(
total=Coalesce(Sum("lhe_output"), Value(0, output_field=DecimalField()))
)["total"] or Decimal("0")
return total
# (NOTE: the date filter above is “wide”; if you prefer exact (y,m) matching, use the loop version below)
def sum_output_for_clients_exact(client_names):
total = Decimal("0")
if not client_names:
@@ -1920,13 +1882,7 @@ class AbrechnungView(TemplateView):
GROUP_IJKL = ["orgchem_thiele", "phychem_m3_buntkow", "orgchem_fohrer", "mawi_m3_gutfl", "mawi_alff"]
GROUP_NOP = ["chemie", "mawi", "physik"]
GROUP_STADT = ["pkm_vogel", "iap_halfmann", "ikp"]
def nop_cols_for_row(row_key: str):
# For gutschriften, NOP should sum the 5 IJKL client columns
if row_key == "gutschriften":
return GROUP_IJKL
# otherwise NOP is the 3 summary columns (Chemie/MaWi/Physik)
return GROUP_NOP
def val(row_key, col_key):
"""Get the final numeric value for a cell (prefer computed, fallback to stored)."""
if (row_key, col_key) in computed:
@@ -2104,7 +2060,7 @@ class RechnungView(TemplateView):
# 3) Heliumverluste (l) from Halbjahresbilanz total Verbraucherverluste
# (sum all clients left+right)
# ------------------------------------------------------------
from sheets.services.halfyear_calc import compute_halfyear_context
half_ctx = compute_halfyear_context(interval_year, interval_start)
heliumverluste_l = Decimal("0")
@@ -2223,14 +2179,7 @@ class SaveAbrechnungCellsView(View):
)
return JsonResponse({"ok": True})
def build_halfyear_window(interval_year: int, interval_start_month: int):
window = []
for offset in range(6):
total_index = (interval_start_month - 1) + offset
y = interval_year + (total_index // 12)
m = (total_index % 12) + 1
window.append((y, m))
return window
class SaveCellsView(View):
def calculate_bottom_3_dependents(self, sheet):
@@ -2296,7 +2245,7 @@ class SaveCellsView(View):
cell = get_cell(r, c)
return dec(cell.value if cell else None)
f47 = get_val(1, 0)
g47 = get_val(1, 1)
i47 = get_val(1, 2)
i50 = get_val(4, 2)
@@ -2410,7 +2359,6 @@ class SaveCellsView(View):
# If conversion fails, treat as empty
new_value = None
old_value = cell.value
cell.value = new_value
cell.save()
@@ -2486,10 +2434,7 @@ class SaveCellsView(View):
14: Verbraucherverluste (Liter L-He) - calculated
15: % - calculated
"""
from decimal import Decimal
from django.db.models import Sum, Count
from django.db.models.functions import Coalesce
from .models import Client, Cell, ExcelEntry, SecondTableEntry
TOP_RIGHT_CLIENTS = [
"Dr. Fohrer", # L
@@ -2820,9 +2765,7 @@ class SaveCellsView(View):
- Nm³ (col 3): SUM Nm³ rows 08
- Lit. LHe (col 4): SUM Lit. LHe rows 08
"""
from decimal import Decimal, InvalidOperation
from .models import Cell
updated_cells = []
DATA_ROWS = list(range(0, 9)) # 08
@@ -2961,9 +2904,7 @@ class SaveCellsView(View):
def calculate_top_left_dependents(self, sheet, changed_cell):
"""Calculate dependent cells in top_left table"""
from decimal import Decimal
from django.db.models import Sum
from django.db.models.functions import Coalesce
client_id = changed_cell.client_id
updated_cells = []
@@ -2986,7 +2927,7 @@ class SaveCellsView(View):
if cell and cell.value is not None:
try:
return Decimal(str(cell.value))
except:
except Exception:
return Decimal('0')
return Decimal('0')
@@ -3041,13 +2982,13 @@ class SaveCellsView(View):
})
# 4. B11 = Sum of LHe Output from SecondTableEntry for this client/month - Bezug
from .models import SecondTableEntry
client = changed_cell.client
# Calculate total LHe output for this client in this month
# 4. B11 = Bezug (Liter L-He)
# For start sheet: manual entry, for other sheets: auto-calculated from SecondTableEntry
from .models import SecondTableEntry
client = changed_cell.client
b11_cell = cell_dict.get(8) # row_index 8 = Excel B11 (UI row 9)
@@ -3258,7 +3199,7 @@ class SaveCellsView(View):
def recalculate_top_left_table(self, sheet, client_id):
"""Recalculate the top-left table for a specific client"""
from decimal import Decimal
# Get all cells for this client in top_left table
cells = Cell.objects.filter(
@@ -3429,7 +3370,7 @@ class SummarySheetView(TemplateView):
year = int(self.kwargs.get('year', datetime.now().year))
# Get 6 monthly sheets
months = [(year, m) for m in range(start_month, start_month + 6)]
sheets = MonthlySheet.objects.filter(
year=year,
month__in=list(range(start_month, start_month + 6))
@@ -3605,7 +3546,7 @@ def set_halfyear_interval(request):
return redirect('clients_list')
# Table One View (ExcelEntry)
def table_one_view(request):
from .models import ExcelEntry, Client, Institute
# --- Base queryset for the main Helium Input table ---
base_entries = ExcelEntry.objects.all().select_related('client', 'client__institute')
@@ -4129,9 +4070,7 @@ def betriebskosten_list(request):
summary = get_summary()
summary.recalculate()
from decimal import Decimal
from django.db.models import Sum, DecimalField, Value
from django.db.models.functions import Coalesce
interval_year = request.session.get("halfyear_year")
interval_start = request.session.get("halfyear_start_month")
@@ -4287,7 +4226,7 @@ def get_summary():
summary, created = BetriebskostenSummary.objects.get_or_create(id=1)
return summary
from django.http import JsonResponse
def update_personalkosten(request):
if request.method == "POST":
@@ -4345,8 +4284,7 @@ class CheckSheetView(View):
return JsonResponse(result)
class TestFormulaView(View):
def get(self, request):
# Test the formula evaluation directly