Bug und Abrechnung bis LexOffice 500

This commit is contained in:
holger.trampe 2020-09-25 14:20:07 +02:00
parent 5d49d05ebd
commit a6fb6bed60
37 changed files with 461 additions and 6 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -34,6 +34,53 @@ class UsersSelfChangeForm(forms.ModelForm):
model = User
fields = ['email']
class AgencyBillMail(forms.ModelForm):
class Meta:
model = Agency
fields = ['payment_address']
labels = {
'payment_address' : 'E-Mail für Rechnungen'
}
class AgencyBillPlan(forms.ModelForm):
class Meta:
model = Agency
labels = {
"name" : "Agenturname",
"inhaber" : "Inhaber",
"street" : "Straße und Hausnummer",
"plz" : "PLZ",
"city" : "Stadt",
"agency_email" : "E-Mail",
"phone" : "Telefon",
"agb" : "AGB akzeptieren",
"contract" : "Auftragsdatenverarbeitung akzeptieren"
}
fields = ['name','inhaber','agency_email', 'phone', 'street', 'plz', 'city', 'paymentplan', 'agb', 'contract']
def __init__(self, *args, **kwargs):
super(AgencyBillPlan, self).__init__(*args, **kwargs)
self.fields['paymentplan'] = forms.CharField(initial=6, required=True, widget=forms.HiddenInput())
self.fields['name'] = forms.CharField(required=True)
self.fields['inhaber'] = forms.CharField(required=True)
self.fields['agency_email'] = forms.CharField(required=True)
self.fields['phone'] = forms.CharField(required=True)
self.fields['street'] = forms.CharField(required=True)
self.fields['plz'] = forms.CharField(required=True)
self.fields['city'] = forms.CharField(required=True)
self.fields['agb'] = forms.BooleanField(required=True, label="AGB's akzeptieren")
self.fields['contract'] = forms.BooleanField(required=True, label="Auftragsdatenverarbeitung akzeptieren")
# Form für die Benachrichtigungseinstellungen
'''
class UsersNotificationForm(forms.ModelForm):

View File

@ -1,3 +1,81 @@
{% load mathfilters %}
{% load humanize %}
<p>Vielen Dank für die Unterstützung während der Pilotphase! Für diesen Zeitraum ist die Nutzung kostenfrei.</p>
{% load counter_tag %}
{% if request.user.profile.agency.paymentstatus == 1 %}
<p>Ihre Agentur darf die Plattform kostenlos nutzen. Vielen Dank für Ihre Unterstützung!</p>
{% else %}
<!-- Infoblock -->
<div class="row" style="">
<div class="col-6">
<div class="card d-block mb-3 mr-2">
<div class="card-body">
<h5 class="card-title">Ihr Betrag</h5>
<p>Ihr monatlicher Nutzungsbeitrag setzt sich wie folgt zusammen:</p>
<table style="width: 55%">
<tr>
<td>Grundbetrag (inkl. 3 Nutzer)</td><td>21,00 €</td>
</tr>
<tr>
{% loadUserCount request.user as usercount %}
<td>Zusätzliche Nutzer (3,00 € pro Nutzer)</td><td>{{usercount}}</td>
</tr>
<tr>
{% loadMWST user as mwst %}
<td>Gesetzliche MwSt. (19%)</td><td>{{mwst|floatformat:2|intcomma}} €</td>
</tr>
<tr>
<td>&nbsp;</td>
</tr>
<tr class="mt-2">
{% loadFinalMoney user as fm %}
<td><b>Monatlicher Bruttobetrag</b></td><td><b>{{fm|floatformat:2|intcomma}} €</b></td>
</tr>
</table>
<hr>
<h5 class="card-title">Ihre Zahlungsweise</h5>
{% getNextMonth request.user.profile.agency as nextMonth %}
Ihre Agentur wurde am {{ request.user.profile.agency.registerdate|date:"d.m.Y" }} registriert.
{% if paymentplan == None %}
Es wurde noch keine Zahlungsweise ausgewählt. Sie können die Digitale Agentur bis zum {{nextMonth|date:"d.m.Y"}} kostenlos nutzen. Möchten Sie die Digitale Agentur auch nach diesem Zeitraum nutzen, wählen Sie bitte einen Zahlplan aus.
<br />
<a href="{% url 'ag-billplanupdate' request.user.profile.agency.pk %}" class="btn btn-primary btn mt-2" onclick="">Zahlplan jetzt auswählen</a>
{% else %}
Zahlungsweise: TODO
{% endif %}
<hr>
<h5 class="card-title.">Fragen, Hilfe, Kündigung</h5>
Bei Fragen zu Ihrer Abrechnung melden Sie sich bitte per E-Mail an <a href="mailto:abrechnung@digitale-agentur.com">abrechnung@digitale-agentur.com</a>.
<!-- TASK: Kündigungsmodaliäten einfügen -->
KÜNDIGUNGSMODALITÄTEN NOCH EINFÜGEN
</div>
</div>
</div>
<div class="col-6">
<!-- Mail für Rechnungen -->
<div class="card d-block mb-3">
<div class="card-body">
<h5 class="card-title">E-Mail-Adresse</h5>
Ihre Rechnungen werden automatisch an folgende E-Mailadresse gesendet.<br />
{% if request.user.profile.agency.payment_address == None %}
<i>Es wurde keine Adresse hinterlegt. Die Adresse wird an die Agenturadresse versendet:</i>&nbsp;<b>{{request.user.profile.agency.agency_email}}</b>
{% else %}
{{request.user.profile.agency.payment_address}}
{% endif %}
<br />
<a href="{% url 'ag-billmailupdate' request.user.profile.agency.pk %}" class="btn btn-primary btn mt-2">E-Mailadresse ändern</a>
</div>
</div>
<div class="card d-block mb-3">
<div class="card-body">
<h5 class="card-title">Rechnungen</h5>
</div>
</div>
</div>
</div>
{% endif %}

View File

@ -0,0 +1,16 @@
{% extends "users/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section col-12">
<h3>Rechnungs-E-Mail aktualisieren</h3>
<hr>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="row"><div class="col-4">
{{form|crispy}}
<hr>
<button type="submit" class="btn btn-primary">E-Mailadresse aktualisieren</button>&nbsp;
<a class="btn" href="{% url 'dasettings' %}">Abbrechen</a>
</form>
</div>
{% endblock content %}

View File

@ -0,0 +1,99 @@
{% extends "users/base.html" %}
{% load crispy_forms_tags %}
{% load mathfilters %}
{% load humanize %}
{% load counter_tag %}
{% block content %}
<div class="content-section col-12">
<h3>Zahlplan auswählen</h3>
<hr>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div class="row">
<div class="col-4">
<h5>Agenturdaten überprüfen</h5>
<hr>
{% for formfield in form %}
{% if formfield.name != 'agb' and formfield.name != 'contract' %}
{{formfield|as_crispy_field}}
{% endif %}
{% endfor %}
</div>
<div class="col-4">
<h5>Zahlplan festlegen</h5>
<hr>
<p>Wählen Sie aus, für welchen Zeitraum die Abrechnung Ihrer Digitalen Agentur durchgeführt werden soll.</p>
<div class="form-check mt-2">
<input class="form-check-input" type="radio" name="planchoose" id="plan_1" value="3" onchange="javascript:updatePlan(3)">
<label class="form-check-label" for="plan_1">
3 Monate
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="planchoose" id="plan_2" value="6" checked onchange="javascript:updatePlan(6)"
<label class="form-check-label" for="plan_3">
6 Monate
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="planchoose" id="plan_3" value="12" onchange="javascript:updatePlan(12)">
<label class="form-check-label" for="plan_3">
12 Monate
</label>
</div>
<hr>
{{form.contract|as_crispy_field}}
{{form.agb|as_crispy_field}}
<hr>
<p>Mit Klick auf dem Button <i>Jetzt kostenpflichtig bestellen</i> wird eine Rechnung für Ihre Agentur generiert und per E-Mail an die hinterlegte Rechnungs-E-Mailadresse oder an die Agentur-E-Mailadresse versendet. Der Rechnungsbetrag muss innerhalb von 14 Tagen beglichen werden.
<hr>
{% loadFinalMoney user as fm %}
<b>Rechnungsbetrag: <span id="billfinal">{{fm|floatformat:2|intcomma}}</span>&nbsp;</b>
</div>
</div>
<hr>
<div class="col-8">
<a class="btn" href="{% url 'dasettings' %}">Abbrechen</a>
<button type="submit" class="btn btn-primary" style="float: right">Jetzt kostenpflichtig bestellen</button>&nbsp;
</div>
</form>
</div>
<script type="text/javascript">
var monthlybill_str = "{{fm}}";
var monthlybill = 0;
$(document).ready(function(){
monthlybill = monthlybill_str.replace(",", ".");
updatePlan(6);
})
function updatePlan(newPlan){
new_bill = (monthlybill * newPlan).toFixed(2);
monthlybill_str_new = "" + new_bill;
monthlybill_str_new = monthlybill_str_new.replace(".", ",");
$("#billfinal").html(monthlybill_str_new);
$("#id_paymentplan").val(newPlan);
}
</script>
{% endblock content %}

View File

@ -3,7 +3,7 @@ from django.contrib.auth import views as auth_views
from django.contrib.auth.decorators import login_required, permission_required
from . import views
from .views import FreeDayDeleteView, AbsenceReasonDeleteView, AbsenceReasonUpdateView, AbsenceReasonAddView
from .views import NewUserFirstStep, UserProfileUpdate, UserChangeMain
from .views import NewUserFirstStep, UserProfileUpdate, UserChangeMain, BillMailUpdate, BillPlanUpdate
'''
Permissions definiert in models.py bei USERS und dann hier vor die View geschrieben!
'''
@ -33,4 +33,6 @@ urlpatterns = [
path('abcatdel/<int:pk>', AbsenceReasonDeleteView.as_view(), name="abcat-delete"),
path('abcatupdate/<int:pk>', AbsenceReasonUpdateView.as_view(), name="abcat-update"),
path('abcatadd/', AbsenceReasonAddView.as_view(), name="abcat-add"),
path('ag/billmail/update/<int:pk>', permission_required('users.agencyinfo')(BillMailUpdate.as_view()), name='ag-billmailupdate'),
path('ag/billplan/<int:pk>', permission_required('users.agencyinfo')(BillPlanUpdate.as_view()), name='ag-billplanupdate'),
]

View File

@ -1,7 +1,7 @@
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect,HttpResponse, JsonResponse
from .forms import UsersSelfChangeForm, UsersNotificationFormStandard, AgencyGroupPerms, AgencyModulsForm, UserNewUserForm, UserProfileForm, AgencyNetworkForm, AgencyOrganigrammForm, UserTimeForm, AbsenceReasonForm, UsersNotificationFormNews, UsersNotificationFormFiles, UsersNotificationFormMessages ,UsersNotificationFormOrganizer, UsersNotificationFormChat, UsersNotificationFormAbTime, UsersNotificationFormGroups, UsersNotificationFormAgn, UsersNotificationFormTasks
from .forms import UsersSelfChangeForm, UsersNotificationFormStandard, AgencyGroupPerms, AgencyModulsForm, UserNewUserForm, UserProfileForm, AgencyNetworkForm, AgencyOrganigrammForm, UserTimeForm, AbsenceReasonForm, UsersNotificationFormNews, UsersNotificationFormFiles, UsersNotificationFormMessages ,UsersNotificationFormOrganizer, UsersNotificationFormChat, UsersNotificationFormAbTime, UsersNotificationFormGroups, UsersNotificationFormAgn, UsersNotificationFormTasks, AgencyBillMail, AgencyBillPlan
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
@ -1440,3 +1440,138 @@ def ModSettingsTm(request):
else:
return JsonResponse({})
'''
########## ABRECHNUNG ##########
class BillMailUpdate(UpdateView):
model = Agency
success_url = reverse_lazy('dasettings')
form_class = AgencyBillMail
template_name = "dasettings/dasettings_billmail.html"
def form_valid(self, form):
messages.success(self.request, f"E-Mailadresse erfolgreich aktualisiert!")
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({'active_link' : 'dasettings'})
return context
from dateutil.relativedelta import *
class BillPlanUpdate(UpdateView):
model = Agency
success_url = reverse_lazy('dasettings')
form_class = AgencyBillPlan
template_name = "dasettings/dasettings_billplan.html"
# LexOffice Verbindung
def form_valid(self, form):
agency = self.request.user.profile.agency
# HEADERS CURL
headers = {
'Authorization': 'Bearer 33bfb1b9-2994-4fd4-b447-1f4754a5c7cb',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
month = agency.registerdate
next_month = month + relativedelta(months=1)
voucher_date = next_month.strftime("%Y-%m-%d")
# USERCOUNT BERECHNEN
usercount = len(User.objects.filter(profile__agency=agency))
if(usercount < 4):
usercount = 0
else:
usercount = usercount - 3
# DataJSON
lexdata = {
"voucherDate": voucher_date + "T00:00:00.000+01:00",
"address" : {
"name" : agency.name,
"countryCode" : "DE"
},
"totalPrice" : {
"currency" : "EUR",
},
"lineItems" : [
{
"type" : "custom",
"name" : "Monatsbeitrag",
"quantity" : form.cleaned_data['paymentplan'],
"unitName" : "Stück",
"unitPrice" :
{
"currency" : "EUR",
"netAmount" : 21.00,
"taxRatePercentage" : 19
},
},
{
"type" : "custom",
"name" : "Weitere Mitarbeiter",
"quantity" : usercount,
"unitName" : "Stück",
"unitPrice" :
{
"currency" : "EUR",
"netAmount" : 3,
"taxRatePercentage" : 19
},
}
],
"taxConditions": {
"taxType": "net"
},
"shippingConditions": {
"shippingDate": voucher_date + "T00:00:00.000+01:00",
"shippingType": "service"
},
}
json_data = json.dumps(lexdata)
r = requests.post("https://api.lexoffice.io/v1/vouchers", data=json_data, headers=headers)
if(r.status_code != 200):
messages.warning(self.request, f"Fehlercode "+str(r.status_code)+". Es wurde keine Rechnung erstellt. Bitte wenden Sie sich an den Support!")
print(r.status_code)
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({'active_link' : 'dasettings'})
return context

Binary file not shown.

Binary file not shown.

View File

@ -19,4 +19,5 @@ urlpatterns = [
path('addpass/', permission_required('users.moduleorganizer')(OrganizerAddPassword.as_view(template_name="organizer/agpass_add.html")), name='add-agpass'),
path('lerg/', views.loaddefaultql, name="ql-ajaxloaddef"),
path('ajo/', views.ajaxorganizer, name="ajaxorganizer"),
]

View File

@ -685,3 +685,54 @@ def counterWDreset():
def getCounterWD():
global workdaycounter
return workdaycounter
from dateutil.relativedelta import *
@register.simple_tag
def getNextMonth(agency):
month = agency.registerdate
next_month = month + relativedelta(months=1)
return next_month
@register.simple_tag
def loadUserCount(user):
usercount = len(User.objects.filter(profile__agency=user.profile.agency))
if(usercount < 4):
usercount = 0
else:
usercount = usercount - 3
return usercount
@register.simple_tag
def loadMWST(user):
mwst = 0.0
usercount = len(User.objects.filter(profile__agency=user.profile.agency))
if(usercount < 4):
usercount = 0
else:
usercount = usercount - 3
mwst = (21.0 + usercount*3)/100 * 19
return mwst
@register.simple_tag
def loadFinalMoney(user):
usercount = len(User.objects.filter(profile__agency=user.profile.agency))
if(usercount < 4):
usercount = 0
else:
usercount = usercount - 3
finalMoney = ((21.0 + usercount*3)/100 * 19) + 21.0 + usercount*3
return finalMoney

View File

@ -59,6 +59,7 @@ class AgencyNetwork(models.Model):
networkid = models.CharField(default="", max_length=30)
standards = models.ManyToManyField("standards.Standards", related_name="sharedstandards", blank=True)
def __str__(self):
return f'{self.name}'
@ -84,9 +85,29 @@ class Agency(models.Model):
agencypic = models.ImageField(default='ag_default.jpg', upload_to=picturepath_agency, blank=True)
# MONEY
balance = models.FloatField(default=0.0, max_length=9, blank=True)
nextdebiting = models.DateTimeField(default=timezone.now, blank=True)
monthlyprice = models.FloatField(default=25.0, max_length=9, blank=True)
#balance = models.FloatField(default=0.0, max_length=9, blank=True)
#nextdebiting = models.DateTimeField(default=timezone.now, blank=True)
#monthlyprice = models.FloatField(default=25.0, max_length=9, blank=True)
# Payment-Status
# 0 = Normal, Agentur muss ganz normal bezahlen
# 1 = Agentur ist kostenlos dabei!
paymentstatus = models.IntegerField(default=0, null=True)
# ID für die Verbindung mit Lexoffice
lexofficeid = models.CharField(default="", max_length=200, blank=True)
# Bezahlplan 3,6,12 Monate
paymentplan = models.IntegerField(default=None, null=True, blank=True)
# Registrierdatum der Agentur
registerdate = models.DateField(default=timezone.now, null=True)
payment_address = models.EmailField(default=None, blank=True, null=True)
#
agb = models.BooleanField(default=False)
contract = models.BooleanField(default=False)
# MODULEEINSTELLUNGEN FÜR DIE AGENTUR
module_news = models.BooleanField(default=True)
@ -111,6 +132,11 @@ class Agency(models.Model):
vve = models.CharField(default="", max_length=200, blank=True)
def __str__(self):
return f'{self.name}'