Finished months balance except the below tables
This commit is contained in:
Binary file not shown.
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
BIN
excel_mimic/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
excel_mimic/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
excel_mimic/__pycache__/settings.cpython-313.pyc
Normal file
BIN
excel_mimic/__pycache__/settings.cpython-313.pyc
Normal file
Binary file not shown.
BIN
excel_mimic/__pycache__/urls.cpython-313.pyc
Normal file
BIN
excel_mimic/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
excel_mimic/__pycache__/wsgi.cpython-313.pyc
Normal file
BIN
excel_mimic/__pycache__/wsgi.cpython-313.pyc
Normal file
Binary file not shown.
36
fix_top_right_cells.py
Normal file
36
fix_top_right_cells.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from sheets.models import MonthlySheet, Client, Cell
|
||||
|
||||
TOP_RIGHT_CLIENTS = [
|
||||
"Dr. Fohrer", # L
|
||||
"AG Buntk.", # M
|
||||
"AG Alff", # N
|
||||
"AG Gutfl.", # O
|
||||
"M3 Thiele", # P
|
||||
"M3 Buntkowsky", # Q
|
||||
"M3 Gutfleisch", # R
|
||||
]
|
||||
|
||||
ROW_COUNT = 16 # top_right rows 0–15
|
||||
|
||||
sheets = MonthlySheet.objects.all().order_by('year', 'month')
|
||||
|
||||
for sheet in sheets:
|
||||
print(f"Fixing sheet {sheet.year}-{sheet.month}...")
|
||||
for col_idx, name in enumerate(TOP_RIGHT_CLIENTS):
|
||||
try:
|
||||
client = Client.objects.get(name=name)
|
||||
except Client.DoesNotExist:
|
||||
print(f" WARNING: client {name!r} not found, skipping this column")
|
||||
continue
|
||||
|
||||
for row_idx in range(ROW_COUNT):
|
||||
cell, created = Cell.objects.get_or_create(
|
||||
sheet=sheet,
|
||||
table_type='top_right',
|
||||
client=client,
|
||||
row_index=row_idx,
|
||||
column_index=col_idx,
|
||||
defaults={'value': None},
|
||||
)
|
||||
if created:
|
||||
print(f" created cell: {name} row {row_idx}")
|
||||
@@ -0,0 +1,2 @@
|
||||
# sheets/__init__.py
|
||||
default_app_config = 'sheets.apps.SheetsConfig'
|
||||
BIN
sheets/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
sheets/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
sheets/__pycache__/admin.cpython-313.pyc
Normal file
BIN
sheets/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
sheets/__pycache__/apps.cpython-313.pyc
Normal file
BIN
sheets/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
sheets/__pycache__/forms.cpython-313.pyc
Normal file
BIN
sheets/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
sheets/__pycache__/models.cpython-313.pyc
Normal file
BIN
sheets/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
sheets/__pycache__/urls.cpython-313.pyc
Normal file
BIN
sheets/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
sheets/__pycache__/views.cpython-313.pyc
Normal file
BIN
sheets/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
@@ -1,6 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SheetsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'sheets'
|
||||
|
||||
def ready(self):
|
||||
import sheets.signals
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
# Generated by Django 5.1.5 on 2026-01-07 11:29
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sheets', '0010_excelentry_constant_300_excelentry_druckkorrektur_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='betriebskosten',
|
||||
name='kostentyp',
|
||||
field=models.CharField(choices=[('sach', 'Sachkosten'), ('ln2', 'LN2'), ('helium', 'Helium'), ('inv', 'Inventar')], max_length=10, verbose_name='Kostentyp'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MonthlySheet',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('year', models.IntegerField()),
|
||||
('month', models.IntegerField()),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['year', 'month'],
|
||||
'unique_together': {('year', 'month')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Cell',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('table_type', models.CharField(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')], max_length=20)),
|
||||
('row_index', models.IntegerField()),
|
||||
('column_index', models.IntegerField()),
|
||||
('value', models.DecimalField(blank=True, decimal_places=6, max_digits=15, null=True)),
|
||||
('formula', models.TextField(blank=True)),
|
||||
('is_formula', models.BooleanField(default=False)),
|
||||
('data_type', models.CharField(choices=[('number', 'Number'), ('text', 'Text'), ('date', 'Date')], default='number', max_length=20)),
|
||||
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.client')),
|
||||
('sheet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cells', to='sheets.monthlysheet')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CellReference',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('source_cell', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dependents', to='sheets.cell')),
|
||||
('target_cell', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dependencies', to='sheets.cell')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('source_cell', 'target_cell')},
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='cell',
|
||||
index=models.Index(fields=['sheet', 'table_type', 'row_index'], name='sheets_cell_sheet_i_511238_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='cell',
|
||||
unique_together={('sheet', 'client', 'table_type', 'row_index', 'column_index')},
|
||||
),
|
||||
]
|
||||
34
sheets/migrations/0012_tableconfig_rowcalculation.py
Normal file
34
sheets/migrations/0012_tableconfig_rowcalculation.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 6.0.1 on 2026-02-01 11:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sheets', '0011_alter_betriebskosten_kostentyp_monthlysheet_cell_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TableConfig',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('table_type', models.CharField(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')], max_length=20, unique=True)),
|
||||
('calculations', models.JSONField(default=dict)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RowCalculation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('table_type', models.CharField(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')], max_length=20)),
|
||||
('row_index', models.IntegerField()),
|
||||
('formula', models.TextField()),
|
||||
('description', models.CharField(blank=True, max_length=200)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('table_type', 'row_index')},
|
||||
},
|
||||
),
|
||||
]
|
||||
BIN
sheets/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
BIN
sheets/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
sheets/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
sheets/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
111
sheets/models.py
111
sheets/models.py
@@ -8,9 +8,74 @@ class Institute(models.Model):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Client(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
address = models.TextField()
|
||||
institute = models.ForeignKey(Institute, on_delete=models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.institute.name})"
|
||||
|
||||
class MonthlySheet(models.Model):
|
||||
"""Represents one monthly page"""
|
||||
year = models.IntegerField()
|
||||
month = models.IntegerField() # 1-12
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ['year', 'month']
|
||||
ordering = ['year', 'month']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.year}-{self.month:02d}"
|
||||
|
||||
class Cell(models.Model):
|
||||
"""A single cell in the spreadsheet"""
|
||||
sheet = models.ForeignKey(MonthlySheet, on_delete=models.CASCADE, related_name='cells')
|
||||
client = models.ForeignKey(Client, on_delete=models.CASCADE)
|
||||
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() # 0-23 for top tables, 0-9 for bottom
|
||||
column_index = models.IntegerField() # Actually client index (0-5)
|
||||
is_formula = models.BooleanField(default=False)
|
||||
# Cell content
|
||||
value = models.DecimalField(max_digits=15, decimal_places=6, null=True, blank=True)
|
||||
formula = models.TextField(blank=True)
|
||||
|
||||
|
||||
# Metadata
|
||||
data_type = models.CharField(max_length=20, default='number', choices=[
|
||||
('number', 'Number'),
|
||||
('text', 'Text'),
|
||||
('date', 'Date'),
|
||||
])
|
||||
|
||||
class Meta:
|
||||
unique_together = ['sheet', 'client', 'table_type', 'row_index', 'column_index']
|
||||
indexes = [
|
||||
models.Index(fields=['sheet', 'table_type', 'row_index']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.sheet} - {self.client.name} - {self.table_type}[{self.row_index}][{self.column_index}]"
|
||||
|
||||
class CellReference(models.Model):
|
||||
"""Track dependencies between cells for calculations"""
|
||||
source_cell = models.ForeignKey(Cell, on_delete=models.CASCADE, related_name='dependents')
|
||||
target_cell = models.ForeignKey(Cell, on_delete=models.CASCADE, related_name='dependencies')
|
||||
|
||||
class Meta:
|
||||
unique_together = ['source_cell', 'target_cell']
|
||||
|
||||
class Betriebskosten(models.Model):
|
||||
KOSTENTYP_CHOICES = [
|
||||
('sach', 'Sachkostöen'),
|
||||
('sach', 'Sachkosten'),
|
||||
('ln2', 'LN2'),
|
||||
('helium', 'Helium'),
|
||||
('inv', 'Inventar'),
|
||||
@@ -30,16 +95,40 @@ class Betriebskosten(models.Model):
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.buchungsdatum} - {self.get_kostentyp_display()} - {self.betrag}€" # Fixed the missing quote
|
||||
|
||||
class Client(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
address = models.TextField()
|
||||
institute = models.ForeignKey(Institute, on_delete=models.CASCADE) # Remove null=True, blank=True
|
||||
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.name} ({self.institute.name})"
|
||||
return f"{self.table_type}[{self.row_index}]: {self.formula}"
|
||||
|
||||
# Or simpler: Just store row calculations in a JSONField
|
||||
class TableConfig(models.Model):
|
||||
"""Configuration for table calculations"""
|
||||
table_type = models.CharField(max_length=20, unique=True, 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'),
|
||||
])
|
||||
calculations = models.JSONField(default=dict) # {11: "10 + 9", 15: "14 - 13"}
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.get_table_type_display()} Config"
|
||||
class ExcelEntry(models.Model):
|
||||
client = models.ForeignKey(Client, on_delete=models.CASCADE)
|
||||
date = models.DateField(default=timezone.now)
|
||||
@@ -103,6 +192,10 @@ class ExcelEntry(models.Model):
|
||||
decimal_places=6,
|
||||
default=0.0
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.client.name} - {self.date}"
|
||||
|
||||
class SecondTableEntry(models.Model):
|
||||
client = models.ForeignKey(Client, on_delete=models.CASCADE)
|
||||
date = models.DateField(default=timezone.now)
|
||||
@@ -119,4 +212,4 @@ class SecondTableEntry(models.Model):
|
||||
date_joined = models.DateField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.client.name} - {self.date}"
|
||||
return f"{self.client.name} - {self.date}"
|
||||
82
sheets/signals.py
Normal file
82
sheets/signals.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# sheets/signals.py
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
from django.db.models import Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
from decimal import Decimal
|
||||
|
||||
# IMPORT THE MODELS AT THE TOP
|
||||
from .models import ExcelEntry, SecondTableEntry, MonthlySheet, Cell
|
||||
|
||||
@receiver([post_save, post_delete], sender=ExcelEntry)
|
||||
def update_top_right_helium_input(sender, instance, **kwargs):
|
||||
"""Update top-right table when ExcelEntry changes"""
|
||||
if instance.date:
|
||||
# Get the monthly sheet for this date
|
||||
sheet = MonthlySheet.objects.filter(
|
||||
year=instance.date.year,
|
||||
month=instance.date.month
|
||||
).first()
|
||||
|
||||
if sheet:
|
||||
# Re-populate helium input data
|
||||
from .views import MonthlySheetView
|
||||
view = MonthlySheetView()
|
||||
view.populate_helium_input_to_top_right(sheet)
|
||||
|
||||
@receiver([post_save, post_delete], sender=SecondTableEntry)
|
||||
def update_monthly_sheet_bezug(sender, instance, **kwargs):
|
||||
"""Update B11 (Bezug) in monthly sheet when SecondTableEntry changes - ONLY for non-start sheets"""
|
||||
if instance.date and instance.client:
|
||||
# Check if this is the start sheet (2025-01)
|
||||
if instance.date.year == 2025 and instance.date.month == 1:
|
||||
return # Don't auto-update for start sheet
|
||||
|
||||
# Get or create the monthly sheet for this date
|
||||
sheet, created = MonthlySheet.objects.get_or_create(
|
||||
year=instance.date.year,
|
||||
month=instance.date.month
|
||||
)
|
||||
|
||||
# Calculate total LHe output for this client in this month
|
||||
lhe_output_sum = SecondTableEntry.objects.filter(
|
||||
client=instance.client,
|
||||
date__year=instance.date.year,
|
||||
date__month=instance.date.month
|
||||
).aggregate(
|
||||
total=Coalesce(Sum('lhe_output'), Decimal('0'))
|
||||
)['total']
|
||||
|
||||
# Update B11 cell (row_index 8 = Excel B11) in TOP-LEFT table
|
||||
b11_cell_left = Cell.objects.filter(
|
||||
sheet=sheet,
|
||||
table_type='top_left',
|
||||
client=instance.client,
|
||||
row_index=8 # Excel B11
|
||||
).first()
|
||||
|
||||
if b11_cell_left and (b11_cell_left.value != lhe_output_sum or b11_cell_left.value is None):
|
||||
b11_cell_left.value = lhe_output_sum
|
||||
b11_cell_left.save()
|
||||
|
||||
# Import here to avoid circular imports
|
||||
from .views import SaveCellsView
|
||||
save_view = SaveCellsView()
|
||||
save_view.calculate_top_left_dependents(sheet, b11_cell_left)
|
||||
|
||||
# ALSO update Bezug (row_index 8) in TOP-RIGHT table
|
||||
b11_cell_right = Cell.objects.filter(
|
||||
sheet=sheet,
|
||||
table_type='top_right',
|
||||
client=instance.client,
|
||||
row_index=8 # Excel row 12 = Bezug
|
||||
).first()
|
||||
|
||||
if b11_cell_right and (b11_cell_right.value != lhe_output_sum or b11_cell_right.value is None):
|
||||
b11_cell_right.value = lhe_output_sum
|
||||
b11_cell_right.save()
|
||||
|
||||
# Also trigger top-right calculations
|
||||
from .views import SaveCellsView
|
||||
save_view = SaveCellsView()
|
||||
save_view.calculate_top_right_dependents(sheet, b11_cell_right)
|
||||
@@ -47,6 +47,7 @@
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
674
sheets/templates/monthly_sheet.html
Normal file
674
sheets/templates/monthly_sheet.html
Normal file
@@ -0,0 +1,674 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="spreadsheet-container">
|
||||
<!-- Navigation Header -->
|
||||
<div class="sheet-navigation">
|
||||
<a href="{% url 'monthly_sheet' prev_month.year prev_month.month %}">← Previous</a>
|
||||
<h2>{{ year }} - {{ month_name }} (Sheet {{ month }}/6)</h2>
|
||||
<a href="{% url 'monthly_sheet' next_month.year next_month.month %}">Next →</a>
|
||||
<a href="{% url 'summary_sheet' year month %}">Go to Summary</a>
|
||||
</div>
|
||||
|
||||
<!-- Top Tables Container -->
|
||||
<div class="top-tables">
|
||||
<!-- Left Table (18 rows × clients) -->
|
||||
<div class="table-container top-left-table">
|
||||
<h3>Table 1: Top Left</h3>
|
||||
<table class="spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Row Label</th>
|
||||
{% for header in top_left_headers %}
|
||||
<th>{{ header }}</th>
|
||||
{% endfor %}
|
||||
<th>Sum</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in top_left_rows %}
|
||||
{% with rownum=forloop.counter %}
|
||||
<tr data-row="{{ forloop.counter0 }}">
|
||||
<td class="row-label">
|
||||
{% if rownum == 1 %}
|
||||
Stand der Gaszähler (Nm³)
|
||||
{% elif rownum == 2 %}
|
||||
Stand der Gaszähler (Vormonat) (Nm³)
|
||||
{% elif rownum == 3 %}
|
||||
Gasrückführung (Nm³)
|
||||
{% elif rownum == 4 %}
|
||||
Rückführung flüssig (Lit. L-He)
|
||||
{% elif rownum == 5 %}
|
||||
Sonderrückführungen (Lit. L-He)
|
||||
{% elif rownum == 6 %}
|
||||
Bestand in Kannen-1 (Lit. L-He)
|
||||
{% elif rownum == 7 %}
|
||||
Summe Bestand (Lit. L-He)
|
||||
{% elif rownum == 8 %}
|
||||
Best. in Kannen Vormonat (Lit. L-He)
|
||||
{% elif rownum == 9 %}
|
||||
Bezug (Liter L-He)
|
||||
{% elif rownum == 10 %}
|
||||
Rückführ. Soll (Lit. L-He)
|
||||
{% elif rownum == 11 %}
|
||||
Verluste (Soll-Rückf.) (Lit. L-He)
|
||||
{% elif rownum == 12 %}
|
||||
Füllungen warm (Lit. L-He)
|
||||
{% elif rownum == 13 %}
|
||||
Kaltgas Rückgabe (Lit. L-He) – Faktor
|
||||
{% elif rownum == 14 %}
|
||||
Faktor 0.06
|
||||
{% elif rownum == 15 %}
|
||||
Verbraucherverluste (Liter L-He)
|
||||
{% elif rownum == 16 %}
|
||||
%
|
||||
{% elif rownum == 17 %}
|
||||
Gesamtrückführung (Nm³)
|
||||
{% elif rownum == 18 %}
|
||||
Aufgeteilte Verluste (Liter L-He)
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
{% for cell in row.cells %}
|
||||
{% if is_start_sheet or rownum == 1 or rownum == 5 or rownum == 6 %}
|
||||
{# Editable in start sheet OR always editable rows (B3, B7, B8) #}
|
||||
<td class="editable-cell"
|
||||
data-cell-id="{{ cell.id|default:'' }}"
|
||||
data-table="top_left"
|
||||
data-row="{{ forloop.parentloop.counter0 }}"
|
||||
data-col="{{ forloop.counter0 }}"
|
||||
data-client-id="{{ cell.client.id|default:'' }}"
|
||||
contenteditable="true">
|
||||
{{ cell.value|default:"" }}
|
||||
</td>
|
||||
{% else %}
|
||||
{# Readonly for non-start sheets #}
|
||||
<td class="readonly-cell"
|
||||
data-cell-id="{{ cell.id|default:'' }}"
|
||||
data-table="top_left"
|
||||
data-row="{{ forloop.parentloop.counter0 }}"
|
||||
data-col="{{ forloop.counter0 }}"
|
||||
data-client-id="{{ cell.client.id|default:'' }}"
|
||||
contenteditable="false">
|
||||
{{ cell.value|default:"" }}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<td class="sum-cell">
|
||||
{{ row.sum|default_if_none:"" }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Right Table (24 rows × 6 clients) -->
|
||||
<!-- Update the top-right table section in monthly_sheet.html -->
|
||||
<div class="table-container top-right-table">
|
||||
<h3>Table 2: Top Right</h3>
|
||||
<table class="spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Row Label</th>
|
||||
{% for header in top_right_headers %}
|
||||
<th>{{ header }}</th>
|
||||
{% endfor %}
|
||||
<th>Sum</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in top_right_rows %}
|
||||
{% with rownum=forloop.counter %}
|
||||
<tr data-row="{{ forloop.counter0 }}">
|
||||
<td class="row-label">
|
||||
{% if rownum == 1 %}
|
||||
Stand der Gaszähler (Vormonat) (Nm³)
|
||||
{% elif rownum == 2 %}
|
||||
Gasrückführung (Nm³)
|
||||
{% elif rownum == 3 %}
|
||||
Rückführung flüssig (Lit. L-He)
|
||||
{% elif rownum == 4 %}
|
||||
Sonderrückführungen (Lit. L-He)
|
||||
{% elif rownum == 5 %}
|
||||
Sammelrückführungen (Lit. L-He)
|
||||
{% elif rownum == 6 %}
|
||||
Bestand in Kannen-1 (Lit. L-He)
|
||||
{% elif rownum == 7 %}
|
||||
Summe Bestand (Lit. L-He)
|
||||
{% elif rownum == 8 %}
|
||||
Best. in Kannen Vormonat (Lit. L-He)
|
||||
{% elif rownum == 9 %}
|
||||
Bezug (Liter L-He)
|
||||
{% elif rownum == 10 %}
|
||||
Rückführ. Soll (Lit. L-He)
|
||||
{% elif rownum == 11 %}
|
||||
Verluste (Soll-Rückf.) (Lit. L-He)
|
||||
{% elif rownum == 12 %}
|
||||
Füllungen warm (Lit. L-He)
|
||||
{% elif rownum == 13 %}
|
||||
Kaltgas Rückgabe (Lit. L-He) – Faktor
|
||||
{% elif rownum == 14 %}
|
||||
Faktor 0.06
|
||||
{% elif rownum == 15 %}
|
||||
Verbraucherverluste (Liter L-He)
|
||||
{% elif rownum == 16 %}
|
||||
%
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
{% for cell in row.cells %}
|
||||
{% with client_name=cell.client.name|default:"" %}
|
||||
{# Determine if this cell should be editable #}
|
||||
{% if is_start_sheet %}
|
||||
<td class="editable-cell"
|
||||
data-cell-id="{{ cell.id|default:'' }}"
|
||||
data-table="top_right"
|
||||
data-row="{{ forloop.parentloop.counter0 }}"
|
||||
data-col="{{ forloop.counter0 }}"
|
||||
data-client-id="{{ cell.client.id|default:'' }}"
|
||||
contenteditable="true">
|
||||
{{ cell.value|default:"" }}
|
||||
</td>
|
||||
{% elif rownum == 4 or rownum == 6 or rownum == 1 and client_name in "M3 Thiele,M3 Buntkowsky,M3 Gutfleisch" %}
|
||||
<td class="editable-cell"
|
||||
data-cell-id="{{ cell.id|default:'' }}"
|
||||
data-table="top_right"
|
||||
data-row="{{ forloop.parentloop.counter0 }}"
|
||||
data-col="{{ forloop.counter0 }}"
|
||||
data-client-id="{{ cell.client.id|default:'' }}"
|
||||
contenteditable="true">
|
||||
{{ cell.value|default:"" }}
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="readonly-cell"
|
||||
data-cell-id="{{ cell.id|default:'' }}"
|
||||
data-table="top_right"
|
||||
data-row="{{ forloop.parentloop.counter0 }}"
|
||||
data-col="{{ forloop.counter0 }}"
|
||||
data-client-id="{{ cell.client.id|default:'' }}"
|
||||
contenteditable="false">
|
||||
{% if rownum == 2 %}
|
||||
Aufteilung Nach Verbrauch
|
||||
{% else %}
|
||||
{{ cell.value|default:"" }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
|
||||
<td class="sum-cell">
|
||||
{{ row.sum|default_if_none:"" }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Bottom Tables -->
|
||||
<div class="bottom-tables">
|
||||
<div class="table-container bottom-table-1">
|
||||
<h3>Bottom Table 1</h3>
|
||||
<table class="spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Row Label</th>
|
||||
{% for client in clients %}
|
||||
<th>{{ client.name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in cells_by_table.bottom_1 %}
|
||||
<tr data-row="{{ forloop.counter0 }}">
|
||||
<td class="row-label">Row {{ forloop.counter }}</td>
|
||||
{% for cell in row %}
|
||||
<td class="editable-cell"
|
||||
data-cell-id="{{ cell.id|default:'' }}"
|
||||
data-table="bottom_1"
|
||||
data-row="{{ forloop.parentloop.counter0 }}"
|
||||
data-col="{{ forloop.counter0 }}"
|
||||
data-client-id="{{ cell.client.id|default:'' }}"
|
||||
contenteditable="true">
|
||||
{{ cell.value|default:"" }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-container bottom-table-2">
|
||||
<h3>Bottom Table 2</h3>
|
||||
<table class="spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Row Label</th>
|
||||
{% for client in clients %}
|
||||
<th>{{ client.name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in cells_by_table.bottom_2 %}
|
||||
<tr data-row="{{ forloop.counter0 }}">
|
||||
<td class="row-label">Row {{ forloop.counter }}</td>
|
||||
{% for cell in row %}
|
||||
<td class="editable-cell"
|
||||
data-cell-id="{{ cell.id|default:'' }}"
|
||||
data-table="bottom_2"
|
||||
data-row="{{ forloop.parentloop.counter0 }}"
|
||||
data-col="{{ forloop.counter0 }}"
|
||||
data-client-id="{{ cell.client.id|default:'' }}"
|
||||
contenteditable="true">
|
||||
{{ cell.value|default:"" }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-container bottom-table-3">
|
||||
<h3>Bottom Table 3</h3>
|
||||
<table class="spreadsheet-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Row Label</th>
|
||||
{% for client in clients %}
|
||||
<th>{{ client.name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in cells_by_table.bottom_3 %}
|
||||
<tr data-row="{{ forloop.counter0 }}">
|
||||
<td class="row-label">Row {{ forloop.counter }}</td>
|
||||
{% for cell in row %}
|
||||
<td class="editable-cell"
|
||||
data-cell-id="{{ cell.id|default:'' }}"
|
||||
data-table="bottom_3"
|
||||
data-row="{{ forloop.parentloop.counter0 }}"
|
||||
data-col="{{ forloop.counter0 }}"
|
||||
data-client-id="{{ cell.client.id|default:'' }}"
|
||||
contenteditable="true">
|
||||
{{ cell.value|default:"" }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="action-bar">
|
||||
<button id="save-all-btn" class="btn btn-primary">Save All Cells</button>
|
||||
<div id="save-status"></div>
|
||||
<div class="legend">
|
||||
<small>
|
||||
<span style="background-color: #d1ecf1; padding: 2px 5px;">Saved cells</span>
|
||||
<span style="background-color: #d4edda; padding: 2px 5px;">Calculated cells</span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden form for cell data -->
|
||||
<form id="cell-data-form" style="display: none;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="sheet_id" value="{{ sheet.id }}">
|
||||
</form>
|
||||
|
||||
<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;
|
||||
}
|
||||
|
||||
.top-tables {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.top-tables .table-container {
|
||||
flex: 1;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
}
|
||||
.merged-cell-left {
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
.merged-cell-right {
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.cell-group-LM {
|
||||
background-color: #f0f8ff; /* Light blue for L&M group */
|
||||
}
|
||||
|
||||
.cell-group-NO {
|
||||
background-color: #f0fff0; /* Light green for N&O group */
|
||||
}
|
||||
|
||||
.cell-group-PQR {
|
||||
background-color: #fff0f0; /* Light red for P,Q,R group */
|
||||
}
|
||||
.bottom-tables {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.bottom-tables .table-container {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.spreadsheet-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.spreadsheet-table th,
|
||||
.spreadsheet-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spreadsheet-table th {
|
||||
background-color: #f2f2f2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.row-label {
|
||||
background-color: #f9f9f9;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.editable-cell {
|
||||
min-width: 100px;
|
||||
min-height: 30px;
|
||||
background-color: white;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.editable-cell:focus {
|
||||
outline: 2px solid #007bff;
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
.editable-cell:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.readonly-cell {
|
||||
background-color: #f8f9fa;
|
||||
color: #495057;
|
||||
cursor: default;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.cell-calculated {
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
background-color: #e8f4f8;
|
||||
}
|
||||
|
||||
.sum-cell {
|
||||
font-weight: bold;
|
||||
background-color: #f0f0f0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#save-status {
|
||||
margin-left: 10px;
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.saving-text {
|
||||
position: absolute;
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.saving {
|
||||
background-color: #fff3cd !important;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.saved-success {
|
||||
background-color: #d4edda !important;
|
||||
transition: background-color 0.5s ease;
|
||||
}
|
||||
|
||||
.calculated-cell {
|
||||
background-color: #e8f4f8 !important;
|
||||
font-style: italic;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.editable-cell[contenteditable="false"] {
|
||||
background-color: #f8f9fa !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Enable editing on focus
|
||||
$('.editable-cell').on('focus', function() {
|
||||
$(this).data('original-value', $(this).text().trim());
|
||||
});
|
||||
|
||||
// Save on blur
|
||||
$('.editable-cell').on('blur', function() {
|
||||
saveCell($(this));
|
||||
});
|
||||
|
||||
// Save on Enter key
|
||||
$('.editable-cell').on('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
$(this).blur();
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent editing of readonly cells
|
||||
$('.readonly-cell').on('focus click', function(e) {
|
||||
e.preventDefault();
|
||||
$(this).blur();
|
||||
});
|
||||
|
||||
function saveCell($cell) {
|
||||
const cellId = $cell.data('cell-id');
|
||||
const newValue = $cell.text().trim();
|
||||
const originalValue = $cell.data('original-value') || '';
|
||||
|
||||
// Skip if no change
|
||||
if (newValue === originalValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if no cell ID
|
||||
if (!cellId) {
|
||||
console.error('No cell ID found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show saving indicator
|
||||
$cell.addClass('saving');
|
||||
|
||||
// Prepare AJAX request
|
||||
const csrfToken = $('[name=csrfmiddlewaretoken]').val();
|
||||
|
||||
$.ajax({
|
||||
url: '{% url "save_cells" %}',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
data: {
|
||||
'sheet_id': '{{ sheet.id }}',
|
||||
'cell_id': cellId,
|
||||
'value': newValue
|
||||
},
|
||||
success: function(response) {
|
||||
$cell.removeClass('saving');
|
||||
|
||||
if (response.status === 'success') {
|
||||
// Update all cells returned by the server
|
||||
if (response.updated_cells) {
|
||||
response.updated_cells.forEach(function(cellData) {
|
||||
const targetCell = $('[data-cell-id="' + cellData.id + '"]');
|
||||
if (targetCell.length) {
|
||||
targetCell.text(cellData.value || '');
|
||||
targetCell.data('original-value', cellData.value || '');
|
||||
|
||||
// Style calculated cells differently
|
||||
// Style calculated cells differently
|
||||
if (cellData.is_calculated) {
|
||||
targetCell.addClass('calculated-cell');
|
||||
// Leave contenteditable as set by the template
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Show success message
|
||||
showStatus('Saved successfully!', 'success');
|
||||
|
||||
// Highlight the saved cell briefly
|
||||
$cell.addClass('saved-success');
|
||||
setTimeout(function() {
|
||||
$cell.removeClass('saved-success');
|
||||
}, 1000);
|
||||
|
||||
} else {
|
||||
// Restore original value on error
|
||||
$cell.text(originalValue);
|
||||
showStatus('Error: ' + response.message, 'error');
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$cell.removeClass('saving');
|
||||
$cell.text(originalValue);
|
||||
showStatus('Error: ' + error, 'error');
|
||||
console.error('Save error:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Save All button
|
||||
$('#save-all-btn').on('click', function() {
|
||||
const csrfToken = $('[name=csrfmiddlewaretoken]').val();
|
||||
const formData = new FormData();
|
||||
formData.append('sheet_id', '{{ sheet.id }}');
|
||||
formData.append('csrfmiddlewaretoken', csrfToken);
|
||||
|
||||
// Collect all cell values
|
||||
$('.editable-cell').each(function() {
|
||||
const cellId = $(this).data('cell-id');
|
||||
if (cellId) {
|
||||
formData.append('cell_' + cellId, $(this).text().trim());
|
||||
}
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: '{% url "save_cells" %}',
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.status === 'success') {
|
||||
if (response.updated_cells) {
|
||||
response.updated_cells.forEach(function(cellData) {
|
||||
const targetCell = $('[data-cell-id="' + cellData.id + '"]');
|
||||
if (targetCell.length) {
|
||||
targetCell.text(cellData.value || '');
|
||||
targetCell.data('original-value', cellData.value || '');
|
||||
}
|
||||
});
|
||||
}
|
||||
showStatus('All cells saved!', 'success');
|
||||
} else {
|
||||
showStatus('Error: ' + response.message, 'error');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
showStatus('Error saving all cells', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function showStatus(message, type) {
|
||||
const statusEl = $('#save-status');
|
||||
statusEl.text(message);
|
||||
statusEl.css('color', type === 'success' ? '#28a745' : '#dc3545');
|
||||
|
||||
setTimeout(function() {
|
||||
statusEl.text('');
|
||||
}, 3000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -191,15 +191,184 @@
|
||||
.readonly-field {
|
||||
background-color: #e9ecef;
|
||||
color: #6c757d;
|
||||
}
|
||||
/* ---- 6-month overview card ---- */
|
||||
.overview-card {
|
||||
background-color: #ffffff;
|
||||
padding: 16px 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.overview-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: flex-end;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.overview-header label {
|
||||
font-size: 0.9rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.overview-header select {
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.overview-header button {
|
||||
padding: 7px 14px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.overview-header button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.overview-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.overview-subtitle {
|
||||
font-size: 0.9rem;
|
||||
color: #555;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.overview-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.overview-table th,
|
||||
.overview-table td {
|
||||
padding: 6px 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.overview-table thead th {
|
||||
background-color: #007bff;
|
||||
color: #fff;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.overview-table thead th:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.overview-table tbody tr:hover {
|
||||
background-color: #f8f9ff;
|
||||
}
|
||||
|
||||
.overview-table .number-cell {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.overview-table .summary-row {
|
||||
background-color: #f0f4ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- This link should ONLY wrap the text below -->
|
||||
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
|
||||
⇦ Go to Clients
|
||||
</a>
|
||||
|
||||
<!-- 6-Month overview card (OUTSIDE any <a>) -->
|
||||
<div class="overview-card">
|
||||
<div class="overview-title">Helium Input – 6 Month Overview</div>
|
||||
|
||||
<form method="get" class="overview-header">
|
||||
<div>
|
||||
<label for="overview-year">Year</label><br>
|
||||
<select name="overview_year" id="overview-year">
|
||||
{% for y in available_years %}
|
||||
<option value="{{ y }}"
|
||||
{% if overview and overview.year == y %}selected{% endif %}>
|
||||
{{ y }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="overview-start-month">Start month</label><br>
|
||||
<select name="overview_start_month" id="overview-start-month">
|
||||
{% for m, label in month_choices %}
|
||||
<option value="{{ m }}"
|
||||
{% if overview and overview.start_month == m %}selected{% endif %}>
|
||||
{{ m }} - {{ label }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit">Show overview</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if overview %}
|
||||
<div class="overview-subtitle">
|
||||
Period:
|
||||
<strong>{{ overview.start_month }}–{{ overview.end_month }} / {{ overview.year }}</strong>
|
||||
</div>
|
||||
|
||||
<table class="overview-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Month</th>
|
||||
{% for g in overview.groups %}
|
||||
<th>{{ g.label }}</th>
|
||||
{% endfor %}
|
||||
<th>Month total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in overview.rows %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ row.month_number }} - {{ row.month_label|slice:":3" }}
|
||||
</td>
|
||||
{% for value in row.values %}
|
||||
<td class="number-cell">{{ value|floatformat:2 }}</td>
|
||||
{% endfor %}
|
||||
<td class="number-cell">{{ row.total|floatformat:2 }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
<tr class="summary-row">
|
||||
<td>Summe</td>
|
||||
{% for total in overview.group_totals %}
|
||||
<td class="number-cell">{{ total|floatformat:2 }}</td>
|
||||
{% endfor %}
|
||||
<td class="number-cell">{{ overview.grand_total|floatformat:2 }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="overview-subtitle">
|
||||
No data yet – choose a year and start month and click “Show overview”.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<h2>Helium Input</h2>
|
||||
<div class="table-container">
|
||||
<button class="add-row-btn" id="add-row-one">Add Row</button>
|
||||
@@ -218,7 +387,8 @@
|
||||
<col style="width: 5%"> <!-- L-He -->
|
||||
<col style="width: 5%"> <!-- L-He zus. -->
|
||||
<col style="width: 6%"> <!-- L-He ges. -->
|
||||
<col style="width: 6%"> <!-- Date Joined -->
|
||||
<col style="width: 7%"> <!-- Date -->
|
||||
<col style="width: 5%"> <!-- Month -->
|
||||
<col style="width: 8%"> <!-- Actions -->
|
||||
</colgroup>
|
||||
<thead>
|
||||
@@ -236,7 +406,8 @@
|
||||
<th>L-He</th>
|
||||
<th>L-He zus.</th>
|
||||
<th>L-He ges.</th>
|
||||
<th>Date Joined</th>
|
||||
<th>Date</th>
|
||||
<th>Month</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -256,7 +427,16 @@
|
||||
<td>{{ entry.lhe|floatformat:6 }}</td>
|
||||
<td>{{ entry.lhe_zus|floatformat:3 }}</td>
|
||||
<td>{{ entry.lhe_ges|floatformat:6 }}</td>
|
||||
<td>{{ entry.date_joined|date:"Y-m-d" }}</td>
|
||||
<td>
|
||||
{% if entry.date %}
|
||||
{{ entry.date|date:"d.m.Y" }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if entry.date %}
|
||||
{{ entry.date|date:"m" }} {# e.g. 01–12 #}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="actions">
|
||||
<button class="edit-btn-one">Edit</button>
|
||||
<button class="delete-btn-one">Delete</button>
|
||||
@@ -645,6 +825,7 @@
|
||||
<td>${response.lhe_zus}</td>
|
||||
<td>${response.lhe_ges}</td>
|
||||
<td>${response.date || ''}</td>
|
||||
<td>${response.month || ''}</td>
|
||||
<td class="actions">
|
||||
<button class="edit-btn-one">Edit</button>
|
||||
<button class="delete-btn-one">Delete</button>
|
||||
@@ -805,6 +986,7 @@
|
||||
row.find('td:eq(11)').text(response.lhe_zus);
|
||||
row.find('td:eq(12)').text(response.lhe_ges);
|
||||
row.find('td:eq(13)').text(response.date || '');
|
||||
row.find('td:eq(14)').text(response.month || '');
|
||||
$('#edit-popup-one').fadeOut();
|
||||
} else {
|
||||
alert('Error: ' + (response.message || 'Failed to update entry'));
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
from django.urls import path
|
||||
from .views import DebugTopRightView
|
||||
from .views import SaveCellsView
|
||||
from . import views
|
||||
# Create your URLs here.
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.clients_list, name='clients_list'), # Main page
|
||||
path('table-one/', views.table_one_view, name='table_one'), # Table One
|
||||
path('table-two/', views.table_two_view, name='table_two'), # Table Two
|
||||
path('', views.clients_list, name='clients_list'),
|
||||
path('table-one/', views.table_one_view, name='table_one'),
|
||||
path('table-two/', views.table_two_view, name='table_two'),
|
||||
path('add-entry/<str:model_name>/', views.add_entry, name='add_entry'),
|
||||
path('update-entry/<str:model_name>/', views.update_entry, name='update_entry'),
|
||||
path('delete-entry/<str:model_name>/', views.delete_entry, name='delete_entry'),
|
||||
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('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('sheet/<int:year>/<int:month>/', views.MonthlySheetView.as_view(), name='monthly_sheet'),
|
||||
path('summary/<int:year>/<int:start_month>/', views.SummarySheetView.as_view(), name='summary_sheet'),
|
||||
path("save-cells/", SaveCellsView.as_view(), name="save_cells"),
|
||||
path('calculate/', views.CalculateView.as_view(), name='calculate'),
|
||||
path('debug-calculation/', views.DebugCalculationView.as_view(), name='debug_calculation'),
|
||||
path('debug-top-right/', DebugTopRightView.as_view(), name='debug_top_right'),
|
||||
]
|
||||
2320
sheets/views.py
2320
sheets/views.py
File diff suppressed because it is too large
Load Diff
2873
sheets/views.txt
Normal file
2873
sheets/views.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
~$He-Anlage 2024_1.Halbjahr.ods
Normal file
BIN
~$He-Anlage 2024_1.Halbjahr.ods
Normal file
Binary file not shown.
Reference in New Issue
Block a user