Compare commits

..

3 Commits

Author SHA1 Message Date
b74cf45c5c update 2025-10-08 10:13:07 +02:00
70e055d20b new update 2025-08-28 12:22:27 +02:00
16fd76ff5a New Changes 2025-07-09 14:38:43 +02:00
17 changed files with 1651 additions and 533 deletions

Binary file not shown.

View File

@@ -1,2 +1,45 @@
from django.contrib import admin from django.contrib import admin
from .models import Client # Only import Client
from .models import SecondTableEntry
# Register only the Client model
@admin.register(Client)
class ClientAdmin(admin.ModelAdmin):
list_display = ('name', 'address')
search_fields = ('name',)
# Optional: Customize the add form fields
fields = ['name', 'address']
@admin.register(SecondTableEntry)
class SecondTableEntryAdmin(admin.ModelAdmin):
list_display = ('id', 'client', 'date', 'is_warm', 'lhe_output_short', 'notes_preview')
list_display_links = ('id', 'client') # Fields that link to edit page
list_editable = ('is_warm',)
list_filter = ('is_warm', 'client')
search_fields = ('client__name', 'notes')
date_hierarchy = 'date'
ordering = ('-date',)
fieldsets = (
(None, {
'fields': ('client', 'date')
}),
('LHe Data', {
'fields': ('is_warm', 'lhe_delivery', 'lhe_output'),
'description': 'Enter all liquid helium measurements'
}),
('Additional Info', {
'fields': ('notes',),
'classes': ('collapse',)
})
)
# Custom display methods
def lhe_output_short(self, obj):
return f"{obj.lhe_output} L" if obj.lhe_output else "-"
lhe_output_short.short_description = 'Output'
def notes_preview(self, obj):
return obj.notes[:30] + '...' if obj.notes else ""
notes_preview.short_description = 'Notes Preview'

View File

@@ -1,7 +1,27 @@
from django import forms from django import forms
from .models import ExcelEntry from .models import ExcelEntry, Betriebskosten
class ExcelEntryForm(forms.ModelForm): class ExcelEntryForm(forms.ModelForm):
class Meta: class Meta:
model = ExcelEntry model = ExcelEntry
fields = ['name', 'age', 'email'] # Include only the fields you want users to input fields = '__all__' # Include only the fields you want users to input
class BetriebskostenForm(forms.ModelForm):
class Meta:
model = Betriebskosten
fields = ['buchungsdatum', 'rechnungsnummer', 'kostentyp', 'gas_volume', 'betrag', 'beschreibung']
widgets = {
'buchungsdatum': forms.DateInput(attrs={'type': 'date'}),
'beschreibung': forms.Textarea(attrs={'rows': 3}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['gas_volume'].required = False
# Add price per liter field (readonly)
self.fields['price_per_liter'] = forms.CharField(
label='Preis pro Liter (€)',
required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'})
)

View File

@@ -0,0 +1,47 @@
# Generated by Django 5.2.1 on 2025-07-08 12:13
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='secondtableentry',
name='age',
),
migrations.RemoveField(
model_name='secondtableentry',
name='email',
),
migrations.AddField(
model_name='secondtableentry',
name='date',
field=models.DateField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='secondtableentry',
name='is_warm',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='secondtableentry',
name='lhe_delivery',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AddField(
model_name='secondtableentry',
name='lhe_output',
field=models.CharField(blank=True, max_length=100, null=True),
),
migrations.AddField(
model_name='secondtableentry',
name='notes',
field=models.TextField(blank=True, null=True),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.1 on 2025-07-08 13:18
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0002_remove_secondtableentry_age_and_more'),
]
operations = [
migrations.AlterField(
model_name='secondtableentry',
name='lhe_output',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, validators=[django.core.validators.MinValueValidator(0)]),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-07-08 13:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0003_alter_secondtableentry_lhe_output'),
]
operations = [
migrations.AlterField(
model_name='secondtableentry',
name='lhe_output',
field=models.CharField(blank=True, max_length=100, null=True),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.2.1 on 2025-07-08 13:36
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0004_alter_secondtableentry_lhe_output'),
]
operations = [
migrations.AlterField(
model_name='secondtableentry',
name='lhe_output',
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, validators=[django.core.validators.MinValueValidator(0)]),
),
]

View File

@@ -0,0 +1,43 @@
# Generated by Django 5.2.1 on 2025-07-09 11:15
import django.core.validators
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0005_alter_secondtableentry_lhe_output'),
]
operations = [
migrations.RemoveField(
model_name='excelentry',
name='age',
),
migrations.RemoveField(
model_name='excelentry',
name='email',
),
migrations.AddField(
model_name='excelentry',
name='date',
field=models.DateField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='excelentry',
name='notes',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='excelentry',
name='pressure',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=10, validators=[django.core.validators.MinValueValidator(0)]),
),
migrations.AddField(
model_name='excelentry',
name='purity',
field=models.DecimalField(decimal_places=2, default=0.0, max_digits=5, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)]),
),
]

View File

@@ -0,0 +1,25 @@
# Generated by Django 5.1.5 on 2025-08-28 09:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0006_remove_excelentry_age_remove_excelentry_email_and_more'),
]
operations = [
migrations.CreateModel(
name='Betriebskosten',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('buchungsdatum', models.DateField(verbose_name='Buchungsdatum')),
('rechnungsnummer', models.CharField(max_length=50, verbose_name='Rechnungsnummer')),
('kostentyp', models.CharField(choices=[('sach', 'Sachkosten'), ('ln2', 'LN2'), ('helium', 'Helium'), ('inv', 'Inventar')], max_length=10, verbose_name='Kostentyp')),
('gas_volume', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Gasvolumen (Liter)')),
('betrag', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Betrag (€)')),
('beschreibung', models.TextField(blank=True, verbose_name='Beschreibung')),
],
),
]

View File

@@ -1,4 +1,31 @@
from django.db import models from django.db import models
from django.utils import timezone
from django.core.validators import MinValueValidator, MaxValueValidator
class Betriebskosten(models.Model):
KOSTENTYP_CHOICES = [
('sach', 'Sachkostöen'),
('ln2', 'LN2'),
('helium', 'Helium'),
('inv', 'Inventar'),
]
buchungsdatum = models.DateField('Buchungsdatum')
rechnungsnummer = models.CharField('Rechnungsnummer', 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)
betrag = models.DecimalField('Betrag (€)', max_digits=10, decimal_places=2)
beschreibung = models.TextField('Beschreibung', blank=True)
@property
def price_per_liter(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}" # Fixed the missing quote
class Client(models.Model): class Client(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
@@ -9,12 +36,39 @@ class Client(models.Model):
class ExcelEntry(models.Model): class ExcelEntry(models.Model):
client = models.ForeignKey(Client, on_delete=models.CASCADE) client = models.ForeignKey(Client, on_delete=models.CASCADE)
age = models.IntegerField() date = models.DateField(default=timezone.now)
email = models.EmailField() pressure = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(0)],
default=0.00
)
purity = models.DecimalField(
max_digits=5,
decimal_places=2,
validators=[MinValueValidator(0), MaxValueValidator(100)],
default=0.00
)
notes = models.TextField(blank=True, null=True)
date_joined = models.DateField(auto_now_add=True) date_joined = models.DateField(auto_now_add=True)
def __str__(self):
return f"{self.client.name} - {self.date}"
class SecondTableEntry(models.Model): class SecondTableEntry(models.Model):
client = models.ForeignKey(Client, on_delete=models.CASCADE) client = models.ForeignKey(Client, on_delete=models.CASCADE)
age = models.IntegerField() date = models.DateField(default=timezone.now) # Added default value
email = models.EmailField() is_warm = models.BooleanField(default=False)
lhe_delivery = models.CharField(max_length=100, blank=True, null=True)
lhe_output = models.DecimalField(
max_digits=10,
decimal_places=2,
validators=[MinValueValidator(0)],
blank=True,
null=True
)
notes = models.TextField(blank=True, null=True)
date_joined = models.DateField(auto_now_add=True) date_joined = models.DateField(auto_now_add=True)
def __str__(self):
return f"{self.client.name} - {self.date}"

