391 lines
13 KiB
Python
391 lines
13 KiB
Python
# coding=utf-8
|
|
|
|
from django.shortcuts import render
|
|
from django.http import HttpResponse
|
|
from django.db.models import Sum
|
|
|
|
from reportlab.pdfgen import canvas
|
|
from reportlab.lib.pagesizes import A4
|
|
|
|
# from PIL import Image
|
|
import os
|
|
from django.conf import settings
|
|
|
|
from datetime import date, datetime, timedelta
|
|
|
|
from .billing_config import loadBillingConfig
|
|
|
|
from .models import (
|
|
Client,
|
|
Contract,
|
|
Prestation,
|
|
)
|
|
|
|
|
|
def contract_listing(request):
|
|
""" Récupère la liste de tous les contrats. """
|
|
|
|
contract_list = Contract.objects.all()
|
|
context = {"contract_list": contract_list}
|
|
return render(request, "contract_listing.html", context)
|
|
|
|
|
|
def contract_detail(request, contract_id):
|
|
"""
|
|
Récupère toutes les informations relatives à un contrat et les prestations relatives à
|
|
celui-ci.
|
|
|
|
Args:
|
|
contract_id (int): identifiant d'un contract
|
|
"""
|
|
|
|
contract = Contract.objects.get(pk=contract_id)
|
|
prestation_list = contract.get_prestation.all()
|
|
prestation_count = prestation_list.count()
|
|
total = list(contract.get_prestation.all().aggregate(Sum("total_amount")).values())[
|
|
0
|
|
]
|
|
context = {
|
|
"contract": contract,
|
|
"prestation_list": prestation_list,
|
|
"prestation_count": prestation_count,
|
|
"total": total,
|
|
}
|
|
return render(request, "contract_detail.html", context)
|
|
|
|
|
|
def client_listing(request):
|
|
""" Récupère la liste de tous les clients. """
|
|
client_list = Client.objects.all()
|
|
context = {"client_list": client_list}
|
|
return render(request, "client_listing.html", context)
|
|
|
|
|
|
def prestation_listing(request):
|
|
""" Récupère la liste de toutes les prestations. """
|
|
prestation_list = Prestation.objects.all()
|
|
context = {"prestation_list": prestation_list}
|
|
return render(request, "prestation_listing.html", context)
|
|
|
|
|
|
# Est-ce que ces variables ne devraient pas être dans un fichier de configuration à part ?
|
|
Y = 841.89
|
|
X = 35
|
|
RIGHT_X = 595.27 - X
|
|
TITLED_X = 125
|
|
INDENTED_X = X + 5
|
|
INDENTED_RIGHT_X = RIGHT_X - 5
|
|
|
|
PRESTATION_COLUMN_2 = INDENTED_X + 65
|
|
PRESTATION_COLUMN_3 = INDENTED_X + 400
|
|
PRESTATION_COLUMN_4 = INDENTED_X + 455
|
|
|
|
COMMON_LINE_HEIGHT = -15
|
|
DOUBLE_LINE_HEIGHT = COMMON_LINE_HEIGHT * 2
|
|
BIG_LINE_HEIGHT = COMMON_LINE_HEIGHT * 3
|
|
HUGE_LINE_HEIGHT = COMMON_LINE_HEIGHT * 4
|
|
|
|
|
|
class MyDocument(object):
|
|
# Create the PDF object, using the response object as its "file."
|
|
# http://www.reportlab.com/docs/reportlab-userguide.pdf
|
|
# canvas.rect(x, y, width, height, stroke=1, fill=0)
|
|
# localhost:8000/billing/contract/pdf/2
|
|
|
|
def __init__(self, response):
|
|
# Create the PDF object, using the response object as its "file."
|
|
self.document = canvas.Canvas(response, pagesize=A4)
|
|
self.y = Y - X
|
|
self.amount = 0
|
|
|
|
def newline(self, height=None):
|
|
"""
|
|
Passe à la ligne, la hauteur de la ligne étant passée en paramètre.
|
|
"""
|
|
if height == DOUBLE_LINE_HEIGHT:
|
|
self.y += DOUBLE_LINE_HEIGHT
|
|
elif height == BIG_LINE_HEIGHT:
|
|
self.y += BIG_LINE_HEIGHT
|
|
elif height == HUGE_LINE_HEIGHT:
|
|
self.y += HUGE_LINE_HEIGHT
|
|
else:
|
|
self.y += COMMON_LINE_HEIGHT
|
|
|
|
# if y < 120;
|
|
# document.PageBreak()
|
|
# y = 790
|
|
|
|
def drawString(
|
|
self, x, string, font_family="Helvetica", font_decoration=None, font_size=10
|
|
):
|
|
font = font_family
|
|
if font_decoration is not None:
|
|
font += "-" + font_decoration
|
|
self.document.setFont(font, font_size)
|
|
self.document.drawString(x, self.y, string)
|
|
|
|
def drawNewLine(
|
|
self,
|
|
x,
|
|
string,
|
|
height=None,
|
|
font_family="Helvetica",
|
|
font_decoration=None,
|
|
font_size=10,
|
|
):
|
|
self.newline(height)
|
|
self.drawString(x, string, font_family, font_decoration, font_size)
|
|
|
|
def header(self, info, contract):
|
|
"""
|
|
Génère le header de la facture.
|
|
"""
|
|
# self.document.rect(X, 735, 525, 70, fill=0)
|
|
self.drawNewLine(INDENTED_X, info["NAME"])
|
|
self.document.drawRightString(
|
|
RIGHT_X, self.y, "N° de Référence : " + str(contract.reference)
|
|
)
|
|
self.drawNewLine(INDENTED_X, info["ADDRESS"])
|
|
self.drawNewLine(INDENTED_X, info["CITY"])
|
|
self.drawNewLine(INDENTED_X, info["GSM"])
|
|
self.newline(DOUBLE_LINE_HEIGHT)
|
|
|
|
def title(self, contract):
|
|
"""
|
|
Génère le "titre" de la facture.
|
|
"""
|
|
res = contract.client.contact.count("i")
|
|
res += contract.client.contact.count("t")
|
|
res += contract.client.contact.count("l")
|
|
res += contract.client.contact.count("j")
|
|
res += contract.client.contact.count("f")
|
|
# res += contract.client.contact.count('r')
|
|
|
|
self.drawString(TITLED_X, "A l'attention de")
|
|
self.drawString(194.25, contract.client.contact, font_decoration="Bold")
|
|
|
|
if contract.client.is_company:
|
|
self.drawString(
|
|
194.25 + ((len(contract.client.contact) - res / 1.5) * 5.8), " pour la"
|
|
)
|
|
self.drawNewLine(TITLED_X, contract.client.name, font_decoration="Bold")
|
|
|
|
self.drawNewLine(TITLED_X, contract.client.address)
|
|
self.drawNewLine(
|
|
TITLED_X, str(contract.client.postal_code) + " " + contract.client.city
|
|
)
|
|
self.newline()
|
|
self.drawNewLine(TITLED_X, "Concernant la/le")
|
|
self.drawString(200, contract.name, font_decoration="Bold")
|
|
self.newline()
|
|
|
|
def payementInformation(self, info, contract):
|
|
"""
|
|
Génère les informations de payement.
|
|
"""
|
|
self.newline()
|
|
height = 40
|
|
self.document.rect(X, self.y - height, RIGHT_X - X, height, fill=0)
|
|
self.drawNewLine(INDENTED_X, "N° Entreprise : " + info["COMPANY_NUMBER"])
|
|
|
|
if contract.client.company_number:
|
|
self.document.drawRightString(
|
|
INDENTED_RIGHT_X,
|
|
self.y,
|
|
"Votre N° Entreprise : " + contract.client.company_number,
|
|
)
|
|
self.drawNewLine(
|
|
INDENTED_X,
|
|
"IBAN : "
|
|
+ info["IBAN"]
|
|
+ " (BIC : "
|
|
+ info["BIC"]
|
|
+ " - "
|
|
+ info["BANK"]
|
|
+ ")",
|
|
)
|
|
self.newline(DOUBLE_LINE_HEIGHT)
|
|
|
|
def prestations(self, contract):
|
|
"""
|
|
Génère l'affichage des prestations : tableau, liste des prestations, …
|
|
"""
|
|
self.drawNewLine(X, "Prestations", font_decoration="Bold")
|
|
total = self.drawPrestationsTable(contract.get_prestation.all())
|
|
self.newline(DOUBLE_LINE_HEIGHT)
|
|
self.document.setFont("Helvetica", 10)
|
|
self.document.drawRightString(INDENTED_X + 445, self.y, "Acompte")
|
|
self.document.drawRightString(INDENTED_RIGHT_X, self.y, str(contract.advance))
|
|
self.newline()
|
|
self.document.setFont("Helvetica-Bold", 10)
|
|
self.document.drawRightString(INDENTED_X + 445, self.y, "Solde à payer")
|
|
self.amount = total - contract.advance
|
|
self.document.drawRightString(INDENTED_RIGHT_X, self.y, str(self.amount))
|
|
|
|
def drawColoredRow(self):
|
|
"""
|
|
Génère une ligne colorée.
|
|
"""
|
|
self.document.setFillColorCMYK(0.43, 0.2, 0, 0)
|
|
self.document.rect(
|
|
X, self.y - 4, RIGHT_X - X, COMMON_LINE_HEIGHT, fill=True, stroke=False
|
|
)
|
|
self.document.setFillColorCMYK(0, 0, 0, 1)
|
|
|
|
def drawHeaderPrestationsTable(self):
|
|
"""
|
|
Génère le header de la table des prestations.
|
|
"""
|
|
self.drawColoredRow()
|
|
self.newline()
|
|
|
|
self.document.setFillColorCMYK(0, 0, 0, 1)
|
|
self.drawString(INDENTED_X, "Date")
|
|
self.drawString(PRESTATION_COLUMN_2, "Libellé")
|
|
self.drawString(INDENTED_X + 365, "Nbre")
|
|
self.drawString(INDENTED_X + 420, "Prix Unit.")
|
|
|
|
self.document.setFont("Helvetica-Bold", 10)
|
|
self.document.drawRightString(INDENTED_X + 510, self.y, "Total")
|
|
|
|
def drawFooterPrestationTable(self, total):
|
|
"""
|
|
Génère le footer de la table des prestations.
|
|
"""
|
|
self.drawColoredRow()
|
|
self.newline()
|
|
self.document.setFont("Helvetica-Bold", 10)
|
|
self.document.drawRightString(INDENTED_X + 445, self.y, "Total")
|
|
self.document.drawRightString(INDENTED_RIGHT_X, self.y, str(total))
|
|
|
|
def displayPrestation(self, prestation, total):
|
|
"""
|
|
Affiche une ligne de prestations dans le tableau.
|
|
"""
|
|
total += prestation.total_amount
|
|
self.drawNewLine(INDENTED_X, str(prestation.date))
|
|
self.drawString(PRESTATION_COLUMN_2, prestation.label)
|
|
self.document.drawRightString(PRESTATION_COLUMN_3, self.y, str(prestation.unit))
|
|
self.document.drawRightString(
|
|
PRESTATION_COLUMN_4, self.y, str(prestation.unit_price)
|
|
)
|
|
self.document.drawRightString(
|
|
INDENTED_RIGHT_X, self.y, str(prestation.total_amount)
|
|
)
|
|
return total
|
|
|
|
def drawPrestationsTable(self, prestation_list):
|
|
"""
|
|
Génère le tableau des prestations.
|
|
"""
|
|
self.drawHeaderPrestationsTable()
|
|
total = 0
|
|
for prestation in prestation_list:
|
|
total = self.displayPrestation(prestation, total)
|
|
self.drawFooterPrestationTable(total)
|
|
return total
|
|
|
|
def conclusion(self, info, contract):
|
|
"""
|
|
Affiche la conclusion de la facture.
|
|
"""
|
|
self.newline(DOUBLE_LINE_HEIGHT)
|
|
self.document.rect(X, self.y, RIGHT_X - X, BIG_LINE_HEIGHT, fill=0)
|
|
self.drawNewLine(INDENTED_X, "Merci de bien vouloir payer la somme de ")
|
|
self.drawString(INDENTED_X + 184, str(self.amount), font_decoration="Bold")
|
|
self.drawString(INDENTED_X + 215, "€ sur le compte ")
|
|
self.drawString(INDENTED_X + 290, info["IBAN"], font_decoration="Bold")
|
|
self.newline(COMMON_LINE_HEIGHT)
|
|
|
|
if not contract.is_paid:
|
|
the_date = datetime.now()
|
|
pay_date = the_date + timedelta(days=15)
|
|
self.drawString(INDENTED_X, "Pour le ")
|
|
self.drawString(
|
|
INDENTED_X + 35,
|
|
str(pay_date.day)
|
|
+ "/"
|
|
+ str(pay_date.month)
|
|
+ "/"
|
|
+ str(pay_date.year),
|
|
font_decoration="Bold",
|
|
)
|
|
self.drawString(INDENTED_X + 85, " au plus tard, avec la référence :")
|
|
self.drawString(
|
|
INDENTED_X + 230,
|
|
'"' + str(contract.reference) + '"',
|
|
font_decoration="Bold",
|
|
)
|
|
|
|
def addSignature(self):
|
|
"""
|
|
Génère la signature.
|
|
"""
|
|
self.newline(BIG_LINE_HEIGHT)
|
|
self.document.drawString(
|
|
INDENTED_X, self.y, "Hennuyères, le " + date.today().strftime("%d-%m-%Y")
|
|
)
|
|
self.document.drawRightString(RIGHT_X, self.y, "Président")
|
|
url = os.path.join(settings.STATICFILES_DIRS[0], "img/signature.png")
|
|
self.newline(DOUBLE_LINE_HEIGHT)
|
|
self.document.drawImage(url, INDENTED_X + 340, self.y, width=180, height=39)
|
|
|
|
def footer(self):
|
|
"""
|
|
Ajoute les conditions générales de payement au document.
|
|
"""
|
|
self.y = 175
|
|
self.newline(DOUBLE_LINE_HEIGHT)
|
|
self.drawNewLine(
|
|
INDENTED_X, "CONDITIONS GENERALES DE PAIEMENT", font_decoration="Bold"
|
|
)
|
|
self.drawNewLine(INDENTED_X, "Facture payable au comptant.")
|
|
self.drawNewLine(
|
|
INDENTED_X,
|
|
"""En cas de défaut de paiement à l'échéance, il est dû de plein droit et sans mise en
|
|
demeure, un interêt fixé au taux de""",
|
|
)
|
|
self.drawNewLine(INDENTED_X, "15% l'an.")
|
|
self.drawNewLine(
|
|
INDENTED_X,
|
|
"""Tout réclamation, pour être admise, doit être faite dans les huit jours de la
|
|
réception de la facture.""",
|
|
)
|
|
self.drawNewLine(
|
|
INDENTED_X,
|
|
"""En cas de litige concernant la présente facture, seuls les tribunaux de MONS seront
|
|
compétents.""",
|
|
)
|
|
|
|
def download(self):
|
|
# Close the PDF object cleanly, and we're done.
|
|
self.document.showPage()
|
|
self.document.save()
|
|
|
|
|
|
def contract_export(request, contractid):
|
|
"""
|
|
Génere une fichier PDF pour fournir au client.
|
|
"""
|
|
|
|
info = loadBillingConfig(request)
|
|
contract = Contract.objects.get(pk=contractid)
|
|
|
|
# Create the HttpResponse object with the appropriate PDF headers.
|
|
response = HttpResponse(content_type="application/pdf")
|
|
response["Content-Disposition"] = (
|
|
'attachment; filename="facture_' + str(contract.reference) + '.pdf"'
|
|
)
|
|
document = MyDocument(response)
|
|
|
|
document.header(info, contract)
|
|
document.title(contract)
|
|
document.payementInformation(info, contract)
|
|
document.prestations(contract)
|
|
document.conclusion(info, contract)
|
|
document.addSignature()
|
|
document.footer()
|
|
|
|
document.download()
|
|
return response
|