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

Binary file not shown.

48
round_entries.py Normal file
View File

@@ -0,0 +1,48 @@
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}")

View File

@@ -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'),
),
]

View File

@@ -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)),
],
),
]

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')},
},
),
]

View File

@@ -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']},
),
]

View File

@@ -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),
),
]

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
sheets/new/views.py Normal file

File diff suppressed because it is too large Load Diff

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
sheets/old/views.py Normal file

File diff suppressed because it is too large Load Diff

4520
sheets/old/views_v2.py Normal file

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

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 %}

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 %}

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>

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 {

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>

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('');

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

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, "")

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'),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.