update
This commit is contained in:
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
48
round_entries.py
Normal file
48
round_entries.py
Normal 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}")
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
24
sheets/migrations/0015_betriebskostensummary.py
Normal file
24
sheets/migrations/0015_betriebskostensummary.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
27
sheets/migrations/0016_abrechnungcell.py
Normal file
27
sheets/migrations/0016_abrechnungcell.py
Normal 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')},
|
||||
},
|
||||
),
|
||||
]
|
||||
17
sheets/migrations/0017_alter_secondtableentry_options.py
Normal file
17
sheets/migrations/0017_alter_secondtableentry_options.py
Normal 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']},
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
124
sheets/models.py
124
sheets/models.py
@@ -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
5041
sheets/new/views.py
Normal file
File diff suppressed because it is too large
Load Diff
353
sheets/old/halfyear_balance.html
Normal file
353
sheets/old/halfyear_balance.html
Normal 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' %}">← 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
sheets/old/views.py
Normal file
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
4520
sheets/old/views_v2.py
Normal file
File diff suppressed because it is too large
Load Diff
0
sheets/services/__init__.py
Normal file
0
sheets/services/__init__.py
Normal file
1156
sheets/services/halfyear_calc.py
Normal file
1156
sheets/services/halfyear_calc.py
Normal file
File diff suppressed because it is too large
Load Diff
123
sheets/templates/Rechnung.html
Normal file
123
sheets/templates/Rechnung.html
Normal 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' %}">← Übersicht</a>
|
||||
<h2>Rechnung</h2>
|
||||
<a href="{% url 'abrechnung' %}">Abrechnung</a>
|
||||
</div>
|
||||
|
||||
{% if needs_interval %}
|
||||
<div class="table-container">
|
||||
<p style="margin:0;">Bitte zuerst ein 6-Monats-Intervall auf der Übersichtsseite auswählen.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
<div class="table-container">
|
||||
<table class="sheet-table spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Arbeitsgruppe</th>
|
||||
<th>Tabellenkürzel</th>
|
||||
<th>Name</th>
|
||||
<th>Heliumverluste (€)</th>
|
||||
<th>Heliumverluste (l)</th>
|
||||
<th>Verbr. u. Repar. (€)</th>
|
||||
<th>Gesamtverflüssigung (l)</th>
|
||||
<th>Preis (€/l)</th>
|
||||
<th>Bezogene Menge (l)</th>
|
||||
<th>Verflüssigungskosten (€)</th>
|
||||
<th>Verlustkosten (€)</th>
|
||||
<th>Gaskosten (€)</th>
|
||||
<th>Gutschriften (€)</th>
|
||||
<th>Personalkosten (€)</th>
|
||||
<th>Anteil Personalkosten (€)</th>
|
||||
<th>Gesamtkosten (€)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td>{{ r.arbeitsgruppe }}</td>
|
||||
<td>{{ r.tabellenkuerzel }}</td>
|
||||
<td>{{ r.name }}</td>
|
||||
|
||||
<td>{{ r.heliumverluste_eur|floatformat:2 }}</td>
|
||||
<td>{{ r.heliumverluste_l|floatformat:2 }}</td>
|
||||
<td>{{ r.verbr_u_repar_eur|floatformat:2 }}</td>
|
||||
<td>{{ r.gesamtverfluessigung_l|floatformat:2 }}</td>
|
||||
<td>{{ r.preis_eur_l|floatformat:4 }}</td>
|
||||
|
||||
<td>{{ r.bezogene_menge_l|floatformat:2 }}</td>
|
||||
<td>{{ r.verfluessigungskosten_eur|floatformat:2 }}</td>
|
||||
<td>{{ r.verlustkosten_eur|floatformat:2 }}</td>
|
||||
<td>{{ r.gaskosten_eur|floatformat:2 }}</td>
|
||||
<td>{{ r.gutschriften_eur|floatformat:2 }}</td>
|
||||
<td>{{ r.personalkosten_eur|floatformat:2 }}</td>
|
||||
<td>{{ r.anteil_personal_eur|floatformat:2 }}</td>
|
||||
<td>{{ r.gesamtkosten_eur|floatformat:2 }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
370
sheets/templates/abrechnung.html
Normal file
370
sheets/templates/abrechnung.html
Normal 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' %}">← Ü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('');
|
||||
|
||||
@@ -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
sheets/templatetags/__init__.py
Normal file
0
sheets/templatetags/__init__.py
Normal file
9
sheets/templatetags/dict_extras.py
Normal file
9
sheets/templatetags/dict_extras.py
Normal 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, "")
|
||||
@@ -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'),
|
||||
|
||||
1918
sheets/views.py
1918
sheets/views.py
File diff suppressed because it is too large
Load Diff
2873
sheets/views.txt
2873
sheets/views.txt
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Reference in New Issue
Block a user