Initial commit

This commit is contained in:
Felix Baumann
2026-06-27 17:07:34 +00:00
commit fb47c53d52
256 changed files with 21270 additions and 0 deletions
+641
View File
@@ -0,0 +1,641 @@
from django import forms
from core.forms.base import BaseForm
from .models import Customer, Item, Invoice, InvoiceItem, CustomerContact, CustomerLocation, Offer, OfferItem, Reminder
from projects.models import Project
from time_tracking.models import TimeEntry
from finance.models import Expense
from django.utils import timezone
from django.db.models import Q, Max
class CustomerForm(BaseForm):
class Meta:
model = Customer
fields = [
"customer_number",
"customer_type",
"company_name",
"tax_number",
"vat_number",
"phone",
"email",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.apply_style()
class ItemForm(BaseForm):
class Meta:
model = Item
fields = [
"item_type",
"description",
"unit",
"price_net",
"tax_rate",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.apply_style()
class CustomerInvoiceForm(BaseForm):
contact = forms.ModelChoiceField(
queryset=CustomerContact.objects.none(),
label="Ansprechpartner",
required=True
)
location = forms.ModelChoiceField(
queryset=CustomerLocation.objects.none(),
label="Standort",
required=True
)
class Meta:
model = Invoice
fields = [
"customer",
"contact",
"location",
"issue_date",
"due_date",
"note",
]
widgets = {
"issue_date": forms.DateInput(attrs={"type": "date"}),
"due_date": forms.DateInput(attrs={"type": "date"}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.apply_style()
customer_id = (
self.data.get("customer")
or getattr(self.instance, "customer_id", None)
)
if customer_id:
self.fields["contact"].queryset = CustomerContact.objects.filter(customer_id=customer_id)
self.fields["location"].queryset = CustomerLocation.objects.filter(customer_id=customer_id)
else:
self.fields["contact"].queryset = CustomerContact.objects.none()
self.fields["location"].queryset = CustomerLocation.objects.none()
class ProjectInvoiceForm(BaseForm):
contact = forms.ModelChoiceField(
queryset=CustomerContact.objects.none(),
required=False,
label="Ansprechpartner",
widget=forms.Select(attrs={
"class": "customer-field"
})
)
location = forms.ModelChoiceField(
queryset=CustomerLocation.objects.none(),
required=False,
label="Standort",
widget=forms.Select(attrs={
"class": "customer-field"
})
)
contact_name_manual = forms.CharField(
required=False,
label="Ansprechpartner",
widget=forms.TextInput(attrs={
"class": "manual-field"
})
)
contact_email_manual = forms.EmailField(
required=False,
label="E-Mail",
widget=forms.EmailInput(attrs={
"class": "manual-field"
})
)
contact_phone_manual = forms.CharField(
required=False,
label="Telefon",
widget=forms.TextInput(attrs={
"class": "manual-field"
})
)
location_name_manual = forms.CharField(
required=False,
label="Standort",
widget=forms.TextInput(attrs={
"class": "manual-field"
})
)
location_street_manual = forms.CharField(
required=False,
label="Straße",
widget=forms.TextInput(attrs={
"class": "manual-field"
})
)
location_zip_manual = forms.CharField(
required=False,
label="PLZ",
widget=forms.TextInput(attrs={
"class": "manual-field"
})
)
location_city_manual = forms.CharField(
required=False,
label="Ort",
widget=forms.TextInput(attrs={
"class": "manual-field"
})
)
location_country_manual = forms.CharField(
required=False,
label="Land",
widget=forms.TextInput(attrs={
"class": "manual-field"
})
)
class Meta:
model = Invoice
fields = [
"project",
"contact",
"location",
"contact_name_manual",
"contact_email_manual",
"contact_phone_manual",
"location_name_manual",
"location_street_manual",
"location_zip_manual",
"location_city_manual",
"location_country_manual",
"issue_date",
"due_date",
"note",
]
widgets = {
"issue_date": forms.DateInput(
attrs={
"type": "date"
}
),
"due_date": forms.DateInput(
attrs={
"type": "date"
}
),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.apply_style()
project_id = self.data.get("project") or getattr(
self.instance,
"project_id",
None
)
if project_id:
project = Project.objects.filter(
id=project_id
).first()
if project and project.customer:
self.fields["contact"].queryset = CustomerContact.objects.filter(
customer=project.customer
)
self.fields["location"].queryset = CustomerLocation.objects.filter(
customer=project.customer
)
class InvoiceItemForm(BaseForm):
class Meta:
model = InvoiceItem
fields = [
"source_type",
"item",
"time_entry",
"expense",
"description",
"quantity",
"unit",
"unit_price_net",
"unit_tax_rate",
'service_period_from',
'service_period_to',
]
widgets = {
"quantity": forms.NumberInput(attrs={
"step": "1.00"
}),
"unit_price_net": forms.NumberInput(attrs={
"step": "1.00"
}),
"unit_tax_rate": forms.NumberInput(attrs={
"step": "1.00"
}),
"issue_date": forms.DateInput(
attrs={
"type": "date"
}
),
"service_period_from": forms.DateInput(
attrs={
"type": "date"
}
),
"service_period_to": forms.DateInput(
attrs={
"type": "date"
}
),
}
def __init__(
self,
*args,
invoice=None,
**kwargs
):
super().__init__(*args, **kwargs)
self.apply_style()
self.invoice = invoice
self.fields["item"].queryset = Item.objects.all()
self.fields["time_entry"].queryset = TimeEntry.objects.none()
self.fields["expense"].queryset = Expense.objects.none()
if invoice and invoice.project:
self.fields["source_type"].choices = [
("item", "Artikel"),
("time", "Zeiterfassung"),
("expense", "Ausgabe"),
]
self.fields["time_entry"].queryset = TimeEntry.objects.filter(
project=invoice.project,end__isnull=False
)
self.fields["expense"].queryset = Expense.objects.filter(
project=invoice.project
)
else:
self.fields["source_type"].choices = [
("item", "Artikel"),
]
self.fields["item"].widget.attrs["class"] += " source-item"
self.fields["time_entry"].widget.attrs["class"] += " source-time"
self.fields["expense"].widget.attrs["class"] += " source-expense"
def save(self, commit=True):
obj = super().save(commit=False)
source = self.cleaned_data.get(
"source_type"
)
# -------------------------
# ARTIKEL
# -------------------------
if source == "item":
item = self.cleaned_data["item"]
obj.item = item
obj.time_entry = None
obj.expense = None
obj.description = self.cleaned_data["description"]
obj.unit = self.cleaned_data["unit"]
obj.unit_price_net = self.cleaned_data["unit_price_net"]
obj.unit_tax_rate = self.cleaned_data["unit_tax_rate"]
# -------------------------
# ZEITERFASSUNG
# -------------------------
elif source == "time":
entry = self.cleaned_data["time_entry"]
obj.item = None
obj.time_entry = entry
obj.expense = None
obj.description = self.cleaned_data["description"]
obj.unit = self.cleaned_data["unit"]
obj.unit_price_net = self.cleaned_data["unit_price_net"]
obj.unit_tax_rate = self.cleaned_data["unit_tax_rate"]
# -------------------------
# AUSGABE
# -------------------------
elif source == "expense":
expense = self.cleaned_data["expense"]
obj.item = None
obj.time_entry = None
obj.expense = expense
obj.description = self.cleaned_data["description"]
obj.unit = self.cleaned_data["unit"]
obj.unit_price_net = self.cleaned_data["unit_price_net"]
obj.unit_tax_rate = self.cleaned_data["unit_tax_rate"]
if commit:
obj.save()
return obj
class OfferForm(BaseForm):
contact = forms.ModelChoiceField(
queryset=CustomerContact.objects.none(),
label="Ansprechpartner",
required=True
)
location = forms.ModelChoiceField(
queryset=CustomerLocation.objects.none(),
label="Standort",
required=True
)
class Meta:
model = Offer
fields = [
"customer",
"contact",
"location",
"issue_date",
"due_date",
"note",
]
widgets = {
"issue_date": forms.DateInput(attrs={"type": "date"}),
"due_date": forms.DateInput(attrs={"type": "date"}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.apply_style()
customer_id = (
self.data.get("customer")
or getattr(self.instance, "customer_id", None)
)
if customer_id:
self.fields["contact"].queryset = CustomerContact.objects.filter(customer_id=customer_id)
self.fields["location"].queryset = CustomerLocation.objects.filter(customer_id=customer_id)
else:
self.fields["contact"].queryset = CustomerContact.objects.none()
self.fields["location"].queryset = CustomerLocation.objects.none()
class OfferItemForm(BaseForm):
class Meta:
model = OfferItem
fields = [
"item",
"description",
"quantity",
"unit",
"unit_price_net",
"unit_tax_rate",
]
widgets = {
"quantity": forms.NumberInput(
attrs={"step": "1.00"}
),
"unit_price_net": forms.NumberInput(
attrs={"step": "1.00"}
),
"unit_tax_rate": forms.NumberInput(
attrs={"step": "1.00"}
),
}
def __init__(
self,
*args,
offer=None,
**kwargs
):
super().__init__(*args, **kwargs)
self.apply_style()
self.offer = offer
self.fields["item"].queryset = (
Item.objects.all()
)
def save(self, commit=True):
obj = super().save(commit=False)
item = self.cleaned_data.get("item")
if item:
obj.item = item
if commit:
obj.save()
return obj
class ReminderForm(BaseForm):
class Meta:
model = Reminder
fields = [
"invoice",
"issue_date",
"due_date",
"fee",
"note",
]
widgets = {
"issue_date": forms.DateInput(attrs={"type": "date"}),
"due_date": forms.DateInput(attrs={"type": "date"}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
possible_invoices = []
invoices = Invoice.objects.filter(
status=Invoice.Status.OVERDUE,
due_date__lt=timezone.now().date(),
).prefetch_related(
"reminders"
)
for invoice in invoices:
reminders = invoice.reminders.exclude(
status=Reminder.Status.CANCELED
).order_by("-level")
# Keine Mahnung vorhanden
if not reminders.exists():
possible_invoices.append(invoice)
continue
last_reminder = reminders.first()
# Nur nach expired möglich
if last_reminder.status != Reminder.Status.EXPIRED:
continue
# Maximal 3 Mahnungen
if last_reminder.level >= 3:
continue
possible_invoices.append(invoice)
self.fields["invoice"].queryset = Invoice.objects.filter(
id__in=[i.id for i in possible_invoices]
).order_by("due_date")
self.apply_style()