Erstellen von eigenen Rechnungen in der Adminoberfläche

This commit is contained in:
holger.trampe 2021-01-18 20:10:47 +01:00
parent 6f4389999d
commit 571cffaf0e
8 changed files with 277 additions and 33 deletions

13
adm/forms.py Normal file
View File

@ -0,0 +1,13 @@
from django import forms
from django.forms import ModelForm
from users.models import AgencyBills
class AgencyBillForm(forms.ModelForm):
class Meta:
model = AgencyBills
fields = ['agency', 'start']
labels = {
'agency' : "Agentur",
'start' : "Leistungszeitraum Start",
}

View File

@ -0,0 +1,42 @@
{% extends "adm/adm_base.html" %}
{% block content %}
{% load adm_tags %}
{% load mathfilters %}
{% load crispy_forms_tags %}
{% load humanize %}
{% load counter_tag %}
<div class="content-section col-12">
<h4>Rechnung manuell erstellen</h4>
<hr>
Achtung! Sie sind in Begriff, eine Rechnung manuell zu erstellen! Achten Sie auf die korrekte Agentur sowie Start- und Endtermin! Prüfen Sie vor Anlagen Ihre Eingaben!
<hr>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{form.media}}
{{form|crispy}}
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="confirmDel" onclick="javascript:toggleEntryOkButton()">
<label class="form-check-label" for="confirmDel">
Bestätigung, dass die Rechnungsinformationenx korrekt sind.
</label>
</div>
<hr>
<a class="btn btn-secondary" href="{% url 'adm-agencys' %}" name="action" disabled="true">Abbrechen</a>
<button style="float: right" class="btn btn-primary" type="submit" name="action" disabled="true" id="doaddbill"><b>Rechnung inkl. PDF-Generierung erstellen und an Agentur schicken.</button>
<script type="text/javascript">
function toggleEntryOkButton(){
if($('#confirmDel').prop('checked')){
$('#doaddbill').attr('disabled', false);
}
else{
$('#doaddbill').attr('disabled', true);
}
}
</script>
</form>
{% endblock content %}

View File

@ -5,7 +5,11 @@
{% load humanize %}
{% load counter_tag %}
<div class="content-section col-12">
<h4>Rechungsübersicht</h4>
<h4>Rechungsübersicht
<span style="float: right;">
<a class="btn btn-primary btn-sm" href="{% url 'admbill-add' %}" style="float: right;"><i class="fas fa-plus"></i>&nbsp;Rechnung</a>
</span>
</h4>
<hr>
<table class="table table-hover" id="agdata" >
<thead>

View File

@ -16,5 +16,6 @@ urlpatterns = [
path('ag/bills/', AdmBills.as_view(), name="adm-bills"),
path('usersingle/<int:uspk>', AdmUserSingle.as_view(), name="adm-user-single"),
path('cron/<slug:code>', statisticCronJob, name="adm-cron"),
path('getorders/', getCSVRDOrders, name="getorders")
path('getorders/', getCSVRDOrders, name="getorders"),
path('adm/addbill', AdmAddBill.as_view(), name="admbill-add")
]

View File

