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 humanize %}
{% load counter_tag %} {% load counter_tag %}
<div class="content-section col-12"> <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> <hr>
<table class="table table-hover" id="agdata" > <table class="table table-hover" id="agdata" >
<thead> <thead>

View File

@ -16,5 +16,6 @@ urlpatterns = [
path('ag/bills/', AdmBills.as_view(), name="adm-bills"), path('ag/bills/', AdmBills.as_view(), name="adm-bills"),
path('usersingle/<int:uspk>', AdmUserSingle.as_view(), name="adm-user-single"), path('usersingle/<int:uspk>', AdmUserSingle.as_view(), name="adm-user-single"),
path('cron/<slug:code>', statisticCronJob, name="adm-cron"), 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 import json
from users.models import UserYearAbsenceInfo, UserTime from users.models import UserYearAbsenceInfo, UserTime
from timemanagement.models import Workday, Absence 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! Prüfung, ob angemeldeter User Mitarbeiterstatus hat. IMMER PER DISPATCH EINBAUEN!
''' '''
@ -121,6 +132,181 @@ class AdmBills(TemplateView):
return context 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 Gesamtansicht der Agenturen

BIN
dump.rdb

Binary file not shown.

View File

@ -1480,11 +1480,9 @@ def cronactionsdaily(request, code):
''' '''
for user in allusers: for user in allusers:
mailstatus += "\n USER: " + user.first_name + " " + user.last_name + " ID: (" + str(user.pk) + ")" mailstatus += "\n USER: " + user.first_name + " " + user.last_name + " ID: (" + str(user.pk) + ")"
# REST URLAUB BERECHNUNG # REST URLAUB BERECHNUNG
#try: try:
usertimedata = UserTime.objects.get(user=user) usertimedata = UserTime.objects.get(user=user)
day_tocheck = usertimedata.loose_holidedate.split(".")[0] day_tocheck = usertimedata.loose_holidedate.split(".")[0]
month_tocheck = usertimedata.loose_holidedate.split(".")[1] month_tocheck = usertimedata.loose_holidedate.split(".")[1]
@ -1513,7 +1511,6 @@ def cronactionsdaily(request, code):
''' '''
# Arbeitstage beenden # Arbeitstage beenden
try:
if(user.usertime.usetime): if(user.usertime.usetime):
try: try:
workdays = Workday.objects.filter(user=user, end=None) workdays = Workday.objects.filter(user=user, end=None)
@ -1578,6 +1575,7 @@ def cronactionsdaily(request, code):
except ObjectDoesNotExist: except ObjectDoesNotExist:
data.update({"status" + str(user.pk) : "no usertime found for " + user.get_full_name()}) data.update({"status" + str(user.pk) : "no usertime found for " + user.get_full_name()})
mailstatus += "USER HAS NO USERTIMEOBJECT USER-ID: " + str(user.pk) mailstatus += "USER HAS NO USERTIMEOBJECT USER-ID: " + str(user.pk)
else: else:
data.update({"status" : "failed"}) data.update({"status" : "failed"})