This commit is contained in:
2026-02-17 14:59:55 +01:00
parent 5424d25822
commit e1da4fa041
27 changed files with 17914 additions and 3919 deletions
@@ -0,0 +1,42 @@
# Generated by Django 5.1.4 on 2026-02-15 06:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0013_monthlysummary'),
]
operations = [
migrations.DeleteModel(
name='RowCalculation',
),
migrations.AddField(
model_name='betriebskosten',
name='gegenstand',
field=models.CharField(default=1, max_length=200, verbose_name='Gegenstand'),
preserve_default=False,
),
migrations.AlterField(
model_name='betriebskosten',
name='buchungsdatum',
field=models.DateField(verbose_name='Zahlungsdatum'),
),
migrations.AlterField(
model_name='betriebskosten',
name='gas_volume',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Gasvolumen (m³)'),
),
migrations.AlterField(
model_name='betriebskosten',
name='kostentyp',
field=models.CharField(choices=[('sach', 'Sach'), ('helium', 'Helium')], max_length=10, verbose_name='Kostentyp'),
),
migrations.AlterField(
model_name='betriebskosten',
name='rechnungsnummer',
field=models.CharField(max_length=50, verbose_name='Firma'),
),
]
@@ -0,0 +1,24 @@
# Generated by Django 5.1.4 on 2026-02-15 07:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0014_delete_rowcalculation_betriebskosten_gegenstand_and_more'),
]
operations = [
migrations.CreateModel(
name='BetriebskostenSummary',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('personalkosten', models.DecimalField(decimal_places=2, default=0, max_digits=12)),
('instandhaltung', models.DecimalField(decimal_places=2, default=0, max_digits=12)),
('heliumkosten', models.DecimalField(decimal_places=2, default=0, max_digits=12)),
('bezugskosten_gashe', models.DecimalField(decimal_places=4, default=0, max_digits=12)),
('umlage_personal', models.DecimalField(decimal_places=2, default=0, max_digits=12)),
],
),
]
+27
View File
@@ -0,0 +1,27 @@
# Generated by Django 5.1.4 on 2026-02-15 11:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0015_betriebskostensummary'),
]
operations = [
migrations.CreateModel(
name='AbrechnungCell',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('interval_year', models.IntegerField()),
('interval_start_month', models.IntegerField()),
('row_key', models.CharField(max_length=60)),
('col_key', models.CharField(max_length=60)),
('value', models.DecimalField(blank=True, decimal_places=6, max_digits=18, null=True)),
],
options={
'unique_together': {('interval_year', 'interval_start_month', 'row_key', 'col_key')},
},
),
]
@@ -0,0 +1,17 @@
# Generated by Django 5.1.5 on 2026-02-16 10:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sheets', '0016_abrechnungcell'),
]
operations = [
migrations.AlterModelOptions(
name='secondtableentry',
options={'ordering': ['date', 'id']},
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.1.5 on 2026-02-17 08:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0017_alter_secondtableentry_options'),
]
operations = [
migrations.AddField(
model_name='secondtableentry',
name='nach',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True),
),
migrations.AddField(
model_name='secondtableentry',
name='vor',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=12, null=True),
),
]
+92 -32
View File
@@ -73,47 +73,45 @@ 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 = [
('sach', 'Sachkosten'),
('ln2', 'LN2'),
('sach', 'Sach'),
('helium', 'Helium'),
('inv', 'Inventar'),
]
buchungsdatum = models.DateField('Buchungsdatum')
rechnungsnummer = models.CharField('Rechnungsnummer', max_length=50)
gegenstand = models.CharField("Gegenstand", max_length=200)
buchungsdatum = models.DateField('Zahlungsdatum')
rechnungsnummer = models.CharField('Firma', max_length=50)
kostentyp = models.CharField('Kostentyp', max_length=10, choices=KOSTENTYP_CHOICES)
gas_volume = models.DecimalField('Gasvolumen (Liter)', max_digits=10, decimal_places=2, null=True, blank=True)
# IMPORTANT: now this field stores m³ (not liters)
gas_volume = models.DecimalField('Gasvolumen (m³)', max_digits=10, decimal_places=2, null=True, blank=True)
betrag = models.DecimalField('Betrag (€)', max_digits=10, decimal_places=2)
beschreibung = models.TextField('Beschreibung', blank=True)
@property
def price_per_liter(self):
def gas_volume_liter(self):
# Liter = m³ / 0.75
if self.kostentyp == 'helium' and self.gas_volume:
return self.gas_volume / Decimal("0.75")
return None
@property
def price_per_m3(self):
if self.kostentyp == 'helium' and self.gas_volume:
return self.betrag / self.gas_volume
return None
def __str__(self):
return f"{self.buchungsdatum} - {self.get_kostentyp_display()} - {self.betrag}"
class RowCalculation(models.Model):
"""Define calculations for specific rows"""
table_type = models.CharField(max_length=20, choices=[
('top_left', 'Top Left Table'),
('top_right', 'Top Right Table'),
('bottom_1', 'Bottom Table 1'),
('bottom_2', 'Bottom Table 2'),
('bottom_3', 'Bottom Table 3'),
])
row_index = models.IntegerField() # Which row has the formula
formula = models.TextField() # e.g., "row_10 + row_9"
description = models.CharField(max_length=200, blank=True)
class Meta:
unique_together = ['table_type', 'row_index']
def __str__(self):
return f"{self.table_type}[{self.row_index}]: {self.formula}"
@property
def price_per_liter(self):
# Preis(Liter) = betrag / liter
liters = self.gas_volume_liter
if self.kostentyp == 'helium' and liters:
return self.betrag / liters
return None
# Or simpler: Just store row calculations in a JSONField
class TableConfig(models.Model):
@@ -201,6 +199,8 @@ class SecondTableEntry(models.Model):
date = models.DateField(default=timezone.now)
is_warm = models.BooleanField(default=False)
lhe_delivery = models.CharField(max_length=100, blank=True, null=True)
vor = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
nach = models.DecimalField(max_digits=12, decimal_places=2, null=True, blank=True)
lhe_output = models.DecimalField(
max_digits=10,
decimal_places=2,
@@ -210,7 +210,8 @@ class SecondTableEntry(models.Model):
)
notes = models.TextField(blank=True, null=True)
date_joined = models.DateField(auto_now_add=True)
class Meta:
ordering = ["date", "id"]
def __str__(self):
return f"{self.client.name} - {self.date}"
@@ -250,4 +251,63 @@ class MonthlySummary(models.Model):
)
def __str__(self):
return f"Summary {self.sheet.year}-{self.sheet.month:02d}"
return f"Summary {self.sheet.year}-{self.sheet.month:02d}"
class BetriebskostenSummary(models.Model):
personalkosten = models.DecimalField(max_digits=12, decimal_places=2, default=0)
instandhaltung = models.DecimalField(max_digits=12, decimal_places=2, default=0)
heliumkosten = models.DecimalField(max_digits=12, decimal_places=2, default=0)
bezugskosten_gashe = models.DecimalField(max_digits=12, decimal_places=4, default=0)
umlage_personal = models.DecimalField(max_digits=12, decimal_places=2, default=0)
def recalculate(self):
from django.db.models import Sum
from django.db.models.functions import Coalesce
from django.db.models import DecimalField, Value
from .models import Betriebskosten
items = Betriebskosten.objects.all()
sach_sum = items.filter(kostentyp='sach').aggregate(
total=Coalesce(Sum('betrag'), Value(0, output_field=DecimalField()))
)['total'] or Decimal('0')
helium_sum = items.filter(kostentyp='helium').aggregate(
total=Coalesce(Sum('betrag'), Value(0, output_field=DecimalField()))
)['total'] or Decimal('0')
helium_m3_sum = items.filter(kostentyp='helium').aggregate(
total=Coalesce(Sum('gas_volume'), Value(0, output_field=DecimalField()))
)['total'] or Decimal('0')
bezug = (helium_sum / helium_m3_sum) if helium_m3_sum not in (None, 0, Decimal('0')) else Decimal('0')
self.instandhaltung = sach_sum
self.heliumkosten = helium_sum
self.bezugskosten_gashe = bezug
self.umlage_personal = self.personalkosten / 2
self.save()
# models.py
from django.db import models
class AbrechnungCell(models.Model):
"""
Storage for the 'Abrechnung' page where columns are custom (not 1:1 with Client).
Values are saved by: (interval_year, interval_start_month, row_key, col_key).
"""
interval_year = models.IntegerField()
interval_start_month = models.IntegerField() # first month of the 6-month window
row_key = models.CharField(max_length=60)
col_key = models.CharField(max_length=60)
value = models.DecimalField(max_digits=18, decimal_places=6, null=True, blank=True)
class Meta:
unique_together = ("interval_year", "interval_start_month", "row_key", "col_key")
def __str__(self):
return f"Abrechnung {self.interval_year}/{self.interval_start_month} {self.row_key}:{self.col_key}={self.value}"
+5041
View File
File diff suppressed because it is too large Load Diff
+353
View File
@@ -0,0 +1,353 @@
{% 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
View File
File diff suppressed because it is too large Load Diff
+123
View File
@@ -0,0 +1,123 @@
{% 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:#f8f9fa; border-radius:5px;
}
.sheet-navigation h2 { margin:0; font-size:20px; font-weight:bold; }
.sheet-navigation a { text-decoration:none; color:#007bff; font-weight:bold; }
.unit-cell {
text-align: center;
font-weight: 600;
background: #f9f9f9;
}
.table-container {
background:#fff; border-radius:5px; padding:10px;
box-shadow:0 2px 4px rgba(0,0,0,0.05);
}
.sheet-table { width:100%; border-collapse:collapse; font-size:14px; }
.sheet-table th, .sheet-table td { border:1px solid #dee2e6; padding:4px 6px; }
.sheet-table th { background:#f2f2f2; font-weight:bold; text-align:center; }
.row-label { background:#f9f9f9; font-weight:bold; white-space:nowrap; }
.number-cell { text-align:right; white-space:nowrap; }
.sheet-table tbody tr:nth-child(even) { background:#fcfcfc; }
.sheet-table tbody tr:hover { background:#f1f3f5; }
.cell-input {
width: 100%;
box-sizing: border-box;
text-align: right;
border: 1px solid #ced4da;
border-radius: 3px;
padding: 2px 4px;
font-size: 14px;
background: #fff;
}
.sheet-table {
table-layout: fixed;
width: auto;
}
.cell-input {
width: 80px;
box-sizing: border-box;
}
.sheet-table th,
.sheet-table td {
padding: 4px 6px;
font-size: 13px;
}
.actions-bar { margin-top: 12px; display:flex; gap:10px; align-items:center; }
.btn {
padding: 6px 10px; border:1px solid #ced4da; border-radius:4px;
background:#fff; cursor:pointer; font-weight:600;
}
</style>
<div class="sheet-navigation">
<a href="{% url 'clients_list' %}">&larr; Übersicht</a>
<h2>Rechnung</h2>
<a href="{% url 'abrechnung' %}">Abrechnung</a>
</div>
{% if needs_interval %}
<div class="table-container">
<p style="margin:0;">Bitte zuerst ein 6-Monats-Intervall auf der Übersichtsseite auswählen.</p>
</div>
{% else %}
<div class="table-container">
<table class="sheet-table spreadsheet-table">
<thead>
<tr>
<th>Arbeitsgruppe</th>
<th>Tabellenkürzel</th>
<th>Name</th>
<th>Heliumverluste (€)</th>
<th>Heliumverluste (l)</th>
<th>Verbr. u. Repar. (€)</th>
<th>Gesamtverflüssigung (l)</th>
<th>Preis (€/l)</th>
<th>Bezogene Menge (l)</th>
<th>Verflüssigungskosten (€)</th>
<th>Verlustkosten (€)</th>
<th>Gaskosten (€)</th>
<th>Gutschriften (€)</th>
<th>Personalkosten (€)</th>
<th>Anteil Personalkosten (€)</th>
<th>Gesamtkosten (€)</th>
</tr>
</thead>
<tbody>
{% for r in rows %}
<tr>
<td>{{ r.arbeitsgruppe }}</td>
<td>{{ r.tabellenkuerzel }}</td>
<td>{{ r.name }}</td>
<td>{{ r.heliumverluste_eur|floatformat:2 }}</td>
<td>{{ r.heliumverluste_l|floatformat:2 }}</td>
<td>{{ r.verbr_u_repar_eur|floatformat:2 }}</td>
<td>{{ r.gesamtverfluessigung_l|floatformat:2 }}</td>
<td>{{ r.preis_eur_l|floatformat:4 }}</td>
<td>{{ r.bezogene_menge_l|floatformat:2 }}</td>
<td>{{ r.verfluessigungskosten_eur|floatformat:2 }}</td>
<td>{{ r.verlustkosten_eur|floatformat:2 }}</td>
<td>{{ r.gaskosten_eur|floatformat:2 }}</td>
<td>{{ r.gutschriften_eur|floatformat:2 }}</td>
<td>{{ r.personalkosten_eur|floatformat:2 }}</td>
<td>{{ r.anteil_personal_eur|floatformat:2 }}</td>
<td>{{ r.gesamtkosten_eur|floatformat:2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endblock %}
+370
View File
@@ -0,0 +1,370 @@
{% extends "base.html" %}
{% load dict_extras %}
{% 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:#f8f9fa; border-radius:5px;
}
.sheet-navigation h2 { margin:0; font-size:20px; font-weight:bold; }
.sheet-navigation a { text-decoration:none; color:#007bff; font-weight:bold; }
.unit-cell {
text-align: center;
font-weight: 600;
background: #f9f9f9;
}
.table-container {
background:#fff; border-radius:5px; padding:10px;
box-shadow:0 2px 4px rgba(0,0,0,0.05);
}
.sheet-table { width:100%; border-collapse:collapse; font-size:14px; }
.sheet-table th, .sheet-table td { border:1px solid #dee2e6; padding:4px 6px; }
.sheet-table th { background:#f2f2f2; font-weight:bold; text-align:center; }
.row-label { background:#f9f9f9; font-weight:bold; white-space:nowrap; }
.number-cell { text-align:right; white-space:nowrap; }
.sheet-table tbody tr:nth-child(even) { background:#fcfcfc; }
.sheet-table tbody tr:hover { background:#f1f3f5; }
.cell-input {
width: 100%;
box-sizing: border-box;
text-align: right;
border: 1px solid #ced4da;
border-radius: 3px;
padding: 2px 4px;
font-size: 14px;
background: #fff;
}
.sheet-table {
table-layout: fixed;
width: auto;
}
.cell-input {
width: 80px;
box-sizing: border-box;
}
.sheet-table th,
.sheet-table td {
padding: 4px 6px;
font-size: 13px;
}
.actions-bar { margin-top: 12px; display:flex; gap:10px; align-items:center; }
.btn {
padding: 6px 10px; border:1px solid #ced4da; border-radius:4px;
background:#fff; cursor:pointer; font-weight:600;
}
</style>
<div class="sheet-navigation">
<a href="{% url 'clients_list' %}">&larr; Übersicht</a>
<h2>Abrechnung</h2>
<a href="{% url 'halfyear_balance' %}">Halbjahres-Bilanz</a>
</div>
<div id="autosaveStatus" style="margin:8px 0; font-size: 14px;"></div>
{% if needs_interval %}
<div class="table-container">
<p>Bitte zuerst ein Halbjahr auswählen (auf der Übersicht-Seite), damit der Zeitraum bekannt ist.</p>
</div>
{% else %}
<div class="table-container">
<div style="margin-bottom:10px; font-weight:600;">
Aufstellung Heliumverbrauch für Zeitraum: <b>{{ interval_text }}</b>
</div>
<div class="abrechnung-row" style="display:flex; gap:24px; align-items:flex-start; overflow-x:auto;">
<div class="abrechnung-left" style="flex: 0 0 auto;">
<table class="sheet-table">
<thead>
<tr>
<th style="width:180px;">&larr; Fachgebiet &rarr;</th>
{% for col_key, col_label in columns %}
<th style="width:90px;">{{ col_label }}</th>
{% endfor %}
<th style="width:70px;">Einheit</th> <!-- 👈 ADD THIS -->
</tr>
</thead>
<tbody>
{% for r in rows %}
<tr>
<td class="row-label">{{ r.label }}</td>
{% for c in r.cells %}
<td class="number-cell">
{% if r.row_key == "sonstiges" %}
<span class="sonstiges-text" data-col-key="{{ c.col_key }}">
{{ sonstiges_text|get_item:c.col_key }}
</span>
{% else %}
{% if r.editable %}
<input
class="cell-input editable"
type="text"
value="{% if c.value is not None %}{{ c.value|floatformat }}{% endif %}"
data-row-key="{{ r.row_key }}"
data-col-key="{{ c.col_key }}"
/>
{% else %}
<span class="cell-value"
data-row-key="{{ r.row_key }}"
data-col-key="{{ c.col_key }}">
{% if c.value is not None %}{{ c.value|floatformat:2 }}{% endif %}
</span>
{% endif %}
{% endif %}
</td>
{% endfor %}
<td class="unit-cell">{{ r.unit }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> <!-- end abrechnung-left -->
<div class="abrechnung-right" style="flex: 0 0 auto;">
<table class="sheet-table spreadsheet-table">
<thead>
<tr>
<th>Summe Lichtwiese<br>IJKL</th>
<th style="min-width:180px; text-align:center;">
&larr; Fachgebiet &rarr;
</th>
<th>Summe Lichtwiese<br>NOP (Check)</th>
<th>Summe<br>Stadtmitte</th>
<th>Summe<br>(Check)</th>
</tr>
</thead>
<tbody>
{% for r in right_summary_rows %}
<tr>
<td>
{% if r.is_text %}{{ r.ijkl }}{% else %}{{ r.ijkl|floatformat:2 }}{% endif %}
</td>
<td class="row-label">
{{ r.label }}
{% if r.unit %}
<span style="float:right; opacity:0.7;">{{ r.unit }}</span>
{% endif %}
</td>
<td>
{% if r.is_text %}{{ r.nop }}{% else %}{{ r.nop|floatformat:2 }}{% endif %}
</td>
<td>
{% if r.is_text %}{{ r.stadt }}{% else %}{{ r.stadt|floatformat:2 }}{% endif %}
</td>
<td>
{% if r.is_text %}{{ r.check }}{% else %}{{ r.check|floatformat:2 }}{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div> <!-- end abrechnung-row -->
</div>
<script>
(function () {
// ===============================
// Helpers
// ===============================
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(";").shift();
return "";
}
function parseNum(val) {
if (val == null) return 0;
let s = String(val).trim();
if (!s) return 0;
s = s.replace(/\s+/g, "");
const hasComma = s.includes(",");
const hasDot = s.includes(".");
if (hasComma && hasDot) {
// German: 1.234,56 -> 1234.56
s = s.replace(/\./g, "").replace(",", ".");
} else if (hasComma && !hasDot) {
// 35,08 -> 35.08
s = s.replace(",", ".");
} else {
// 35.08 stays 35.08
}
const n = parseFloat(s);
return isNaN(n) ? 0 : n;
}
function fmt2(n) {
return (Math.round(n * 100) / 100).toFixed(2);
}
function getInputValue(rowKey, colKey) {
const inp = document.querySelector(
`.cell-input[data-row-key="${rowKey}"][data-col-key="${colKey}"]`
);
return inp ? inp.value : "";
}
function getCellSpan(rowKey, colKey) {
return document.querySelector(
`.cell-value[data-row-key="${rowKey}"][data-col-key="${colKey}"]`
);
}
function setDisplay(rowKey, colKey, value) {
const el = getCellSpan(rowKey, colKey);
if (el) el.textContent = fmt2(value);
}
function getDisplayNum(rowKey, colKey) {
const el = getCellSpan(rowKey, colKey);
if (!el) return 0;
return parseNum(el.textContent);
}
function getSonstigesCell(colKey) {
return document.querySelector(
`.sonstiges-text[data-col-key="${colKey}"]`
);
}
function setSonstiges(colKey, betragVal) {
const el = getSonstigesCell(colKey);
if (!el) return;
let txt = "--------------";
if (betragVal < 0) txt = "Gutschrift";
else if (betragVal > 0) txt = "Nachzahlung";
el.textContent = txt;
}
// ===============================
// Config
// ===============================
const SAVE_URL = "{% url 'abrechnung_autosave' %}";
const CSRF = getCookie("csrftoken");
// from backend: context["bezugskosten_gashe"] = bezugskosten_gashe
const BEZUGSKOSTEN_GASHE = parseNum("{{ bezugskosten_gashe|floatformat:6 }}");
// ===============================
// Autosave (debounced)
// ===============================
let debounceTimer = null;
let pending = {}; // "row|col" -> {row_key,col_key,value}
function queueSave(rowKey, colKey, value) {
pending[rowKey + "|" + colKey] = { row_key: rowKey, col_key: colKey, value: value };
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(flushPending, 400);
}
async function flushPending() {
const changes = Object.values(pending);
pending = {};
if (!changes.length) return;
try {
const resp = await fetch(SAVE_URL, {
method: "POST",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": CSRF,
},
body: JSON.stringify({ changes })
});
const text = await resp.text();
if (!resp.ok) {
console.error("Autosave failed:", resp.status, text);
}
} catch (e) {
console.error("Autosave error:", e);
}
}
// ===============================
// Live Recalculation (per column)
// ===============================
function recalcColumn(colKey) {
// Editable inputs
const ghe = parseNum(getInputValue("ghe_bezug", colKey));
const betrag = parseNum(getInputValue("betrag", colKey));
const gutschriften = parseNum(getInputValue("gutschriften", colKey));
// Usually computed display
const personal = getDisplayNum("umlage_personal_5", colKey);
// Computed displays already present from server
const sum14 = getDisplayNum("summe_anteile_1_4", colKey);
const heVerbrauch = getDisplayNum("he_verbrauch", colKey);
// 4-Kosten He-Gas-Bezug = GHe-Bezug * Bezugskosten-GasHe
const kosten4 = ghe * BEZUGSKOSTEN_GASHE;
setDisplay("kosten_he_gas_bezug_4", colKey, kosten4);
// Rechnungsbetrag = Summe Anteile 1-4 Gutschriften + Betrag + 5-Umlage Personal
const rechnungsbetrag = sum14 - gutschriften + betrag + personal;
setDisplay("rechnungsbetrag", colKey, rechnungsbetrag);
// eff. L-He-Preis = Rechnungsbetrag / He-Verbrauch
const eff = heVerbrauch !== 0 ? (rechnungsbetrag / heVerbrauch) : 0;
setDisplay("eff_lhe_preis", colKey, eff);
// Sonstiges = IF(Betrag=0,"--------------",IF(Betrag<0,"Gutschrift","Nachzahlung"))
setSonstiges(colKey, betrag);
}
// ===============================
// Event listener
// ===============================
document.addEventListener("input", function (e) {
const t = e.target;
if (!t.classList.contains("cell-input")) return;
if (!t.classList.contains("editable")) return;
const rowKey = t.dataset.rowKey;
const colKey = t.dataset.colKey;
if (!rowKey || !colKey) return;
// autosave this edited cell
queueSave(rowKey, colKey, t.value);
// live recompute dependent rows for this column
if (rowKey === "ghe_bezug" || rowKey === "betrag" || rowKey === "gutschriften") {
recalcColumn(colKey);
}
});
})();
</script>
{% endif %}
</div>
{% endblock %}
+234 -99
View File
@@ -35,15 +35,19 @@
text-align: center;
border-bottom: 1px solid #ddd;
}
th:nth-child(1), td:nth-child(1) { width: 5%; } /* # column */
th:nth-child(2), td:nth-child(2) { width: 10%; } /* Buchungsdatum */
th:nth-child(3), td:nth-child(3) { width: 15%; } /* Rechnungsnummer */
th:nth-child(4), td:nth-child(4) { width: 10%; } /* Kostentyp */
th:nth-child(5), td:nth-child(5) { width: 10%; } /* Gasvolumen */
th:nth-child(6), td:nth-child(6) { width: 10%; } /* Betrag */
th:nth-child(7), td:nth-child(7) { width: 25%; } /* Beschreibung */
th:nth-child(8), td:nth-child(8) { width: 15%; } /* Actions */
th:nth-child(1), td:nth-child(1) { width: 4%; }
th:nth-child(2), td:nth-child(2) { width: 12%; }
th:nth-child(3), td:nth-child(3) { width: 8%; }
th:nth-child(4), td:nth-child(4) { width: 12%; }
th:nth-child(5), td:nth-child(5) { width: 8%; }
th:nth-child(6), td:nth-child(6) { width: 8%; }
th:nth-child(7), td:nth-child(7) { width: 8%; }
th:nth-child(8), td:nth-child(8) { width: 8%; }
th:nth-child(9), td:nth-child(9) { width: 8%; }
th:nth-child(10), td:nth-child(10) { width: 8%; }
th:nth-child(11), td:nth-child(11) { width: 12%; }
th:nth-child(12), td:nth-child(12) { width: 8%; }
.actions {
white-space: nowrap; /* Prevent buttons from wrapping */
}
@@ -147,74 +151,126 @@
<h2>Betriebskosten</h2>
<div class="table-container">
<button class="add-row-btn" id="add-row-btn">Add Row</button>
<table id="betriebskosten-table">
<thead>
<tr>
<th>#</th>
<th>Buchungsdatum</th>
<th>Rechnungsnummer</th>
<th>Kostentyp</th>
<th>Gasvolumen (L)</th>
<th>Betrag (€)</th>
<th>Beschreibung</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr data-id="{{ item.id }}">
<td>{{ forloop.counter }}</td>
<td>{{ item.buchungsdatum|date:"Y-m-d" }}</td>
<td>{{ item.rechnungsnummer }}</td>
<td>{{ item.get_kostentyp_display }}</td>
<td>{{ item.gas_volume|default_if_none:"-" }}</td>
<td>{{ item.betrag }}</td>
<td>{{ item.beschreibung|default:"" }}</td>
<td class="actions">
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" style="text-align: center;">No entries found</td>
</tr>
{% endfor %}
</tbody>
<div style="margin-bottom:20px;">
<label><strong>Personalkosten:</strong></label>
<input id="personalkosten"
type="number"
step="0.01"
value="{{ summary.personalkosten }}"
style="width:200px;">
</div>
<table style="margin-bottom:20px;">
<tr>
<td>Instandhaltung</td>
<td>{{ summary.instandhaltung|floatformat:2 }} €</td>
</tr>
<tr>
<td>Verflüssigungskosten L-He</td>
<td>{{ verfluessigungskosten_lhe|floatformat:6 }} €/L-He</td>
</tr>
<tr>
<td>Heliumkosten</td>
<td>{{ summary.heliumkosten|floatformat:2 }}</td>
</tr>
<tr>
<td>Bezugskosten -GasHe</td>
<td>{{ summary.bezugskosten_gashe|floatformat:2 }} €/m³</td>
</tr>
<tr>
<td>Umlage Personal</td>
<td id="umlage-personal">{{ summary.umlage_personal|floatformat:2 }} €</td>
</tr>
</table>
<button class="add-row-btn" id="add-row-btn">Add Row</button>
<table id="betriebskosten-table">
<thead>
<tr>
<th>#</th>
<th>Gegenstand</th>
<th>Zahlungsdatum</th>
<th>Firma</th>
<th>Kostentyp</th>
<th>Gasvolumen (m³)</th>
<th>Gasvolumen (Liter)</th>
<th>Preis pro m³ (€)</th>
<th>Preis pro Liter (€)</th>
<th>Betrag (€)</th>
<th>Beschreibung</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr data-id="{{ item.id }}">
<td>{{ forloop.counter }}</td>
<td>{{ item.gegenstand }}</td>
<td>{{ item.buchungsdatum|date:"Y-m-d" }}</td>
<td>{{ item.rechnungsnummer }}</td>
<td>{{ item.get_kostentyp_display }}</td>
<td>{{ item.gas_volume|default_if_none:"-" }}</td>
<td>{% if item.gas_volume %}{{ item.gas_volume_liter|floatformat:2 }}{% else %}-{% endif %}</td>
<td>{% if item.price_per_m3 %}{{ item.price_per_m3|floatformat:2 }}{% else %}-{% endif %}</td>
<td>{% if item.price_per_liter %}{{ item.price_per_liter|floatformat:2 }}{% else %}-{% endif %}</td>
<td>{{ item.betrag }}</td>
<td>{{ item.beschreibung|default:"" }}</td>
<td class="actions">
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
</td>
</tr>
{% empty %}
<tr>
<td colspan="12" style="text-align:center; padding:20px;">
No entries found
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Add Popup -->
<div id="add-popup" class="popup">
<span class="close-btn">&times;</span>
<h3>Betriebskosten Eintrag</h3>
<label for="add-gegenstand">Gegenstand:</label>
<input type="text" id="add-gegenstand" required>
<label for="add-buchungsdatum">Buchungsdatum:</label>
<label for="add-buchungsdatum">Zahlungsdatum:</label>
<input type="date" id="add-buchungsdatum" required>
<label for="add-rechnungsnummer">Rechnungsnummer:</label>
<label for="add-rechnungsnummer">Firma:</label>
<input type="text" id="add-rechnungsnummer" required>
<label for="add-kostentyp">Kostentyp:</label>
<select id="add-kostentyp" required onchange="toggleGasVolume('add')">
<option value="">Bitte auswählen</option>
<option value="sach">Sachkosten</option>
<option value="ln2">LN2</option>
<option value="helium">Helium</option>
<option value="inv">Inventar</option>
</select>
<option value="" selected disabled>Bitte auswählen</option>
<option value="sach">Sach</option>
<option value="helium">Helium</option></select>
<div id="add-gas-volume-group" style="display: none;">
<label for="add-gas-volume">Gasvolumen (Liter):</label>
<input type="number" id="add-gas-volume" step="0.01" min="0" oninput="calculatePrice('add')">
<label for="add-gas-volume">Gasvolumen ():</label>
<input type="number" id="add-gas-volume" step="0.01" min="0" oninput="calculateHelium('add')">
<label for="add-gas-volume-liter">Gasvolumen (Liter):</label>
<input type="text" id="add-gas-volume-liter" readonly>
</div>
<label for="add-betrag">Betrag (€):</label>
<input type="number" id="add-betrag" step="0.01" min="0" required oninput="calculatePrice('add')">
<input type="number" id="add-betrag" step="0.01" min="0" required oninput="calculateHelium('add')">
<div id="add-price-per-liter-group" style="display: none;">
<label for="add-price-per-liter">Preis pro Liter ():</label>
<label for="add-price-per-m3">Preis pro Liter (m³) (€) (auto):</label>
<input type="text" id="add-price-per-m3" readonly>
<label for="add-price-per-liter">Preis (Liter) (€):</label>
<input type="text" id="add-price-per-liter" readonly>
</div>
@@ -233,32 +289,38 @@
<span class="close-btn">&times;</span>
<h3>Betriebskosten Eintrag</h3>
<input type="hidden" id="edit-id">
<label for="edit-gegenstand">Gegenstand:</label>
<input type="text" id="edit-gegenstand" required>
<label for="edit-buchungsdatum">Buchungsdatum:</label>
<label for="edit-buchungsdatum">Zahlungsdatum:</label>
<input type="date" id="edit-buchungsdatum" required>
<label for="edit-rechnungsnummer">Rechnungsnummer:</label>
<label for="edit-rechnungsnummer">Firma:</label>
<input type="text" id="edit-rechnungsnummer" required>
<label for="edit-kostentyp">Kostentyp:</label>
<select id="edit-kostentyp" required onchange="toggleGasVolume('edit')">
<option value="">Bitte auswählen</option>
<option value="sach">Sachkosten</option>
<option value="ln2">LN2</option>
<option value="helium">Helium</option>
<option value="inv">Inventar</option>
</select>
<option value="" selected disabled>Bitte auswählen</option>
<option value="sach">Sach</option>
<option value="helium">Helium</option></select>
<div id="edit-gas-volume-group" style="display: none;">
<label for="edit-gas-volume">Gasvolumen (Liter):</label>
<input type="number" id="edit-gas-volume" step="0.01" min="0" oninput="calculatePrice('edit')">
<label for="edit-gas-volume">Gasvolumen ():</label>
<input type="number" id="edit-gas-volume" step="0.01" min="0" oninput="calculateHelium('edit')">
<label for="edit-gas-volume-liter">Gasvolumen (Liter) (auto):</label>
<input type="text" id="edit-gas-volume-liter" readonly>
</div>
<label for="edit-betrag">Betrag (€):</label>
<input type="number" id="edit-betrag" step="0.01" min="0" required oninput="calculatePrice('edit')">
<input type="number" id="edit-betrag" step="0.01" min="0" required oninput="calculateHelium('edit')">
<div id="edit-price-per-liter-group" style="display: none;">
<label for="edit-price-per-liter">Preis pro Liter ():</label>
<label for="edit-price-per-m3">Preis pro Liter (m³) (€) (auto):</label>
<input type="text" id="edit-price-per-m3" readonly>
<label for="edit-price-per-liter">Preis (Liter) (€) (auto):</label>
<input type="text" id="edit-price-per-liter" readonly>
</div>
@@ -276,6 +338,11 @@
$(document).ready(function () {
// Open add popup
$('#add-row-btn').on('click', function () {
// Reset fields so the placeholder "Gegenstand" is shown
$('#add-popup input, #add-popup textarea').val('');
$('#add-kostentyp').val('');
toggleGasVolume('add');
calculateHelium('add');
$('#add-popup').fadeIn();
});
@@ -287,6 +354,7 @@
// Add new entry
$('#save-add').on('click', function() {
const formData = {
'gegenstand': $('#add-gegenstand').val(),
'buchungsdatum': $('#add-buchungsdatum').val(),
'rechnungsnummer': $('#add-rechnungsnummer').val(),
'kostentyp': $('#add-kostentyp').val(),
@@ -297,7 +365,8 @@
};
// Validate required fields
if (!formData.buchungsdatum || !formData.rechnungsnummer || !formData.kostentyp || !formData.betrag) {
if (!formData.gegenstand || !formData.buchungsdatum || !formData.rechnungsnummer || !formData.kostentyp || !formData.betrag) {
alert('Bitte füllen Sie alle erforderlichen Felder aus');
return;
}
@@ -312,10 +381,14 @@
const newRow = `
<tr data-id="${response.id}">
<td>${$('#betriebskosten-table tbody tr').length + 1}</td>
<td>${response.gegenstand || ''}</td>
<td>${response.buchungsdatum}</td>
<td>${response.rechnungsnummer}</td>
<td>${response.kostentyp_display}</td>
<td>${response.gas_volume || '-'}</td>
<td>${response.gas_volume_m3 || '-'}</td>
<td>${response.gas_volume_liter || '-'}</td>
<td>${response.price_per_m3 || '-'}</td>
<td>${response.price_per_liter || '-'}</td>
<td>${response.betrag}</td>
<td>${response.beschreibung || ''}</td>
<td class="actions">
@@ -323,7 +396,7 @@
<button class="delete-btn">Delete</button>
</td>
</tr>
`;
`;
$('#betriebskosten-table tbody').append(newRow);
$('#add-popup').fadeOut();
$('#add-popup input, #add-popup select, #add-popup textarea').val('');
@@ -344,26 +417,28 @@
// Fill the edit form with current data
$('#edit-id').val(id);
$('#edit-buchungsdatum').val(row.find('td:eq(1)').text());
$('#edit-rechnungsnummer').val(row.find('td:eq(2)').text());
$('#edit-gegenstand').val(row.find('td:eq(1)').text());
$('#edit-buchungsdatum').val(row.find('td:eq(2)').text());
$('#edit-rechnungsnummer').val(row.find('td:eq(3)').text());
const kostentypText = row.find('td:eq(4)').text();
// Set kostentyp based on display text
const kostentypText = row.find('td:eq(3)').text();
const kostentypMap = {
'Sach': 'sach',
'Sachkosten': 'sach',
'LN2': 'ln2',
'Helium': 'helium',
'Inventar': 'inv'
'Helium': 'helium'
};
$('#edit-kostentyp').val(kostentypMap[kostentypText] || '');
$('#edit-gas-volume').val(row.find('td:eq(4)').text() === '-' ? '' : row.find('td:eq(4)').text());
$('#edit-betrag').val(row.find('td:eq(5)').text());
$('#edit-beschreibung').val(row.find('td:eq(6)').text());
$('#edit-gas-volume').val(row.find('td:eq(5)').text() === '-' ? '' : row.find('td:eq(5)').text());
$('#edit-betrag').val(row.find('td:eq(6)').text());
$('#edit-beschreibung').val(row.find('td:eq(7)').text());
// Show/hide gas volume based on kostentyp
toggleGasVolume('edit');
calculatePrice('edit');
calculateHelium('edit');
$('#edit-popup').fadeIn();
});
@@ -372,6 +447,7 @@
$('#save-edit').on('click', function() {
const formData = {
'id': $('#edit-id').val(),
'gegenstand': $('#edit-gegenstand').val(),
'buchungsdatum': $('#edit-buchungsdatum').val(),
'rechnungsnummer': $('#edit-rechnungsnummer').val(),
'kostentyp': $('#edit-kostentyp').val(),
@@ -395,14 +471,21 @@
if (response.status === 'success') {
// Update the row in the table
const row = $(`tr[data-id="${response.id}"]`);
row.find('td:eq(1)').text(response.buchungsdatum);
row.find('td:eq(2)').text(response.rechnungsnummer);
row.find('td:eq(3)').text(response.kostentyp_display);
row.find('td:eq(4)').text(response.gas_volume || '-');
row.find('td:eq(5)').text(response.betrag);
row.find('td:eq(6)').text(response.beschreibung || '');
row.find('td:eq(1)').text(response.gegenstand || '');
row.find('td:eq(2)').text(response.buchungsdatum);
row.find('td:eq(3)').text(response.rechnungsnummer);
row.find('td:eq(4)').text(response.kostentyp_display);
row.find('td:eq(5)').text(response.gas_volume_m3 || '-');
row.find('td:eq(6)').text(response.gas_volume_liter || '-');
row.find('td:eq(7)').text(response.price_per_m3 || '-');
row.find('td:eq(8)').text(response.price_per_liter || '-');
row.find('td:eq(9)').text(response.betrag);
row.find('td:eq(10)').text(response.beschreibung || '');
$('#edit-popup').fadeOut();
$('#betriebskosten-table tbody tr').each(function(index) {
$(this).find('td:first').text(index + 1);
});
} else {
alert('Error: ' + (response.message || 'Failed to update entry'));
}
@@ -417,6 +500,10 @@
$(document).on('click', '.delete-btn', function () {
const row = $(this).closest('tr');
const id = row.data('id');
if (!id) {
alert("Invalid entry ID. Cannot delete this row.");
return;
}
if (!confirm('Are you sure you want to delete this entry?')) return;
@@ -445,35 +532,83 @@
});
});
// Toggle gas volume field based on kostentyp
// Toggle helium-only fields based on Kostentyp
function toggleGasVolume(type) {
const kostentyp = $(`#${type}-kostentyp`).val();
const gasVolumeGroup = $(`#${type}-gas-volume-group`);
const pricePerLiterGroup = $(`#${type}-price-per-liter-group`);
const priceGroup = $(`#${type}-price-per-liter-group`);
if (kostentyp === 'helium') {
gasVolumeGroup.show();
pricePerLiterGroup.show();
priceGroup.show();
calculateHelium(type);
} else {
gasVolumeGroup.hide();
pricePerLiterGroup.hide();
priceGroup.hide();
// reset helium-only fields
$(`#${type}-gas-volume`).val('');
$(`#${type}-gas-volume-liter`).val('');
$(`#${type}-price-per-m3`).val('');
$(`#${type}-price-per-liter`).val('');
}
}
// Calculate price per liter
function calculatePrice(type) {
// Helium calculations (all auto/read-only fields)
// Liter = m³ / 0.75
// Preis/m³ = Betrag / m³
// Preis/Liter = Betrag / Liter
function calculateHelium(type) {
const kostentyp = $(`#${type}-kostentyp`).val();
const volume = parseFloat($(`#${type}-gas-volume`).val()) || 0;
const m3 = parseFloat($(`#${type}-gas-volume`).val()) || 0;
const betrag = parseFloat($(`#${type}-betrag`).val()) || 0;
if (kostentyp === 'helium' && volume > 0 && betrag > 0) {
$(`#${type}-price-per-liter`).val((betrag / volume).toFixed(2) + ' €/L');
if (kostentyp !== 'helium') return;
if (m3 > 0) {
const liters = m3 / 0.75;
$(`#${type}-gas-volume-liter`).val(liters.toFixed(2));
if (betrag > 0) {
const priceM3 = betrag / m3;
const priceLiter = betrag / liters;
$(`#${type}-price-per-m3`).val(priceM3.toFixed(2));
$(`#${type}-price-per-liter`).val(priceLiter.toFixed(2));
} else {
$(`#${type}-price-per-m3`).val('');
$(`#${type}-price-per-liter`).val('');
}
} else {
$(`#${type}-gas-volume-liter`).val('');
$(`#${type}-price-per-m3`).val('');
$(`#${type}-price-per-liter`).val('');
}
}
$('#personalkosten').on('change', function () {
$.ajax({
url: "{% url 'update_personalkosten' %}",
method: "POST",
data: {
personalkosten: $(this).val(),
csrfmiddlewaretoken: "{{ csrf_token }}"
},
success: function(response) {
if (response.status === "success") {
$('#umlage-personal').text(
parseFloat(response.umlage_personal).toFixed(2) + " €"
);
} else {
alert(response.message || "Failed to save Personalkosten");
}
},
error: function(xhr) {
alert(xhr.responseJSON?.message || "Server error while saving Personalkosten");
}
});
});
</script>
</body>
</html>
+23 -7
View File
@@ -72,18 +72,33 @@
<td class="total">{{ data.year_total|floatformat:2 }}</td>
</tr>
{% endfor %}
<tr class="total">
<td><strong>Σ (all clients, selected 6-month interval)</strong></td>
{% for month in months %}
<td>-</td>
{% endfor %}
<td class="total"><strong>{{ interval_total_output_lhe|floatformat:2 }}</strong></td>
</tr>
</tbody>
</table>
<!-- Navigation Buttons -->
<div class="navigation-buttons">
<a href="{% url 'table_one' %}" class="nav-button">Go to Helium Input</a>
<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 'betriebskosten_list' %}" class="nav-button">Betriebskosten</a>
<a href="{% url 'monthly_sheet' year=2024 month=1 %}">Monthly Sheets</a>
<a href="{% url 'halfyear_balance' %}">Halbjahres Bilanz</a>
<a href="{% url 'monthly_sheet' year=2024 month=1 %}" class="nav-button">Monthly Sheets</a>
<a href="{% url 'halfyear_balance' %}" class="nav-button">Halbjahres Bilanz</a>
<!-- ✅ NEW -->
<a href="{% url 'abrechnung' %}" class="nav-button">Abrechnung</a>
<a href="{% url 'rechnung' %}" class="nav-button">Rechnung</a>
</div>
</div>
<style>
@@ -145,10 +160,11 @@
}
.navigation-buttons {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
display: flex;
justify-content: center;
gap: 12px;
margin-top: 30px;
flex-wrap: wrap; /* ✅ allow wrapping */
}
.nav-button {
+159 -5
View File
@@ -117,9 +117,13 @@
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 %}
{% if window and window.0 and window.0.0 and window.0.1 %}
{% with first=window.0 %}
<a href="{% url 'monthly_sheet' first.0 first.1 %}">Monatsblätter</a>
{% endwith %}
{% else %}
<a href="{% url 'monthly_sheet' interval_year interval_start_month %}">Monatsblätter</a>
{% endif %}
</div>
<div class="top-tables">
@@ -204,7 +208,7 @@
</tbody>
</table>
</div>
<div class="table-container">
<div class="table-container overall-summary-table">
<h3>Summe</h3>
<table class="sheet-table spreadsheet-table">
<thead>
@@ -219,9 +223,10 @@
</thead>
<tbody>
{% for r in rows_sum %}
<tr data-row-index="{{ r.row_index }}">
<tr>
<td>{{ r.label }}</td>
<td class="sum-col">
<td class="sum-col sum-cell">
{% if r.is_percent %}
{{ r.total|floatformat:2 }}%
{% else %}
@@ -293,6 +298,155 @@
{% 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 class="table-container bottom-table-3" style="margin-top: 20px;">
<h3>Bottom Table 3 Bilanz (read-only)</h3>
<table class="sheet-table spreadsheet-table">
<thead>
<tr>
<th>Bezeichnung</th>
<th>F</th>
<th>G</th>
<th>I</th>
<th>Nm³ (J)</th>
<th>Lit. L-He (K)</th>
</tr>
</thead>
<tbody>
<!-- Row 46 -->
<tr>
<td class="row-label">Gesamtbestand Vormonat</td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell">{{ bottom3.j46|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.k46|floatformat:2 }}</td>
</tr>
<!-- Row 47 -->
<tr>
<td class="row-label">+ Verbrauch / Anlage</td>
<td class="readonly-cell"></td>
<td class="readonly-cell">{{ bottom3.g47|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.i47|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.j47|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.k47|floatformat:2 }}</td>
</tr>
<!-- Row 48 -->
<tr>
<td class="row-label">Gesamtbestand inkl. Anlage</td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell">{{ bottom3.j48|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.k48|floatformat:2 }}</td>
</tr>
<!-- Row 49 -->
<tr>
<td class="row-label">Gesamtbestand (akt. Monat)</td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell">{{ bottom3.j49|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.k49|floatformat:2 }}</td>
</tr>
<!-- Row 50 -->
<tr>
<td class="row-label">Verbrauch Sonstiges</td>
<td class="readonly-cell"></td>
<td class="readonly-cell">{{ bottom3.g50|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.i50|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.j50|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.k50|floatformat:2 }}</td>
</tr>
<!-- Row 51 -->
<tr>
<td class="row-label">Differenz Bestand</td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell">{{ bottom3.j51|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.k51|floatformat:2 }}</td>
</tr>
<!-- Row 52 -->
<tr>
<td class="row-label">Verbraucherverluste (aus Übersicht)</td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell">{{ bottom3.j52|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.k52|floatformat:2 }}</td>
</tr>
<!-- Row 53 -->
<tr>
<td class="row-label">Differenz Bilanz</td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell"></td>
<td class="readonly-cell">{{ bottom3.j53|floatformat:2 }}</td>
<td class="readonly-cell">{{ bottom3.k53|floatformat:2 }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
+24 -22
View File
@@ -391,13 +391,13 @@
<td>{{ entry.client.name }}</td>
<td>{{ entry.pressure|floatformat:2 }}</td>
<td>{{ entry.purity|floatformat:2 }}</td>
<td>{{ entry.druckkorrektur|floatformat:3 }}</td>
<td>{{ entry.constant_300|floatformat:3 }}</td>
<td>{{ entry.korrig_druck|floatformat:6 }}</td>
<td>{{ entry.nm3|floatformat:6 }}</td>
<td>{{ entry.lhe|floatformat:6 }}</td>
<td>{{ entry.lhe_zus|floatformat:3 }}</td>
<td>{{ entry.lhe_ges|floatformat:6 }}</td>
<td>{{ entry.druckkorrektur|floatformat:2 }}</td>
<td>{{ entry.constant_300|floatformat:2 }}</td>
<td>{{ entry.korrig_druck|floatformat:2 }}</td>
<td>{{ entry.nm3|floatformat:2 }}</td>
<td>{{ entry.lhe|floatformat:2 }}</td>
<td>{{ entry.lhe_zus|floatformat:2 }}</td>
<td>{{ entry.lhe_ges|floatformat:2 }}</td>
<td>
{% if entry.date %}
{{ entry.date|date:"d.m.Y" }}
@@ -468,8 +468,9 @@
<!-- Auto-calculated Fields with Formulas -->
<div class="input-with-label">
<label for="add-constant-300">ges. Flasch. Inhalt:</label>
<input type="number" id="add-constant-300" value="300.000" step="0.001" readonly class="readonly-field">
<input type="number" id="add-constant-300" step="0.001" min="0">
</div>
<div class="formula-display">Editable value</div>
<div class="formula-display">Formula: Fixed value (300)</div>
<div class="input-with-label">
@@ -554,7 +555,7 @@
<!-- Auto-calculated Fields with Formulas -->
<div class="input-with-label">
<label for="edit-constant-300">ges. Flasch. Inhalt:</label>
<input type="number" id="edit-constant-300" value="300.000" step="0.001" readonly class="readonly-field">
<input type="number" id="edit-constant-300" step="0.001" min="0">
</div>
<div class="formula-display">Formula: Fixed value (300)</div>
@@ -640,19 +641,19 @@
// Calculate Korrig. Druck
const korrigDruck = pressure * druckkorrektur / ((pressure * druckkorrektur) / 2000 + 1);
$(`#${prefix}-korrig-druck`).val(korrigDruck.toFixed(6));
// Calculate Nm³ - Updated to divide by 1000
$(`#${prefix}-korrig-druck`).val(korrigDruck.toFixed(2));
// Calculate Nm³
const nm3 = (gesFlaschInhalt * korrigDruck) / 1000;
$(`#${prefix}-nm3`).val(nm3.toFixed(6));
$(`#${prefix}-nm3`).val(nm3.toFixed(2));
// Calculate L-He
const lhe = nm3 * (1 - purity/100) / 0.75;
$(`#${prefix}-lhe`).val(lhe.toFixed(6));
$(`#${prefix}-lhe`).val(lhe.toFixed(2));
// Calculate L-He ges.
const lheGes = lheZus + lhe;
$(`#${prefix}-lhe-ges`).val(lheGes.toFixed(6));
$(`#${prefix}-lhe-ges`).val(lheGes.toFixed(2));
}
// Institute change handler for add popup
@@ -668,12 +669,13 @@
});
// Add event listeners for calculation triggers in add popup
$('#add-pressure, #add-purity, #add-druckkorrektur, #add-lhe-zus').on('input change', function() {
$('#add-pressure, #add-purity, #add-druckkorrektur, #add-lhe-zus, #add-constant-300')
.on('input change', function() {
calculateDerivedValues('add');
});
// Add event listeners for calculation triggers in edit popup
$('#edit-pressure, #edit-purity, #edit-druckkorrektur, #edit-lhe-zus').on('input change', function() {
$('#edit-pressure, #edit-purity, #edit-druckkorrektur, #edit-lhe-zus, #edit-constant-300')
.on('input change', function() {
calculateDerivedValues('edit');
});
@@ -684,10 +686,10 @@
$('#add-client-id').prop('disabled', true);
$('#add-date').val('');
$('#add-pressure').val('');
$('#add-purity').val('');
$('#add-purity').val('0.1');
$('#add-druckkorrektur').val('1.0');
$('#add-lhe-zus').val('0.0');
$('#add-constant-300').val('300.000');
$('#add-constant-300').val('604.00');
$('#add-korrig-druck').val('');
$('#add-nm3').val('');
$('#add-lhe').val('');
+129 -54
View File
@@ -208,6 +208,8 @@
<th>Date</th>
<th>Warm</th>
<th>LHe Delivery</th>
<th>Vor</th>
<th>Nach</th>
<th>LHe Output</th>
<th>Notes</th>
<th>Actions</th>
@@ -223,7 +225,9 @@
<td>{{ entry.date }}</td>
<td>{{ entry.is_warm|yesno:"Yes,No" }}</td>
<td>{{ entry.lhe_delivery }}</td>
<td>{{ entry.lhe_output }}</td>
<td>{% if entry.vor is not None %}{{ entry.vor|floatformat:1 }}{% endif %}</td>
<td>{% if entry.nach is not None %}{{ entry.nach|floatformat:1 }}{% endif %}</td>
<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>
@@ -272,9 +276,19 @@
<input type="text" id="add-lhe-delivery" placeholder="Enter delivery amount">
</div>
<div class="input-with-label">
<label for="add-vor">Vor:</label>
<input type="number" id="add-vor" step="0.1">
</div>
<div class="input-with-label">
<label for="add-nach">Nach:</label>
<input type="number" id="add-nach" step="0.1">
</div>
<div class="input-with-label">
<label for="add-lhe-output">LHe Ausgabe:</label>
<input type="number" id="add-lhe-output" placeholder="Enter output amount (numbers only)" step="0.01" min="0">
<input type="number" id="add-lhe-output" readonly>
</div>
<label for="add-notes">Notes:</label>
@@ -326,9 +340,20 @@
</div>
<div class="input-with-label">
<label for="edit-lhe-output">LHe Ausgabe:</label>
<input type="number" id="edit-lhe-output" placeholder="Enter output amount">
<label for="edit-vor">Vor:</label>
<input type="number" id="edit-vor" step="0.1">
</div>
<div class="input-with-label">
<label for="edit-nach">Nach:</label>
<input type="number" id="edit-nach" step="0.1">
</div>
<div class="input-with-label">
<label for="edit-lhe-output">LHe Ausgabe:</label>
<input type="number" id="edit-lhe-output" readonly>
</div>
<label for="edit-notes">Notes:</label>
<textarea id="edit-notes" placeholder="Additional notes"></textarea>
@@ -346,7 +371,30 @@
// Store all client options for both dropdowns
const allClientOptions = $('#add-client-id').html();
const allEditClientOptions = $('#edit-client-id').html();
function calculateAddOutput() {
let vor = parseFloat($('#add-vor').val()) || 0;
let nach = parseFloat($('#add-nach').val()) || 0;
let output = nach - vor;
if (output < 0) output = 0;
$('#add-lhe-output').val(output.toFixed(1));
}
$('#add-vor, #add-nach').on('input', calculateAddOutput);
function calculateEditOutput() {
let vor = parseFloat($('#edit-vor').val()) || 0;
let nach = parseFloat($('#edit-nach').val()) || 0;
let output = nach - vor;
if (output < 0) output = 0;
$('#edit-lhe-output').val(output.toFixed(1));
}
$('#edit-vor, #edit-nach').on('input', calculateEditOutput);
// Function to filter clients based on institute selection
function filterClients(instituteId, targetSelect, allOptions) {
if (!instituteId) {
@@ -469,15 +517,21 @@
}
let formData = {
'client_id': clientId,
'date': $('#add-date').val(),
'is_warm': warmValue,
'lhe_delivery': $('#add-lhe-delivery').val(),
'lhe_output': $('#add-lhe-output').val(),
'notes': $('#add-notes').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
'client_id': clientId,
'date': $('#add-date').val(),
'is_warm': warmValue,
'lhe_delivery': $('#add-lhe-delivery').val(),
// ✅ NEW: send vor/nach to backend
'vor': $('#add-vor').val(),
'nach': $('#add-nach').val(),
// ❌ DO NOT send lhe_output anymore (backend should compute it)
// 'lhe_output': $('#add-lhe-output').val(),
'notes': $('#add-notes').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
// Validate date format
if (!formData.date) {
alert('Please select a date');
@@ -485,9 +539,16 @@
}
// Validate LHe Output is a number
if (formData.lhe_output && isNaN(parseFloat(formData.lhe_output))) {
alert('LHe Output must be a valid number');
return;
const vorVal = $('#add-vor').val();
const nachVal = $('#add-nach').val();
if (vorVal !== "" && isNaN(parseFloat(vorVal))) {
alert('Vor must be a valid number');
return;
}
if (nachVal !== "" && isNaN(parseFloat(nachVal))) {
alert('Nach must be a valid number');
return;
}
$.ajax({
@@ -498,22 +559,25 @@
if (response.status === 'success') {
// Add the new row to the table
let newRow = `
<tr data-id="${response.id}">
<td>${$('#table-two tbody tr').length + 1}</td>
<td>${response.id}</td>
<td>${response.institute_name || ''}</td>
<td>${response.client_name}</td>
<td>${response.date || ''}</td>
<td>${response.is_warm ? 'Yes' : 'No'}</td>
<td>${response.lhe_delivery}</td>
<td>${response.lhe_output || ''}</td>
<td>${response.notes || ''}</td>
<td class="actions">
<button class="edit-btn-two">Edit</button>
<button class="delete-btn-two">Delete</button>
</td>
</tr>
<tr data-id="${response.id}">
<td>${$('#table-two tbody tr').length + 1}</td>
<td>${response.id}</td>
<td>${response.institute_name || ''}</td>
<td>${response.client_name}</td>
<td>${response.date || ''}</td>
<td>${response.is_warm ? 'Yes' : 'No'}</td>
<td>${response.lhe_delivery || ''}</td>
<td>${response.vor ?? ''}</td>
<td>${response.nach ?? ''}</td>
<td>${response.lhe_output ?? ''}</td>
<td>${response.notes || ''}</td>
<td class="actions">
<button class="edit-btn-two">Edit</button>
<button class="delete-btn-two">Delete</button>
</td>
</tr>
`;
$('#table-two tbody').append(newRow);
$('#add-popup-two').fadeOut();
} else {
@@ -558,15 +622,16 @@
// Set other fields
$('#edit-id').val(entryId);
$('#edit-date').val(row.find('td:eq(3)').text().trim());
// Set warm value (convert from "Yes"/"No" to 1/0)
const warmValue = row.find('td:eq(4)').text().trim() === 'Yes' ? 1 : 0;
$('#edit-date').val(row.find('td:eq(4)').text().trim());
const warmValue = row.find('td:eq(5)').text().trim() === 'Yes' ? 1 : 0;
$('#edit-is-warm').val(warmValue);
$('#edit-lhe-delivery').val(row.find('td:eq(5)').text().trim());
$('#edit-lhe-output').val(row.find('td:eq(6)').text().trim());
$('#edit-notes').val(row.find('td:eq(7)').text().trim());
$('#edit-lhe-delivery').val(row.find('td:eq(6)').text().trim());
$('#edit-vor').val(row.find('td:eq(7)').text().trim());
$('#edit-nach').val(row.find('td:eq(8)').text().trim());
$('#edit-lhe-output').val(row.find('td:eq(9)').text().trim());
$('#edit-notes').val(row.find('td:eq(10)').text().trim());
// Apply warm field logic
handleWarmFieldChange($('#edit-is-warm'), $('#edit-lhe-delivery'));
@@ -597,15 +662,22 @@
}
let formData = {
'id': $('#edit-id').val(),
'client_id': clientId,
'date': $('#edit-date').val(),
'is_warm': warmValue,
'lhe_delivery': $('#edit-lhe-delivery').val(),
'lhe_output': $('#edit-lhe-output').val(),
'notes': $('#edit-notes').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
'id': $('#edit-id').val(),
'client_id': clientId,
'date': $('#edit-date').val(),
'is_warm': warmValue,
'lhe_delivery': $('#edit-lhe-delivery').val(),
// ✅ NEW
'vor': $('#edit-vor').val(),
'nach': $('#edit-nach').val(),
// ❌ DO NOT send lhe_output anymore
// 'lhe_output': $('#edit-lhe-output').val(),
'notes': $('#edit-notes').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
// Validate inputs
if (!formData.date) {
@@ -623,13 +695,16 @@
data: formData,
success: function(response) {
if (response.status === 'success') {
let row = $(`tr[data-id="${response.id}"]`);
row.find('td:eq(2)').text(response.client_name);
row.find('td:eq(3)').text(response.date || '');
row.find('td:eq(4)').text(response.is_warm ? 'Yes' : 'No');
row.find('td:eq(5)').text(response.lhe_delivery);
row.find('td:eq(6)').text(response.lhe_output || '');
row.find('td:eq(7)').text(response.notes || '');
let row = $(`tr[data-id="${response.id}"]`);
row.find('td:eq(2)').text(response.institute_name || '');
row.find('td:eq(3)').text(response.client_name || '');
row.find('td:eq(4)').text(response.date || '');
row.find('td:eq(5)').text(response.is_warm ? 'Yes' : 'No');
row.find('td:eq(6)').text(response.lhe_delivery || '');
row.find('td:eq(7)').text(response.vor ?? '');
row.find('td:eq(8)').text(response.nach ?? '');
row.find('td:eq(9)').text(response.lhe_output ?? '');
row.find('td:eq(10)').text(response.notes || '');
$('#edit-popup-two').fadeOut();
} else {
alert('Update failed: ' + (response.message || 'Unknown error'));
View File
+9
View File
@@ -0,0 +1,9 @@
from django import template
register = template.Library()
@register.filter
def get_item(d, key):
if d is None:
return ""
return d.get(key, "")
+7 -1
View File
@@ -1,7 +1,8 @@
from django.urls import path
from .views import DebugTopRightView
from .views import SaveCellsView , SaveMonthSummaryView, halfyear_settings,MonthlySheetView, monthly_sheet_root,set_halfyear_interval
from .views import SaveCellsView , SaveMonthSummaryView, halfyear_settings,MonthlySheetView, monthly_sheet_root,set_halfyear_interval,AbrechnungView, SaveAbrechnungCellsView
from . import views
from .views import RechnungView
urlpatterns = [
path('', views.clients_list, name='clients_list'),
@@ -13,10 +14,15 @@ urlpatterns = [
path('betriebskosten/', views.betriebskosten_list, name='betriebskosten_list'),
path('betriebskosten/create/', views.betriebskosten_create, name='betriebskosten_create'),
path('betriebskosten/delete/', views.betriebskosten_delete, name='betriebskosten_delete'),
path('betriebskosten/update-personalkosten/', views.update_personalkosten, name='update_personalkosten'),
path("abrechnung/", AbrechnungView.as_view(), name="abrechnung"),
path("abrechnung/autosave/", SaveAbrechnungCellsView.as_view(), name="abrechnung_autosave"),
path("abrechnung/save/", SaveAbrechnungCellsView.as_view(), name="save_abrechnung"),
path('check-sheets/', views.CheckSheetView.as_view(), name='check_sheets'),
path('quick-debug/', views.QuickDebugView.as_view(), name='quick_debug'),
path('test-formula/', views.TestFormulaView.as_view(), name='test_formula'),
path('simple-debug/', views.SimpleDebugView.as_view(), name='simple_debug'),
path("rechnung/", RechnungView.as_view(), name="rechnung"),
path('sheet/', monthly_sheet_root, name='monthly_sheet_root'),
path('set-halfyear-interval/', set_halfyear_interval, name='set_halfyear_interval'),
path('halfyear-bilanz/', views.halfyear_balance_view, name='halfyear_balance'),
+1092 -826
View File
File diff suppressed because it is too large Load Diff
-2873
View File
File diff suppressed because it is too large Load Diff