View File

@@ -3,6 +3,9 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{% block title %}My App{% endblock %}</title> <title>{% block title %}My App{% endblock %}</title>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.css" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.js"></script>

View File

@@ -0,0 +1,479 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Betriebskosten</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.css" />
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f9;
}
.table-container {
width: 100%;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
color: #333;
}
table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
th, td {
padding: 12px;
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 */
.actions {
white-space: nowrap; /* Prevent buttons from wrapping */
}
th {
background-color: #007bff;
color: white;
font-weight: bold;
position: sticky;
top: 0;
}
tr:hover {
background-color: #f1f1f1;
}
.actions button {
margin: 2px;
padding: 5px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.edit-btn {
background-color: #28a745;
color: white;
}
.delete-btn {
background-color: #dc3545;
color: white;
}
.popup {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 1000;
width: 400px;
}
.popup input, .popup select, .popup textarea {
display: block;
margin-bottom: 10px;
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.popup button {
margin-top: 10px;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.close-btn {
cursor: pointer;
float: right;
font-size: 18px;
color: #333;
}
.add-row-btn {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-bottom: 20px;
}
.add-row-btn:hover {
background-color: #0056b3;
}
.save-btn {
background-color: #28a745;
color: white;
}
.cancel-btn {
background-color: #6c757d;
color: white;
}
.help-btn {
background-color: #17a2b8;
color: white;
}
#price-per-liter-group {
display: none;
}
</style>
</head>
<body>
<a href="{% url 'clients_list' %}" class="btn btn-outline-primary">
&#8678; Go to Clients
</a>
<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>
</table>
</div>
<!-- Add Popup -->
<div id="add-popup" class="popup">
<span class="close-btn">&times;</span>
<h3>Betriebskosten Eintrag</h3>
<label for="add-buchungsdatum">Buchungsdatum:</label>
<input type="date" id="add-buchungsdatum" required>
<label for="add-rechnungsnummer">Rechnungsnummer:</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>
<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')">
</div>
<label for="add-betrag">Betrag (€):</label>
<input type="number" id="add-betrag" step="0.01" min="0" required oninput="calculatePrice('add')">
<div id="add-price-per-liter-group" style="display: none;">
<label for="add-price-per-liter">Preis pro Liter (€):</label>
<input type="text" id="add-price-per-liter" readonly>
</div>
<label for="add-beschreibung">Beschreibung:</label>
<textarea id="add-beschreibung" placeholder="Optionale Beschreibung"></textarea>
<div class="popup-buttons">
<button class="save-btn" id="save-add">Save</button>
<button class="cancel-btn">Cancel</button>
<button class="help-btn">Help</button>
</div>
</div>
<!-- Edit Popup -->
<div id="edit-popup" class="popup">
<span class="close-btn">&times;</span>
<h3>Betriebskosten Eintrag</h3>
<input type="hidden" id="edit-id">
<label for="edit-buchungsdatum">Buchungsdatum:</label>
<input type="date" id="edit-buchungsdatum" required>
<label for="edit-rechnungsnummer">Rechnungsnummer:</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>
<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')">
</div>
<label for="edit-betrag">Betrag (€):</label>
<input type="number" id="edit-betrag" step="0.01" min="0" required oninput="calculatePrice('edit')">
<div id="edit-price-per-liter-group" style="display: none;">
<label for="edit-price-per-liter">Preis pro Liter (€):</label>
<input type="text" id="edit-price-per-liter" readonly>
</div>
<label for="edit-beschreibung">Beschreibung:</label>
<textarea id="edit-beschreibung" placeholder="Optionale Beschreibung"></textarea>
<div class="popup-buttons">
<button class="save-btn" id="save-edit">Save</button>
<button class="cancel-btn">Cancel</button>
<button class="help-btn">Help</button>
</div>
</div>
<script>
$(document).ready(function () {
// Open add popup
$('#add-row-btn').on('click', function () {
$('#add-popup').fadeIn();
});
// Close popups
$('.close-btn, .cancel-btn').on('click', function () {
$('.popup').fadeOut();
});
// Add new entry
$('#save-add').on('click', function() {
const formData = {
'buchungsdatum': $('#add-buchungsdatum').val(),
'rechnungsnummer': $('#add-rechnungsnummer').val(),
'kostentyp': $('#add-kostentyp').val(),
'gas_volume': $('#add-gas-volume').val(),
'betrag': $('#add-betrag').val(),
'beschreibung': $('#add-beschreibung').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
// Validate required fields
if (!formData.buchungsdatum || !formData.rechnungsnummer || !formData.kostentyp || !formData.betrag) {
alert('Bitte füllen Sie alle erforderlichen Felder aus');
return;
}
$.ajax({
url: "{% url 'betriebskosten_create' %}",
method: 'POST',
data: formData,
success: function(response) {
if (response.status === 'success') {
// Add the new row to the table
const newRow = `
<tr data-id="${response.id}">
<td>${$('#betriebskosten-table tbody tr').length + 1}</td>
<td>${response.buchungsdatum}</td>
<td>${response.rechnungsnummer}</td>
<td>${response.kostentyp_display}</td>
<td>${response.gas_volume || '-'}</td>
<td>${response.betrag}</td>
<td>${response.beschreibung || ''}</td>
<td class="actions">
<button class="edit-btn">Edit</button>
<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('');
} else {
alert('Error: ' + (response.message || 'Failed to add entry'));
}
},
error: function(xhr) {
alert('Error: ' + (xhr.responseJSON?.message || 'Server error'));
}
});
});
// Edit entry
$(document).on('click', '.edit-btn', function() {
const row = $(this).closest('tr');
const id = row.data('id');
// 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());
// Set kostentyp based on display text
const kostentypText = row.find('td:eq(3)').text();
const kostentypMap = {
'Sachkosten': 'sach',
'LN2': 'ln2',
'Helium': 'helium',
'Inventar': 'inv'
};
$('#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());
// Show/hide gas volume based on kostentyp
toggleGasVolume('edit');
calculatePrice('edit');
$('#edit-popup').fadeIn();
});
// Save edit
$('#save-edit').on('click', function() {
const formData = {
'id': $('#edit-id').val(),
'buchungsdatum': $('#edit-buchungsdatum').val(),
'rechnungsnummer': $('#edit-rechnungsnummer').val(),
'kostentyp': $('#edit-kostentyp').val(),
'gas_volume': $('#edit-gas-volume').val(),
'betrag': $('#edit-betrag').val(),
'beschreibung': $('#edit-beschreibung').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
// Validate required fields
if (!formData.buchungsdatum || !formData.rechnungsnummer || !formData.kostentyp || !formData.betrag) {
alert('Bitte füllen Sie alle erforderlichen Felder aus');
return;
}
$.ajax({
url: "{% url 'betriebskosten_create' %}",
method: 'POST',
data: formData,
success: function(response) {
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 || '');
$('#edit-popup').fadeOut();
} else {
alert('Error: ' + (response.message || 'Failed to update entry'));
}
},
error: function(xhr) {
alert('Error: ' + (xhr.responseJSON?.message || 'Server error'));
}
});
});
// Delete entry
$(document).on('click', '.delete-btn', function () {
const row = $(this).closest('tr');
const id = row.data('id');
if (!confirm('Are you sure you want to delete this entry?')) return;
$.ajax({
url: "{% url 'betriebskosten_delete' %}",
method: 'POST',
data: {
'id': id,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (response) {
if (response.status === 'success') {
row.fadeOut(300, function () { $(this).remove(); });
// Update row numbers
$('#betriebskosten-table tbody tr').each(function(index) {
$(this).find('td:first').text(index + 1);
});
} else {
alert('Failed to delete entry: ' + response.message);
}
},
error: function () {
alert('Failed to delete entry. Please try again.');
}
});
});
});
// Toggle gas volume field based on kostentyp
function toggleGasVolume(type) {
const kostentyp = $(`#${type}-kostentyp`).val();
const gasVolumeGroup = $(`#${type}-gas-volume-group`);
const pricePerLiterGroup = $(`#${type}-price-per-liter-group`);
if (kostentyp === 'helium') {
gasVolumeGroup.show();
pricePerLiterGroup.show();
} else {
gasVolumeGroup.hide();
pricePerLiterGroup.hide();
$(`#${type}-gas-volume`).val('');
$(`#${type}-price-per-liter`).val('');
}
}
// Calculate price per liter
function calculatePrice(type) {
const kostentyp = $(`#${type}-kostentyp`).val();
const volume = 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');
} else {
$(`#${type}-price-per-liter`).val('');
}
}
</script>
</body>
</html>

View File

@@ -1,289 +1,139 @@
<!DOCTYPE html> {% extends "base.html" %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Clients Table</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.1/jquery.modal.min.css" />
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f9;
}
.add-row-btn, .btn-go-back {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-bottom: 20px;
}
.add-row-btn:hover,
.btn-go-back:hover {
background-color: #0056b3;
}
.container {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
h2 {
text-align: center;
color: #333;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #007bff;
color: white;
font-weight: bold;
}
tr:hover {
background-color: #f1f1f1;
}
.actions button {
margin: 2px;
padding: 5px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.edit-btn {
background-color: #28a745;
color: white;
}
.delete-btn {
background-color: #dc3545;
color: white;
}
.popup {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 1000;
}
.popup input {
display: block;
margin-bottom: 10px;
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.popup button {
margin-top: 10px;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.close-btn {
cursor: pointer;
float: right;
font-size: 18px;
color: #333;
}
</style>
</head>
<body>
{% block content %}
<div class="container">
<!-- Title -->
<h1 class="page-title">Helium Output Yearly Summary</h1>
<div class="container"> <!-- Year Filter -->
<h2>Clients Table</h2> <div class="year-filter">
<button class="add-row-btn" id="add-client">Add Client</button> <label for="year-select">Year:</label>
<table> <select id="year-select" onchange="window.location.href='?year='+this.value">
{% for year in available_years %}
<option value="{{ year }}" {% if year == current_year %}selected{% endif %}>
{{ year }}
</option>
{% endfor %}
</select>
</div>
<!-- Annual Summary Table -->
<table class="summary-table">
<thead> <thead>
<tr> <tr>
<th>#</th> <!-- This is your new sequential number column --> <th>Client</th>
<th>ID</th> {% for month in months %}
<th>Name</th> <th>{{ month }}</th>
<th>Address</th> {% endfor %}
<th>Actions</th> <th>Total</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for client in clients %} {% for data in monthly_data %}
<tr data-id="{{ client.id }}"> <tr>
<td>{{ forloop.counter }}</td> <!-- ← this gives you 1, 2, 3... --> <td>{{ data.client.name }}</td>
<td>{{ client.id }}</td> {% for total in data.monthly_totals %}
<td>{{ client.name }}</td> <td>{{ total|floatformat:2 }}</td>
<td>{{ client.address }}</td> {% endfor %}
<td class="actions"> <td class="total">{{ data.year_total|floatformat:2 }}</td>
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<!-- Navigation Buttons -->
<div class="navigation-buttons">
<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>
</div> </div>
</div>
<!-- Add Client Popup --> <style>
<div id="add-popup" class="popup"> .container {
<span class="close-btn">&times;</span> max-width: 1200px;
<h3>Add Client</h3> margin: 0 auto;
<input type="text" id="add-name" placeholder="Name"> padding: 20px;
<input type="text" id="add-address" placeholder="Address">
<button id="save-add">Add</button>
</div>
<!-- Edit Client Popup -->
<div id="edit-popup" class="popup">
<span class="close-btn">&times;</span>
<h3>Edit Client</h3>
<input type="hidden" id="edit-id">
<input type="text" id="edit-name" placeholder="Name">
<input type="text" id="edit-address" placeholder="Address">
<button id="save-edit">Save</button>
</div>
<div style="margin-top: 30px; text-align: center;">
<a href="{% url 'table_one' %}">
<button class="add-row-btn">Go to Helium input</button>
</a>
<a href="{% url 'table_two' %}">
<button class="add-row-btn">Go to Table output</button>
</a>
</div>
<script>
let currentModelName = "Client";
// Open Add Popup
$('#add-client').on('click', function () {
$('#add-popup').fadeIn();
});
// Close Popups
$('.close-btn').on('click', function () {
$('.popup').fadeOut();
});
// Save Add Client
$('#save-add').on('click', function () {
let name = $('#add-name').val();
let address = $('#add-address').val();
$.ajax({
url: `/add-entry/Client/`,
method: 'POST',
data: {
'name': name,
'address': address,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (response) {
let rowCount = $('tbody tr').length + 1;
let newRow = `
<tr data-id="${response.id}">
<td>${rowCount}</td>
<td>${response.id}</td>
<td>${response.name}</td>
<td>${response.address}</td>
<td class="actions">
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
</td>
</tr>
`;
$('tbody').append(newRow);
$('#add-popup').fadeOut();
},
error: function (xhr) {
alert('Failed to add client: ' + xhr.responseText);
} }
});
});
// Open Edit Popup .page-title {
$(document).on('click', '.edit-btn', function () { text-align: center;
let row = $(this).closest('tr'); color: #333;
$('#edit-id').val(row.data('id')); margin-bottom: 20px;
$('#edit-name').val(row.find('td:eq(2)').text()); // Name
$('#edit-address').val(row.find('td:eq(3)').text()); // Address
$('#edit-popup').fadeIn();
});
// Save Edit Client
$('#save-edit').on('click', function () {
let id = $('#edit-id').val();
let name = $('#edit-name').val();
let address = $('#edit-address').val();
$.ajax({
url: `/update-entry/${currentModelName}/`,
method: 'POST',
data: {
'id': id,
'name': name,
'address': address,
'age': 0,
'email': 'none@example.com',
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (response) {
if (response.status === 'success') {
let row = $(`tr[data-id="${id}"]`);
row.find('td:eq(2)').text(name); // Correct column for Name
row.find('td:eq(3)').text(address); // Correct column for Address
$('#edit-popup').fadeOut();
} }
.year-filter {
margin: 20px 0;
text-align: right;
} }
});
});
// Delete Client .year-filter label {
$(document).on('click', '.delete-btn', function () { margin-right: 10px;
let row = $(this).closest('tr');
let id = row.data('id');
if (!confirm('Are you sure you want to delete this client?')) return;
$.ajax({
url: `/delete-entry/${currentModelName}/`,
method: 'POST',
data: {
'id': id,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (response) {
if (response.status === 'success') {
row.fadeOut(300, function () { $(this).remove(); });
} else {
alert('Delete failed: ' + response.message);
} }
},
error: function (xhr, status, error) {
alert('Delete request failed:\n' + xhr.responseText);
console.log('Error:', error);
console.log('Status:', status);
}
});
});
</script>
</body> .year-filter select {
</html> padding: 5px;
border-radius: 4px;
border: 1px solid #ddd;
}
.summary-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
.summary-table th, .summary-table td {
padding: 10px;
text-align: center;
border: 1px solid #ddd;
}
.summary-table th {
background-color: #007bff;
color: white;
}
.summary-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.summary-table tr:hover {
background-color: #f1f1f1;
}
.total {
font-weight: bold;
background-color: #e6f2ff;
}
.navigation-buttons {
display: flex;
justify-content: center;
gap: 20px;
margin-top: 30px;
}
.nav-button {
padding: 12px 24px;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
transition: background-color 0.3s;
}
.nav-button:hover {
background-color: #0056b3;
}
.admin-button {
background-color: #6c757d;
}
.admin-button:hover {
background-color: #5a6268;
}
</style>
{% endblock %}

View File

@@ -27,18 +27,32 @@
} }
table { table {
width: 100%; width: 100%;
table-layout: fixed;
border-collapse: collapse; border-collapse: collapse;
margin-top: 20px;
} }
th, td { th, td {
padding: 12px; padding: 12px;
text-align: left; text-align: center;
border-bottom: 1px solid #ddd; 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: 5%; } /* ID column */
th:nth-child(3), td:nth-child(3) { width: 15%; } /* Client column */
th:nth-child(4), td:nth-child(4) { width: 10%; } /* Entry 1 (Pressure) */
th:nth-child(5), td:nth-child(5) { width: 10%; } /* Entry 2 (Purity) */
th:nth-child(6), td:nth-child(6) { width: 15%; } /* Date Joined */
th:nth-child(7), td:nth-child(7) { width: 25%; } /* Notes */
th:nth-child(7), td:nth-child(7) { width: 30%; } /* Actions */
.actions {
white-space: nowrap; /* Prevent buttons from wrapping */
}
th { th {
background-color: #007bff; background-color: #007bff;
color: white; color: white;
font-weight: bold; font-weight: bold;
position: sticky;
top: 0;
} }
tr:hover { tr:hover {
background-color: #f1f1f1; background-color: #f1f1f1;
@@ -106,6 +120,7 @@
.add-row-btn:hover { .add-row-btn:hover {
background-color: #0056b3; background-color: #0056b3;
} }
</style> </style>
</head> </head>
<body> <body>
@@ -118,14 +133,25 @@
<div class="table-container"> <div class="table-container">
<button class="add-row-btn" id="add-row-one">Add Row</button> <button class="add-row-btn" id="add-row-one">Add Row</button>
<table id="table-one"> <table id="table-one">
<colgroup>
<col style="width: 5%"> <!-- # -->
<col style="width: 5%"> <!-- ID -->
<col style="width: 15%"> <!-- Client -->
<col style="width: 10%"> <!-- Druck -->
<col style="width: 10%"> <!-- Reinheit -->
<col style="width: 15%"> <!-- Date Joined -->
<col style="width: 25%"> <!-- Notes -->
<col style="width: 15%"> <!-- Actions -->
</colgroup>
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
<th>ID</th> <th>ID</th>
<th>Client</th> <th>Client</th>
<th>Entry 1</th> <th>Druck</th>
<th>Entry 2</th> <th>Reinheit</th>
<th>Date Joined</th> <th>Date Joined</th>
<th>Notes</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@@ -135,9 +161,10 @@
<td>{{ forloop.counter }}</td> <td>{{ forloop.counter }}</td>
<td>{{ entry.id }}</td> <td>{{ entry.id }}</td>
<td>{{ entry.client.name }}</td> <td>{{ entry.client.name }}</td>
<td>{{ entry.age }}</td> <td>{{ entry.pressure|floatformat:2 }}</td>
<td>{{ entry.email }}</td> <td>{{ entry.purity|floatformat:2 }}</td>
<td>{{ entry.date_joined }}</td> <td>{{ entry.date_joined|date:"Y-m-d" }}</td>
<td>{{ entry.notes|default:"" }}</td>
<td class="actions"> <td class="actions">
<button class="edit-btn-one">Edit</button> <button class="edit-btn-one">Edit</button>
<button class="delete-btn-one">Delete</button> <button class="delete-btn-one">Delete</button>
@@ -152,30 +179,72 @@
<!-- Add Popup --> <!-- Add Popup -->
<div id="add-popup-one" class="popup"> <div id="add-popup-one" class="popup">
<span class="close-btn">&times;</span> <span class="close-btn">&times;</span>
<h3>Add Entry</h3> <h3>He Gas Bundle</h3>
<label for="add-client-id">Kunde:</label>
<select id="add-client-id"> <select id="add-client-id">
{% for client in clients %} {% for client in clients %}
<option value="{{ client.id }}">{{ client.name }}</option> <option value="{{ client.id }}">{{ client.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="number" id="add-age" placeholder="Age">
<input type="email" id="add-email" placeholder="Email"> <label for="add-date">Date:</label>
<button id="save-add-one">Add</button> <input type="date" id="add-date">
<div class="input-with-label">
<label for="add-pressure">Druck:</label>
<input type="number" id="add-pressure" placeholder="Enter pressure" step="0.01" min="0">
</div>
<div class="input-with-label">
<label for="add-purity">Reinheit:</label>
<input type="number" id="add-purity" placeholder="Enter purity" step="0.01" min="0" max="100">
</div>
<label for="add-notes">Notes:</label>
<textarea id="add-notes" placeholder="Additional notes"></textarea>
<div class="popup-buttons">
<button class="save-btn" id="save-add-one">Save</button>
<button class="cancel-btn">Cancel</button>
<button class="help-btn">Help</button>
</div>
</div> </div>
<!-- Edit Popup --> <!-- Edit Popup -->
<div id="edit-popup-one" class="popup"> <div id="edit-popup-one" class="popup">
<span class="close-btn">&times;</span> <span class="close-btn">&times;</span>
<h3>Edit Entry</h3> <h3>He Gas Bundle</h3>
<input type="hidden" id="edit-id"> <input type="hidden" id="edit-id">
<label for="edit-client-id">Kunde:</label>
<select id="edit-client-id"> <select id="edit-client-id">
{% for client in clients %} {% for client in clients %}
<option value="{{ client.id }}">{{ client.name }}</option> <option value="{{ client.id }}">{{ client.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="number" id="edit-age" placeholder="Age">
<input type="email" id="edit-email" placeholder="Email"> <label for="edit-date">Date:</label>
<button id="save-edit-one">Save</button> <input type="date" id="edit-date">
<div class="input-with-label">
<label for="edit-pressure">Druck:</label>
<input type="number" id="edit-pressure" placeholder="Enter pressure" step="0.01" min="0">
</div>
<div class="input-with-label">
<label for="edit-purity">Reinheit:</label>
<input type="number" id="edit-purity" placeholder="Enter purity" step="0.01" min="0" max="100">
</div>
<label for="edit-notes">Notes:</label>
<textarea id="edit-notes" placeholder="Additional notes"></textarea>
<div class="popup-buttons">
<button class="save-btn" id="save-edit-one">Save</button>
<button class="cancel-btn">Cancel</button>
<button class="help-btn">Help</button>
</div>
</div> </div>
<script> <script>
@@ -192,81 +261,135 @@
}); });
// Add // Add
$('#save-add-one').on('click', function () { $('#save-add-one').on('click', function() {
let client_id = $('#add-client-id').val(); // Validate date first
let age = $('#add-age').val(); let dateInput = $('#add-date').val();
let email = $('#add-email').val(); if (!dateInput) {
alert('Please select a date');
return;
}
// Validate numbers
let pressure = parseFloat($('#add-pressure').val());
let purity = parseFloat($('#add-purity').val());
if (isNaN(pressure) || pressure < 0) {
alert('Please enter a valid pressure value');
return;
}
if (isNaN(purity) || purity < 0 || purity > 100) {
alert('Please enter a valid purity value (0-100)');
return;
}
// Prepare the form data
let formData = {
'client_id': $('#add-client-id').val(),
'date': dateInput,
'pressure': pressure,
'purity': purity,
'notes': $('#add-notes').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
$.ajax({ $.ajax({
url: `/add-entry/${currentModelName}/`, url: '/add-entry/ExcelEntry/',
method: 'POST', method: 'POST',
data: { data: formData,
'client_id': client_id, success: function(response) {
'age': age, if (response.status === 'success') {
'email': email, // Add the new row to the table
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (response) {
let rowCount = $(`#${currentTableId} tbody tr`).length + 1;
let newRow = ` let newRow = `
<tr data-id="${response.id}"> <tr data-id="${response.id}">
<td>${rowCount}</td> <td>${$('#table-one tbody tr').length + 1}</td>
<td>${response.id}</td> <td>${response.id}</td>
<td>${response.client_name}</td> <td>${response.client_name}</td>
<td>${response.age}</td> <td>${response.pressure}</td>
<td>${response.email}</td> <td>${response.purity}</td>
<td>${response.date_joined}</td> <td>${response.date || ''}</td>
<td>${response.notes || ''}</td>
<td class="actions"> <td class="actions">
<button class="edit-btn-one">Edit</button> <button class="edit-btn-one">Edit</button>
<button class="delete-btn-one">Delete</button> <button class="delete-btn-one">Delete</button>
</td> </td>
</tr> </tr>
`; `;
$(`#${currentTableId} tbody`).append(newRow); $('#table-one tbody').append(newRow);
$('#add-popup-one').fadeOut(); $('#add-popup-one').fadeOut();
} else {
alert('Error: ' + (response.message || 'Failed to add entry'));
}
},
error: function(xhr) {
alert('Error: ' + (xhr.responseJSON?.message || 'Server error'));
} }
}); });
}); });
// Edit // Edit
$(document).on('click', '.edit-btn-one', function () { $(document).on('click', '.edit-btn-one', function() {
let row = $(this).closest('tr'); let row = $(this).closest('tr');
$('#edit-id').val(row.data('id')); $('#edit-id').val(row.data('id'));
$('#edit-client-id').val(row.find('td:eq(2)').text()); $('#edit-client-id').val(row.find('td:eq(2)').text().trim());
$('#edit-age').val(row.find('td:eq(3)').text());
$('#edit-email').val(row.find('td:eq(4)').text()); // Handle date - ensure it's in correct format
let dateText = row.find('td:eq(5)').text().trim();
if (dateText) {
$('#edit-date').val(dateText);
}
$('#edit-pressure').val(row.find('td:eq(3)').text().trim());
$('#edit-purity').val(row.find('td:eq(4)').text().trim());
$('#edit-notes').val(row.find('td:eq(6)').text().trim());
$('#edit-popup-one').fadeIn(); $('#edit-popup-one').fadeIn();
}); });
$('#save-edit-one').on('click', function () { // Update the save-edit-one handler
let id = $('#edit-id').val(); $('#save-edit-one').on('click', function() {
let client_id = $('#edit-client-id').val(); let formData = {
let age = $('#edit-age').val(); 'id': $('#edit-id').val(),
let email = $('#edit-email').val(); 'client_id': $('#edit-client-id').val(),
'date': $('#edit-date').val(), // Already in YYYY-MM-DD format
'pressure': $('#edit-pressure').val(),
'purity': $('#edit-purity').val(),
'notes': $('#edit-notes').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
// Validate inputs
if (!formData.date) {
alert('Please select a date');
return;
}
if (isNaN(formData.pressure) || formData.pressure < 0) {
alert('Please enter a valid pressure value');
return;
}
if (isNaN(formData.purity) || formData.purity < 0 || formData.purity > 100) {
alert('Please enter a valid purity value (0-100)');
return;
}
$.ajax({ $.ajax({
url: `/update-entry/${currentModelName}/`, url: '/update-entry/ExcelEntry/',
method: 'POST', method: 'POST',
data: { data: formData,
'id': id, success: function(response) {
'client_id': client_id,
'age': age,
'email': email,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (response) {
if (response.status === 'success') { if (response.status === 'success') {
let row = $(`tr[data-id="${response.id}"]`); let row = $(`tr[data-id="${response.id}"]`);
row.find('td:eq(2)').text(response.name); row.find('td:eq(2)').text(response.client_name);
row.find('td:eq(3)').text(response.age); row.find('td:eq(3)').text(response.pressure);
row.find('td:eq(4)').text(response.email); row.find('td:eq(4)').text(response.purity);
row.find('td:eq(5)').text(response.date || '');
row.find('td:eq(6)').text(response.notes || '');
$('#edit-popup-one').fadeOut(); $('#edit-popup-one').fadeOut();
} else { } else {
alert('Failed to update entry: ' + response.message); alert('Error: ' + (response.message || 'Failed to update entry'));
} }
}, },
error: function () { error: function(xhr) {
alert('Failed to update entry. Please try again.'); alert('Error: ' + (xhr.responseJSON?.message || 'Server error'));
} }
}); });
}); });

View File

@@ -75,23 +75,56 @@
border-radius: 5px; border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 1000; z-index: 1000;
width: 400px;
} }
.popup input { .popup h3 {
margin-top: 0;
border-bottom: 1px solid #ddd;
padding-bottom: 10px;
}
.popup label {
display: block; display: block;
margin-bottom: 10px; margin-bottom: 5px;
font-weight: bold;
}
.popup input, .popup select, .popup textarea {
display: block;
margin-bottom: 15px;
width: 100%; width: 100%;
padding: 8px; padding: 8px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
box-sizing: border-box;
}
.popup textarea {
height: 100px;
resize: vertical;
}
.popup-buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
} }
.popup button { .popup button {
margin-top: 10px;
padding: 8px 16px; padding: 8px 16px;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px;
} }
.save-btn {
background-color: #28a745;
color: white;
}
.cancel-btn {
background-color: #6c757d;
color: white;
}
.help-btn {
background-color: #17a2b8;
color: white;
}
.close-btn { .close-btn {
cursor: pointer; cursor: pointer;
float: right; float: right;
@@ -111,14 +144,29 @@
.add-row-btn:hover { .add-row-btn:hover {
background-color: #0056b3; background-color: #0056b3;
} }
.popup select { .checkbox-container {
display: block; display: flex;
margin-bottom: 10px; align-items: center;
width: 100%; margin-bottom: 15px;
padding: 8px; }
border: 1px solid #ddd; .checkbox-container input[type="checkbox"] {
border-radius: 4px; width: auto;
font-family: Arial, sans-serif; margin-right: 10px;
margin-bottom: 0;
}
.input-with-label {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.input-with-label label {
width: 120px;
margin-bottom: 0;
margin-right: 10px;
}
.input-with-label input {
flex: 1;
margin-bottom: 0;
} }
</style> </style>
</head> </head>
@@ -131,23 +179,21 @@
</a> </a>
</div> </div>
<h2>Helium Output</h2> <h2>LHe Dewar Output</h2>
<!-- Second Table -->
<div class="table-container"> <div class="table-container">
<button class="add-row-btn" id="add-row-two">Add Row</button> <button class="add-row-btn" id="add-row-two">Add Output</button>
<table id="table-two"> <table id="table-two">
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
<th>ID</th> <th>ID</th>
<th>Client</th> <th>Client</th>
<th>Entry 1</th> <th>Date</th>
<th>Entry 2</th> <th>Warm</th>
<th>Date Joined</th> <th>LHe Delivery</th>
<th>LHe Output</th>
<th>Notes</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@@ -157,9 +203,11 @@
<td>{{ forloop.counter }}</td> <td>{{ forloop.counter }}</td>
<td>{{ entry.id }}</td> <td>{{ entry.id }}</td>
<td>{{ entry.client.name }}</td> <td>{{ entry.client.name }}</td>
<td>{{ entry.age }}</td> <td>{{ entry.date }}</td>
<td>{{ entry.email }}</td> <td>{{ entry.is_warm|yesno:"Yes,No" }}</td>
<td>{{ entry.date_joined }}</td> <td>{{ entry.lhe_delivery }}</td>
<td>{{ entry.lhe_output }}</td>
<td>{{ entry.notes }}</td>
<td class="actions"> <td class="actions">
<button class="edit-btn-two">Edit</button> <button class="edit-btn-two">Edit</button>
<button class="delete-btn-two">Delete</button> <button class="delete-btn-two">Delete</button>
@@ -169,133 +217,243 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
<!-- Add Entry Popup --> <!-- Add Entry Popup -->
<div id="add-popup-two" class="popup"> <div id="add-popup-two" class="popup">
<span class="close-btn">&times;</span> <span class="close-btn">&times;</span>
<h3>Add Entry</h3> <h3>LHe Dewar Output</h3>
<label for="add-client-id">Client:</label>
<select id="add-client-id"> <select id="add-client-id">
{% for client in clients %} {% for client in clients %}
<option value="{{ client.id }}">{{ client.name }}</option> <option value="{{ client.id }}">{{ client.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="number" id="add-age" placeholder="Age"> <label for="add-date">Date:</label>
<input type="email" id="add-email" placeholder="Email"> <input type="date" id="add-date">
<button id="save-add-two">Add</button>
<div class="checkbox-container">
<input type="checkbox" id="add-is-warm">
<label for="add-is-warm">Warm</label>
</div>
<div class="input-with-label">
<label for="add-lhe-delivery">LHe Anlieferung:</label>
<input type="text" id="add-lhe-delivery" placeholder="Enter delivery amount">
</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" placeholder="Enter output amount">
</div>
<label for="add-notes">Notes:</label>
<textarea id="add-notes" placeholder="Additional notes"></textarea>
<div class="popup-buttons">
<button class="save-btn" id="save-add-two">Save</button>
<button class="cancel-btn" id="cancel-add-two">Cancel</button>
<button class="help-btn">Help</button>
</div>
</div> </div>
<!-- Edit Entry Popup --> <!-- Edit Entry Popup -->
<div id="edit-popup-two" class="popup"> <div id="edit-popup-two" class="popup">
<span class="close-btn">&times;</span> <span class="close-btn">&times;</span>
<h3>Edit Entry</h3> <h3>LHe Dewar Output</h3>
<input type="hidden" id="edit-id"> <input type="hidden" id="edit-id">
<label for="edit-client-id">Client:</label>
<select id="edit-client-id"> <select id="edit-client-id">
{% for client in clients %} {% for client in clients %}
<option value="{{ client.id }}">{{ client.name }}</option> <option value="{{ client.id }}">{{ client.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="number" id="edit-age" placeholder="Age"> <label for="edit-date">Date:</label>
<input type="email" id="edit-email" placeholder="Email"> <input type="date" id="edit-date">
<button id="save-edit-two">Save</button>
<div class="checkbox-container">
<input type="checkbox" id="edit-is-warm">
<label for="edit-is-warm">Warm</label>
</div>
<div class="input-with-label">
<label for="edit-lhe-delivery">LHe Anlieferung:</label>
<input type="text" id="edit-lhe-delivery" placeholder="Enter delivery amount">
</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">
</div>
<label for="edit-notes">Notes:</label>
<textarea id="edit-notes" placeholder="Additional notes"></textarea>
<div class="popup-buttons">
<button class="save-btn" id="save-edit-two">Save</button>
<button class="cancel-btn" id="cancel-edit-two">Cancel</button>
<button class="help-btn">Help</button>
</div>
</div> </div>
<script> <script>
$(document).ready(function () { $(document).ready(function () {
let currentTableId = null; // To track which table is being edited let currentTableId = null;
let currentModelName = null; // To track which model is being used let currentModelName = null;
// Open Add Popup for Table 2 // Open Add Popup for Table 2
$('#add-row-two').on('click', function () { $('#add-row-two').on('click', function () {
currentTableId = 'table-two'; currentTableId = 'table-two';
currentModelName = 'SecondTableEntry'; // Model name for Table 2 currentModelName = 'SecondTableEntry';
$('#add-popup-two').fadeIn(); $('#add-popup-two').fadeIn();
}); });
// Close Popups // Close Popups
$('.close-btn').on('click', function () { $('.close-btn, .cancel-btn').on('click', function () {
$('.popup').fadeOut(); $('.popup').fadeOut();
}); });
// Save Add Entry // Save Add Entry
$('#save-add-two').on('click', function () { $('#save-add-two').on('click', function() {
let client_id = $('#add-client-id').val(); // ✅ Correct variable let formData = {
let age = $('#add-age').val(); 'client_id': $('#add-client-id').val(),
let email = $('#add-email').val(); 'date': $('#add-date').val(), // Ensure this is in YYYY-MM-DD format
'is_warm': $('#add-is-warm').is(':checked'),
'lhe_delivery': $('#add-lhe-delivery').val(),
'lhe_output': $('#add-lhe-output').val(),
'notes': $('#add-notes').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
// Validate date format
if (!formData.date) {
alert('Please select a date');
return;
}
// Validate LHe Output is a number
if (isNaN(parseFloat(formData.lhe_output))) {
alert('LHe Output must be a valid number');
return;
}
$.ajax({ $.ajax({
url: `/add-entry/${currentModelName}/`, url: '/add-entry/SecondTableEntry/',
method: 'POST', method: 'POST',
data: { data: formData,
'client_id': client_id, // ✅ REPLACE 'name' with this success: function(response) {
'age': age, if (response.status === 'success') {
'email': email, // Add the new row to the table
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (response) {
let rowCount = $('#table-two tbody tr').length + 1;
let newRow = ` let newRow = `
<tr data-id="${response.id}"> <tr data-id="${response.id}">
<td>${rowCount}</td> <!-- Serial # --> <td>${$('#table-two tbody tr').length + 1}</td>
<td>${response.id}</td> <!-- ID --> <td>${response.id}</td>
<td>${response.client_name}</td> <!-- Name --> <td>${response.client_name}</td>
<td>${response.age}</td> <!-- Age --> <td>${response.date || ''}</td>
<td>${response.email}</td> <!-- Email --> <td>${response.is_warm ? 'Yes' : 'No'}</td>
<td>${response.date_joined}</td> <!-- Date Joined --> <td>${response.lhe_delivery}</td>
<td class="actions"> <!-- Actions --> <td>${response.lhe_output || ''}</td>
<td>${response.notes || ''}</td>
<td class="actions">
<button class="edit-btn-two">Edit</button> <button class="edit-btn-two">Edit</button>
<button class="delete-btn-two">Delete</button> <button class="delete-btn-two">Delete</button>
</td> </td>
</tr> </tr>
`; `;
$(`#${currentTableId} tbody`).append(newRow); $('#table-two tbody').append(newRow);
$('#add-popup-two').fadeOut(); $('#add-popup-two').fadeOut();
} else {
alert('Error: ' + (response.message || 'Failed to add entry'));
}
},
error: function(xhr) {
alert('Error: ' + (xhr.responseJSON?.message || 'Server error'));
} }
}); });
}); });
// Add this new function to load table data
function loadTableData() {
$.ajax({
url: window.location.href,
method: 'GET',
success: function(data) {
// Parse the HTML response to extract the table body
let newBody = $(data).find('#table-two tbody').html();
$('#table-two tbody').html(newBody);
},
error: function() {
alert('Failed to refresh table data');
}
});
}
// Open Edit Popup // Open Edit Popup
$(document).on('click', '.edit-btn-two', function () { $(document).on('click', '.edit-btn-two', function() {
let row = $(this).closest('tr'); let row = $(this).closest('tr');
currentTableId = row.closest('table').attr('id'); // Set current table ID
currentModelName = 'SecondTableEntry'; // Set model name
$('#edit-id').val(row.data('id')); $('#edit-id').val(row.data('id'));
$('#edit-client-id').val(row.find('td:eq(2)').text()); // Name is now in column 2
$('#edit-age').val(row.find('td:eq(3)').text()); // Age is now in column 3 // Set client - find by name since we're showing names in the table
$('#edit-email').val(row.find('td:eq(4)').text()); // Email is now in column 4 let clientName = row.find('td:eq(2)').text().trim();
$(`#edit-client-id option:contains("${clientName}")`).prop('selected', true);
// Set date - ensure proper format
let dateText = row.find('td:eq(3)').text().trim();
if (dateText) {
$('#edit-date').val(dateText);
}
// Set other fields
$('#edit-is-warm').prop('checked', row.find('td:eq(4)').text().trim() === 'Yes');
$('#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-popup-two').fadeIn(); $('#edit-popup-two').fadeIn();
}); });
// Save Edit Entry // Save Edit Entry
$('#save-edit-two').on('click', function () { $('#save-edit-two').on('click', function() {
let id = $('#edit-id').val(); let formData = {
let client_id = $('#edit-client-id').val(); 'id': $('#edit-id').val(),
let age = $('#edit-age').val(); 'client_id': $('#edit-client-id').val(),
let email = $('#edit-email').val(); 'date': $('#edit-date').val(), // Already in YYYY-MM-DD format
'is_warm': $('#edit-is-warm').is(':checked'),
'lhe_delivery': $('#edit-lhe-delivery').val(),
'lhe_output': $('#edit-lhe-output').val(),
'notes': $('#edit-notes').val(),
'csrfmiddlewaretoken': '{{ csrf_token }}'
};
// Validate inputs
if (!formData.date) {
alert('Please select a date');
return;
}
if (isNaN(parseFloat(formData.lhe_output))) {
alert('Please enter a valid LHe Output value');
return;
}
$.ajax({ $.ajax({
url: `/update-entry/${currentModelName}/`, url: '/update-entry/SecondTableEntry/',
method: 'POST', method: 'POST',
data: { data: formData,
'id': id, success: function(response) {
'client_id': client_id,
'age': age,
'email': email,
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
success: function (response) {
if (response.status === 'success') { if (response.status === 'success') {
let row = $(`tr[data-id="${response.id}"]`); let row = $(`tr[data-id="${response.id}"]`);
row.find('td:eq(2)').text(response.name); row.find('td:eq(2)').text(response.client_name);
row.find('td:eq(3)').text(response.age); row.find('td:eq(3)').text(response.date || '');
row.find('td:eq(4)').text(response.email); 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 || '');
$('#edit-popup-two').fadeOut(); $('#edit-popup-two').fadeOut();
} else { } else {
alert('Failed to update entry: ' + response.message); alert('Update failed: ' + (response.message || 'Unknown error'));
} }
}, },
error: function () { error: function(xhr) {
alert('Failed to update entry. Please try again.'); alert('Error: ' + (xhr.responseJSON?.message || 'Server error'));
} }
}); });
}); });
@@ -304,8 +462,8 @@
$(document).on('click', '.delete-btn-two', function () { $(document).on('click', '.delete-btn-two', function () {
let row = $(this).closest('tr'); let row = $(this).closest('tr');
let id = row.data('id'); let id = row.data('id');
currentTableId = row.closest('table').attr('id'); // Set current table ID currentTableId = row.closest('table').attr('id');
currentModelName = 'SecondTableEntry'; // Set model name currentModelName = 'SecondTableEntry';
if (!confirm('Are you sure you want to delete this entry?')) return; if (!confirm('Are you sure you want to delete this entry?')) return;
@@ -320,16 +478,16 @@
if (response.status === 'success') { if (response.status === 'success') {
row.fadeOut(300, function () { $(this).remove(); }); row.fadeOut(300, function () { $(this).remove(); });
} else { } else {
alert('Failed to delete entry: ' + response.message); alert('Delete failed: ' + response.message);
} }
}, },
error: function () { error: function (xhr, status, error) {
alert('Failed to delete entry. Please try again.'); alert('Delete failed: ' + (xhr.responseJSON?.message || 'Please try again later'));
console.error('Error:', error);
} }
}); });
}); });
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@@ -8,4 +8,7 @@ urlpatterns = [
path('add-entry/<str:model_name>/', views.add_entry, name='add_entry'), 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('update-entry/<str:model_name>/', views.update_entry, name='update_entry'),
path('delete-entry/<str:model_name>/', views.delete_entry, name='delete_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'),
] ]

View File

@@ -1,20 +1,73 @@
from django.shortcuts import render from django.shortcuts import render ,redirect
from django.db.models import Sum, Value, DecimalField
from django.http import JsonResponse from django.http import JsonResponse
from django.db.models import Q
from decimal import Decimal, InvalidOperation
from django.apps import apps from django.apps import apps
from datetime import date from datetime import date, datetime
from .models import Client from django.utils import timezone
from .models import Client, SecondTableEntry
from django.db.models import Sum
from django.urls import reverse
from django.db.models.functions import Coalesce
from .forms import BetriebskostenForm
from .models import Betriebskosten
from django.utils.dateparse import parse_date
# Clients Page (Main) # Clients Page (Main)
from django.shortcuts import render
from django.db.models import Sum
from datetime import datetime
from .models import Client, SecondTableEntry
def clients_list(request): def clients_list(request):
clients = Client.objects.all().order_by('id') # Get all clients
return render(request, 'clients_table.html', {'clients': clients}) clients = Client.objects.all()
# Get all years available in SecondTableEntry
available_years_qs = SecondTableEntry.objects.dates('date', 'year', order='DESC')
available_years = [y.year for y in available_years_qs]
# Determine selected year
year_param = request.GET.get('year')
if year_param:
selected_year = int(year_param)
else:
# If no year in GET, default to latest available year or current year if DB empty
selected_year = available_years[0] if available_years else datetime.now().year
# Prepare monthly totals per client
monthly_data = []
for client in clients:
monthly_totals = []
for month in range(1, 13):
total = SecondTableEntry.objects.filter(
client=client,
date__year=selected_year,
date__month=month
).aggregate(
total=Coalesce(Sum('lhe_output'), Value(0, output_field=DecimalField()))
)['total']
monthly_totals.append(total)
monthly_data.append({
'client': client,
'monthly_totals': monthly_totals,
'year_total': sum(monthly_totals)
})
return render(request, 'clients_table.html', {
'monthly_data': monthly_data,
'current_year': selected_year,
'available_years': available_years,
'months': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
})
# Table One View (ExcelEntry) # Table One View (ExcelEntry)
def table_one_view(request): def table_one_view(request):
ExcelEntry = apps.get_model('sheets', 'ExcelEntry') ExcelEntry = apps.get_model('sheets', 'ExcelEntry')
entries_table1 = ExcelEntry.objects.all() entries_table1 = ExcelEntry.objects.all().select_related('client')
clients = Client.objects.all() clients = Client.objects.all()
return render(request, 'table_one.html', { return render(request, 'table_one.html', {
'entries_table1': entries_table1, 'entries_table1': entries_table1,
@@ -24,14 +77,24 @@ def table_one_view(request):
# Table Two View (SecondTableEntry) # Table Two View (SecondTableEntry)
def table_two_view(request): def table_two_view(request):
try:
SecondTableEntry = apps.get_model('sheets', 'SecondTableEntry') SecondTableEntry = apps.get_model('sheets', 'SecondTableEntry')
entries_table2 = SecondTableEntry.objects.all() entries = SecondTableEntry.objects.all().order_by('-date')
clients = Client.objects.all() clients = Client.objects.all()
return render(request, 'table_two.html', { return render(request, 'table_two.html', {
'entries_table2': entries_table2, 'entries_table2': entries,
'clients': clients, 'clients': clients,
}) })
except Exception as e:
return render(request, 'table_two.html', {
'error_message': f"Failed to load data: {str(e)}",
'entries_table2': [],
'clients': Client.objects.all()
})
# Add Entry (Generic) # Add Entry (Generic)
def add_entry(request, model_name): def add_entry(request, model_name):
@@ -39,42 +102,80 @@ def add_entry(request, model_name):
try: try:
model = apps.get_model('sheets', model_name) model = apps.get_model('sheets', model_name)
if model_name.lower() == 'client': if model_name == 'SecondTableEntry':
name = request.POST.get('name', 'New Name') # Handle date conversion
address = request.POST.get('address', '') date_str = request.POST.get('date')
entry = model.objects.create(name=name, address=address) try:
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() if date_str else None
except (ValueError, TypeError):
return JsonResponse({ return JsonResponse({
'id': entry.id, 'status': 'error',
'name': entry.name, 'message': 'Invalid date format. Use YYYY-MM-DD'
'address': entry.address, }, status=400)
})
client_id = request.POST.get('client_id')
client = Client.objects.get(id=client_id)
age = int(request.POST.get('age', 0))
email = request.POST.get('email', 'example@email.com')
# Handle Helium Output (Table Two)
lhe_output = request.POST.get('lhe_output')
entry = model.objects.create( entry = model.objects.create(
client=client, client=Client.objects.get(id=request.POST.get('client_id')),
age=age, date=date_obj,
email=email, is_warm=request.POST.get('is_warm') == 'true',
date_joined=date.today() lhe_delivery=request.POST.get('lhe_delivery', ''),
lhe_output=Decimal(lhe_output) if lhe_output else None,
notes=request.POST.get('notes', '')
) )
return JsonResponse({ return JsonResponse({
'status': 'success',
'id': entry.id, 'id': entry.id,
'client_name': client.name, 'client_name': entry.client.name,
'age': entry.age, 'date': entry.date.strftime('%Y-%m-%d') if entry.date else '',
'email': entry.email, 'is_warm': entry.is_warm,
'date_joined': entry.date_joined.strftime('%Y-%m-%d'), 'lhe_delivery': entry.lhe_delivery,
'lhe_output': str(entry.lhe_output) if entry.lhe_output else '',
'notes': entry.notes
}) })
elif model_name == 'ExcelEntry':
# Parse the date string into a date object
date_str = request.POST.get('date')
try:
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() if date_str else None
except (ValueError, TypeError):
date_obj = None
# Create the entry
entry = model.objects.create(
client=Client.objects.get(id=request.POST.get('client_id')),
date=date_obj,
pressure=Decimal(request.POST.get('pressure', 0)),
purity=Decimal(request.POST.get('purity', 0)),
notes=request.POST.get('notes', '')
)
# Prepare the response
response_data = {
'status': 'success',
'id': entry.id,
'client_name': entry.client.name,
'pressure': str(entry.pressure),
'purity': str(entry.purity),
'notes': entry.notes
}
# Only add date if it exists
if entry.date:
response_data['date'] = entry.date.strftime('%Y-%m-%d')
else:
response_data['date'] = None
return JsonResponse(response_data)
# Keep your existing SecondTableEntry code here...
except Exception as e: except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=400) return JsonResponse({'status': 'error', 'message': str(e)}, status=400)
return JsonResponse({'status': 'error', 'message': 'Invalid request'}, status=400) return JsonResponse({'status': 'error', 'message': 'Invalid request'}, status=400)
# Update Entry (Generic) # Update Entry (Generic)
def update_entry(request, model_name): def update_entry(request, model_name):
if request.method == 'POST': if request.method == 'POST':
@@ -83,34 +184,69 @@ def update_entry(request, model_name):
entry_id = int(request.POST.get('id')) entry_id = int(request.POST.get('id'))
entry = model.objects.get(id=entry_id) entry = model.objects.get(id=entry_id)
if model_name.lower() == 'client': # Common updates for both models
entry.name = request.POST.get('name') entry.client = Client.objects.get(id=request.POST.get('client_id'))
entry.address = request.POST.get('address', '') entry.notes = request.POST.get('notes', '')
# Handle date properly for both models
date_str = request.POST.get('date')
if date_str:
try:
entry.date = datetime.strptime(date_str, '%Y-%m-%d').date()
except ValueError:
return JsonResponse({
'status': 'error',
'message': 'Invalid date format. Use YYYY-MM-DD'
}, status=400)
if model_name == 'SecondTableEntry':
# Handle Helium Output specific fields
entry.is_warm = request.POST.get('is_warm') == 'true'
entry.lhe_delivery = request.POST.get('lhe_delivery', '')
lhe_output = request.POST.get('lhe_output')
try:
entry.lhe_output = Decimal(lhe_output) if lhe_output else None
except InvalidOperation:
return JsonResponse({
'status': 'error',
'message': 'Invalid LHe Output value'
}, status=400)
entry.save() entry.save()
return JsonResponse({ return JsonResponse({
'status': 'success', 'status': 'success',
'id': entry.id, 'id': entry.id,
'name': entry.name, 'client_name': entry.client.name,
'address': entry.address, 'date': entry.date.strftime('%Y-%m-%d') if entry.date else '',
'is_warm': entry.is_warm,
'lhe_delivery': entry.lhe_delivery,
'lhe_output': str(entry.lhe_output) if entry.lhe_output else '',
'notes': entry.notes
}) })
client_id = request.POST.get('client_id') elif model_name == 'ExcelEntry':
client = Client.objects.get(id=client_id) # Handle Helium Input specific fields
age = int(request.POST.get('age')) try:
email = request.POST.get('email') entry.pressure = Decimal(request.POST.get('pressure', 0))
entry.purity = Decimal(request.POST.get('purity', 0))
except InvalidOperation:
return JsonResponse({
'status': 'error',
'message': 'Invalid pressure or purity value'
}, status=400)
entry.client = client
entry.age = age
entry.email = email
entry.save() entry.save()
return JsonResponse({ return JsonResponse({
'status': 'success', 'status': 'success',
'id': entry.id, 'id': entry.id,
'name': client.name, 'client_name': entry.client.name,
'age': entry.age, 'date': entry.date.strftime('%Y-%m-%d') if entry.date else '',
'email': entry.email, 'pressure': str(entry.pressure),
'date_joined': entry.date_joined.strftime('%Y-%m-%d'), 'purity': str(entry.purity),
'notes': entry.notes
}) })
except model.DoesNotExist: except model.DoesNotExist:
@@ -118,9 +254,7 @@ def update_entry(request, model_name):
except Exception as e: except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=400) return JsonResponse({'status': 'error', 'message': str(e)}, status=400)
return JsonResponse({'status': 'error', 'message': 'Invalid request'}, status=400) return JsonResponse({'status': 'error', 'message': 'Invalid request method'}, status=400)
# Delete Entry (Generic) # Delete Entry (Generic)
def delete_entry(request, model_name): def delete_entry(request, model_name):
if request.method == 'POST': if request.method == 'POST':
@@ -137,3 +271,83 @@ def delete_entry(request, model_name):
return JsonResponse({'status': 'error', 'message': str(e)}, status=400) return JsonResponse({'status': 'error', 'message': str(e)}, status=400)
return JsonResponse({'status': 'error', 'message': 'Invalid request'}, status=400) return JsonResponse({'status': 'error', 'message': 'Invalid request'}, status=400)
def betriebskosten_list(request):
items = Betriebskosten.objects.all().order_by('-buchungsdatum')
return render(request, 'betriebskosten_list.html', {'items': items})
def betriebskosten_create(request):
if request.method == 'POST':
try:
entry_id = request.POST.get('id')
if entry_id:
# Update existing entry
entry = Betriebskosten.objects.get(id=entry_id)
else:
# Create new entry
entry = Betriebskosten()
# Get form data
buchungsdatum_str = request.POST.get('buchungsdatum')
rechnungsnummer = request.POST.get('rechnungsnummer')
kostentyp = request.POST.get('kostentyp')
betrag = request.POST.get('betrag')
beschreibung = request.POST.get('beschreibung')
gas_volume = request.POST.get('gas_volume')
# Validate required fields
if not all([buchungsdatum_str, rechnungsnummer, kostentyp, betrag]):
return JsonResponse({'status': 'error', 'message': 'All required fields must be filled'})
# Convert date string to date object
try:
buchungsdatum = parse_date(buchungsdatum_str)
if not buchungsdatum:
return JsonResponse({'status': 'error', 'message': 'Invalid date format'})
except (ValueError, TypeError):
return JsonResponse({'status': 'error', 'message': 'Invalid date format'})
# Set entry values
entry.buchungsdatum = buchungsdatum
entry.rechnungsnummer = rechnungsnummer
entry.kostentyp = kostentyp
entry.betrag = betrag
entry.beschreibung = beschreibung
# Only set gas_volume if kostentyp is helium and gas_volume is provided
if kostentyp == 'helium' and gas_volume:
entry.gas_volume = gas_volume
else:
entry.gas_volume = None
entry.save()
return JsonResponse({
'status': 'success',
'id': entry.id,
'buchungsdatum': entry.buchungsdatum.strftime('%Y-%m-%d'),
'rechnungsnummer': entry.rechnungsnummer,
'kostentyp_display': entry.get_kostentyp_display(),
'gas_volume': str(entry.gas_volume) if entry.gas_volume else '-',
'betrag': str(entry.betrag),
'beschreibung': entry.beschreibung or ''
})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)})
return JsonResponse({'status': 'error', 'message': 'Invalid request method'})
def betriebskosten_delete(request):
if request.method == 'POST':
try:
entry_id = request.POST.get('id')
entry = Betriebskosten.objects.get(id=entry_id)
entry.delete()
return JsonResponse({'status': 'success'})
except Betriebskosten.DoesNotExist:
return JsonResponse({'status': 'error', 'message': 'Entry not found'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)})
return JsonResponse({'status': 'error', 'message': 'Invalid request method'})