Merge branch 'main' into container
This commit is contained in:
BIN
Binary file not shown.
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -5,4 +5,4 @@ class SheetsConfig(AppConfig):
|
||||
name = 'sheets'
|
||||
|
||||
def ready(self):
|
||||
import sheets.signals
|
||||
pass
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
from django import forms
|
||||
from .models import ExcelEntry, Betriebskosten, Institute
|
||||
from .models import ExcelEntry, Betriebskosten
|
||||
|
||||
|
||||
|
||||
|
||||
+7
-8
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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' %}">← 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>Σ</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>Σ</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
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
<body>
|
||||
|
||||
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
|
||||
⇦ Go to Clients
|
||||
⇦ Zur Startseite
|
||||
</a>
|
||||
|
||||
<h2>Betriebskosten</h2>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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 ----------
|
||||
|
||||
|
||||
@@ -286,21 +286,21 @@
|
||||
|
||||
<!-- This link should ONLY wrap the text below -->
|
||||
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
|
||||
⇦ Go to Clients
|
||||
⇦ 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.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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">
|
||||
⇦ Go to Clients
|
||||
⇦ 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,3 +1,2 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
+2
-2
@@ -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
@@ -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 0–8
|
||||
- Lit. LHe (col 4): SUM Lit. LHe rows 0–8
|
||||
"""
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from .models import Cell
|
||||
|
||||
|
||||
updated_cells = []
|
||||
|
||||
DATA_ROWS = list(range(0, 9)) # 0–8
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user