@ -15,6 +15,17 @@ from datetime import date, datetime
import json
from users.models import UserYearAbsenceInfo, UserTime
from timemanagement.models import Workday, Absence
from .forms import AgencyBillForm
from datetime import date, timedelta
from django.core.mail import EmailMessage
from django.core.mail import EmailMultiAlternatives
import io as BytesIO
import base64
from django.http import HttpResponse
from dateutil.relativedelta import *
import requests
from django.template.loader import render_to_string
'''
Prüfung, ob angemeldeter User Mitarbeiterstatus hat. IMMER PER DISPATCH EINBAUEN!
'''
@ -121,6 +132,181 @@ class AdmBills(TemplateView):
return context
'''
Erstellen einer neuen Rechnung
'''
class AdmAddBill(CreateView):
template_name = "adm/adm_addbill.html"
model = AgencyBills
success_url = reverse_lazy('adm-bills')
form_class = AgencyBillForm
def dispatch(self, *args, **kwargs):
if(checkForStuffUser(self.request)):
return super().dispatch(*args, **kwargs)
else:
messages.warning(self.request, f'Sie benötigen einen Mitarbeiter-Account, um diese Seiten aufzurufen!')
return redirect("login")
def form_valid(self, form):
today = date.today()
agency = form.cleaned_data['agency']
# USERCOUNT BERECHNEN
usercount = len(User.objects.filter(profile__agency=agency))
if(usercount < 4):
usercount = 0
else:
usercount = usercount - 3
# HEADERS CURL
headers = {
'Authorization': 'Bearer ' + settings.LEX_API,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
plan = 1
start_date = form.cleaned_data["start"]
start_date_string = start_date.strftime("%d.%m.%Y")
end_date = start_date + relativedelta(days=30)
end_date_string= end_date.strftime("%d.%m.%Y")
# Rechnungsdatum passt ja
voucher_date_today = date.today().strftime("%Y-%m-%d")
monthword = "Monat"
lexdata = {
"voucherDate": voucher_date_today + "T00:00:00.000+00:00",
"address" : {
"name" : agency.name,
"street": agency.street,
"zip": agency.plz,
"city": agency.city,
"countryCode" : "DE"
},
"totalPrice" : {
"currency" : "EUR",
},
"lineItems" : [
{
"type" : "custom",
"name" : "Digitale Agentur: Grundbetrag für " + str(plan) + " " + monthword,
"quantity" : 1,
"unitName" : "Stück",
"description" : "Zeitraum " + start_date_string + " - " + end_date_string,
"unitPrice" :
{
"currency" : "EUR",
"netAmount" : 21.00,
"taxRatePercentage" : 19
},
},
{
"type" : "custom",
"name" : "Digitale Agentur: Zusätzliche Nutzer",
"description" : "Zeitraum " + start_date_string + " - " + end_date_string,
"quantity" : usercount,
"unitName" : "Stück",
"unitPrice" :
{
"currency" : "EUR",
"netAmount" : 3,
"taxRatePercentage" : 19
},
}
],
"taxConditions": {
"taxType": "net"
},
#"paymentConditions": {
# "paymentTermLabel": "Bitte zahlen Sie innerhalb von 14 Tagen.",
# "paymentTermDuration": 14,
#},
"shippingConditions": {
#"shippingDate": voucher_date_today + "T00:00:00.000+00:00",
"shippingType": "none"
}
}
json_data = json.dumps(lexdata)
# WIEDER RAUSNEHMEN
# NEUE RECHNUNG ALs ENTWURF
#r = requests.post("https://api.lexoffice.io/v1/invoices/", data=json_data, headers=headers)
# RICHTIGE RECHNUNG
r = requests.post("https://api.lexoffice.io/v1/invoices/?finalize=true", data=json_data, headers=headers)
if(r.status_code == 201):
response_text = json.loads(r.text)
newbill_id = response_text["id"]
# OrganizationId berechnen, wenn noch nicht gesetzt
r = requests.get("https://api.lexoffice.io/v1/invoices/" + response_text["id"], data=json_data, headers=headers)
response_text = json.loads(r.text)
newbill = AgencyBills(agency=agency, lexid=newbill_id, billtype="invoice", billnumber=response_text["voucherNumber"], billstatus=response_text["voucherStatus"], start=start_date, end=end_date, plan=plan, usercount=usercount)
newbill.save()
mail_to_send = ""
if(agency.payment_address == None):
mail_to_send = agency.agency_email
else:
mail_to_send = agency.payment_address
# BCC Mail with Object - NICHT DEN IMPORT VERGESSEN!!!
email = EmailMultiAlternatives(
'Digitale Agentur | Rechnung ' + str(response_text["voucherNumber"]),
'Sehr geehrte Nutzer, hiermit erhalten Sie eine neue Rechnung für die Digitale Agentur. Ihr Team der Digitalen Agentur',
'noreply@digitale-agentur.com',
[mail_to_send],
['info@digitale-agentur.com'],
headers={},
)
headers = {
'Authorization': 'Bearer ' + settings.LEX_API,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
lexdata = {
"renderType" : "pdf"
}
json_data = json.dumps(lexdata)
r_final = requests.get("https://api.lexoffice.io/v1/invoices/"+newbill_id+"/document", data=json_data, headers=headers)
json.loads(r_final.text)
base64String = requests.get("https://api.lexoffice.io/v1/files/"+json.loads(r_final.text)["documentFileId"]+"/", data=json_data, headers=headers)
content = base64.b64decode(base64String.text)
msg_html = render_to_string('users/newbill_mail.html', {})
email.attach_alternative(msg_html, "text/html")
email.attach('Rechnung_' + str(response_text["voucherNumber"]) + '.pdf', content, "application/pdf")
email.send()
return super(AdmAddBill, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({'active_link' : "adm-agencys"})
#context.update({'agencys' : Agency.objects.all()})
return context
'''
Gesamtansicht der Agenturen

BIN
dump.rdb

Binary file not shown.

View File

@ -1480,40 +1480,37 @@ def cronactionsdaily(request, code):
'''
for user in allusers:
mailstatus += "\n USER: " + user.first_name + " " + user.last_name + " ID: (" + str(user.pk) + ")"
# REST URLAUB BERECHNUNG
#try:
usertimedata = UserTime.objects.get(user=user)
day_tocheck = usertimedata.loose_holidedate.split(".")[0]
month_tocheck = usertimedata.loose_holidedate.split(".")[1]
month = today.month
day = today.day
if month < 10:
month = "0" + str(month)
day = today.day
else:
month = month
if day < 10:
day = "0" + str(day)
else:
day = day
# Restetag erreicht, Reste ins nächste Jahr übertragen
''' DAS IST UNNÖTIG '''
'''
if(str(day_tocheck) == str(day) and str(month_tocheck) == str(month)):
sourceyear = today.year
this_year = list(UserYearAbsenceInfo.objects.filter(year=sourceyear, user=user))[0]
next_year = list(UserYearAbsenceInfo.objects.filter(year=sourceyear+1, user=user))[0]
next_year.restdays = this_year.days - this_year.days_inuse
next_year.save()
'''
# Arbeitstage beenden
try:
usertimedata = UserTime.objects.get(user=user)
day_tocheck = usertimedata.loose_holidedate.split(".")[0]
month_tocheck = usertimedata.loose_holidedate.split(".")[1]
month = today.month
day = today.day
if month < 10:
month = "0" + str(month)
day = today.day
else:
month = month
if day < 10:
day = "0" + str(day)
else:
day = day
# Restetag erreicht, Reste ins nächste Jahr übertragen
''' DAS IST UNNÖTIG '''
'''
if(str(day_tocheck) == str(day) and str(month_tocheck) == str(month)):
sourceyear = today.year
this_year = list(UserYearAbsenceInfo.objects.filter(year=sourceyear, user=user))[0]
next_year = list(UserYearAbsenceInfo.objects.filter(year=sourceyear+1, user=user))[0]
next_year.restdays = this_year.days - this_year.days_inuse
next_year.save()
'''
# Arbeitstage beenden
if(user.usertime.usetime):
try:
workdays = Workday.objects.filter(user=user, end=None)
@ -1578,6 +1575,7 @@ def cronactionsdaily(request, code):
except ObjectDoesNotExist:
data.update({"status" + str(user.pk) : "no usertime found for " + user.get_full_name()})
mailstatus += "USER HAS NO USERTIMEOBJECT USER-ID: " + str(user.pk)
else:
data.update({"status" : "failed"})