update
This commit is contained in:
BIN
Binary file not shown.
@@ -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}")
|
||||
@@ -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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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' %}">← Helium Output Übersicht</a>
|
||||
<h2>
|
||||
{% with first=window.0 last=window|last %}
|
||||
Halbjahres-Bilanz ({{ first.1 }}/{{ first.0 }} – {{ last.1 }}/{{ last.0 }})
|
||||
{% endwith %}
|
||||
</h2>
|
||||
{% with first=window.0 %}
|
||||
<a href="{% url 'monthly_sheet' first.0 first.1 %}">Monatsblätter</a>
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<div class="top-tables">
|
||||
<!-- LEFT TABLE (Top Left – AG Vogel / AG Halfm / IKP) -->
|
||||
<div class="table-container">
|
||||
<h3>Top Left – Halbjahresbilanz</h3>
|
||||
<table class="sheet-table spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bezeichnung</th>
|
||||
{% for c in clients_left %}
|
||||
<th>{{ c }}</th>
|
||||
{% endfor %}
|
||||
<th>Σ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in rows_left %}
|
||||
<tr>
|
||||
<td>{{ row.label }}</td>
|
||||
{% for v in row.values %}
|
||||
<td class="number-cell">
|
||||
{% if row.is_percent and v is not None %}
|
||||
{{ v|floatformat:4 }}
|
||||
{% elif v is not None %}
|
||||
{{ v|floatformat:2 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td class="number-cell">
|
||||
{% if row.is_percent and row.total %}
|
||||
{{ row.total|floatformat:4 }}
|
||||
{% elif row.total is not None %}
|
||||
{{ row.total|floatformat:2 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT TABLE (Top Right – Dr. Fohrer / AG Buntk. / etc.) -->
|
||||
<div class="table-container">
|
||||
<h3>Top Right – Halbjahresbilanz</h3>
|
||||
<table class="sheet-table spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bezeichnung</th>
|
||||
{% for c in clients_right %}
|
||||
<th>{{ c }}</th>
|
||||
{% endfor %}
|
||||
<th>Σ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in rows_right %}
|
||||
<tr>
|
||||
<td>{{ row.label }}</td>
|
||||
{% for v in row.values %}
|
||||
<td class="number-cell">
|
||||
{% if row.is_text_row %}
|
||||
{{ v }}
|
||||
{% elif row.is_percent and v is not None %}
|
||||
{{ v|floatformat:4 }}
|
||||
{% elif v is not None %}
|
||||
{{ v|floatformat:2 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td class="number-cell">
|
||||
{% if row.is_text_row %}
|
||||
{{ row.total }}
|
||||
{% elif row.is_percent and row.total %}
|
||||
{{ row.total|floatformat:4 }}
|
||||
{% elif row.total is not None %}
|
||||
{{ row.total|floatformat:2 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-container overall-summary-table">
|
||||
<h3>Summe</h3>
|
||||
<table class="sheet-table spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bezeichnung</th>
|
||||
<th>Σ</th>
|
||||
<th>Licht-wiese</th>
|
||||
<th>Chemie</th>
|
||||
<th>MaWi</th>
|
||||
<th>M3</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows_sum %}
|
||||
<tr data-row-index="{{ r.row_index }}">
|
||||
<tr>
|
||||
<td>{{ r.label }}</td>
|
||||
<td class="sum-col sum-cell">
|
||||
{% if r.is_percent %}
|
||||
{{ r.total|floatformat:2 }}%
|
||||
{% else %}
|
||||
{{ r.total|floatformat:2 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="sum-col">
|
||||
{% if r.is_percent %}
|
||||
{{ r.lichtwiese|floatformat:2 }}%
|
||||
{% else %}
|
||||
{{ r.lichtwiese|floatformat:2 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="sum-col">
|
||||
{% if r.is_percent %}
|
||||
{{ r.chemie|floatformat:2 }}%
|
||||
{% else %}
|
||||
{{ r.chemie|floatformat:2 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="sum-col">
|
||||
{% if r.is_percent %}
|
||||
{{ r.mawi|floatformat:2 }}%
|
||||
{% else %}
|
||||
{{ r.mawi|floatformat:2 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="sum-col">
|
||||
{% if r.is_percent %}
|
||||
{{ r.m3|floatformat:2 }}%
|
||||
{% else %}
|
||||
{{ r.m3|floatformat:2 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-container" style="margin-top: 20px;">
|
||||
<h3>Bottom Table 1 – Bilanz (read-only)</h3>
|
||||
|
||||
<table class="sheet-table spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Gasspeicher</th>
|
||||
<th>Volumen</th>
|
||||
<th>bar</th>
|
||||
<th>korrigiert</th>
|
||||
<th>Nm³</th>
|
||||
<th>Lit. LHe</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for r in bottom1_rows %}
|
||||
<tr class="{% if r.is_total %}summary-row{% endif %}">
|
||||
<td class="row-label {% if r.indent %}indent{% endif %}">{{ r.label }}</td>
|
||||
<td class="number-cell">{{ r.volume|floatformat:1 }}</td>
|
||||
<td class="number-cell">{{ r.bar|floatformat:0 }}</td>
|
||||
<td class="number-cell">{{ r.korr|floatformat:1 }}</td>
|
||||
<td class="number-cell">{{ r.nm3|floatformat:0 }}</td>
|
||||
<td class="number-cell">{{ r.lhe|floatformat:0 }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="table-container bottom-table-2">
|
||||
<h3>Bottom Table 2 – Verbraucherbestand L-He (read-only)</h3>
|
||||
|
||||
<table class="sheet-table spreadsheet-table">
|
||||
<tbody>
|
||||
<!-- Row 38 -->
|
||||
<tr>
|
||||
<td class="row-label" colspan="5">Verbraucherbestand</td>
|
||||
<td class="readonly-cell">{{ bottom2.j38|floatformat:2 }}</td>
|
||||
<td class="readonly-cell">{{ bottom2.k38|floatformat:2 }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Row 39 -->
|
||||
<tr>
|
||||
<td class="row-label">+ Anlage:</td>
|
||||
<td>Gefäss 2,5:</td>
|
||||
<td class="readonly-cell">{{ bottom2.g39|floatformat:2 }}</td>
|
||||
<td>Gefäss 1,0:</td>
|
||||
<td class="readonly-cell">{{ bottom2.i39|floatformat:2 }}</td>
|
||||
<td class="readonly-cell">{{ bottom2.j39|floatformat:2 }}</td>
|
||||
<td class="readonly-cell">{{ bottom2.k39|floatformat:2 }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Row 40 -->
|
||||
<tr>
|
||||
<td class="row-label">+ Kaltgas:</td>
|
||||
<td>Gefäss 2,5:</td>
|
||||
<td class="readonly-cell">{{ bottom2.g40|default_if_none:""|floatformat:2 }}</td>
|
||||
<td>Gefäss 1,0:</td>
|
||||
<td class="readonly-cell">{{ bottom2.i40|default_if_none:""|floatformat:2 }}</td>
|
||||
<td class="readonly-cell">{{ bottom2.j40|floatformat:2 }}</td>
|
||||
<td class="readonly-cell">{{ bottom2.k40|floatformat:2 }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Row 43 -->
|
||||
<tr>
|
||||
<td class="row-label" colspan="5">Bestand flüssig He</td>
|
||||
<td class="readonly-cell">{{ bottom2.j43|floatformat:2 }}</td>
|
||||
<td class="readonly-cell">{{ bottom2.k43|floatformat:2 }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Row 44 -->
|
||||
<tr>
|
||||
<td class="row-label" colspan="5">Gesamtbestand neu:</td>
|
||||
<td class="readonly-cell">{{ bottom2.j44|floatformat:2 }}</td>
|
||||
<td class="readonly-cell">{{ bottom2.k44|floatformat:2 }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div> {# closes .spreadsheet-container #}
|
||||
{% endblock %}
|
||||
+4401
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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' %}">← Ü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 %}
|
||||
@@ -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' %}">← Ü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;">← Fachgebiet →</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;">
|
||||
← Fachgebiet →
|
||||
</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 %}
|
||||
@@ -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">×</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 (m³):</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">×</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 (m³):</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>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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'));
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
-2873
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Reference in New Issue
Block a user