ComptaClub/src/comptabilite/views.py

775 lines
25 KiB
Python

# coding=utf-8
from itertools import zip_longest
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from django.http import HttpResponse
from django.shortcuts import render
from django.db.models import Q
from collections import OrderedDict
from .models import (
Transaction,
TransactionType,
EvaluationRules,
EvaluationRulesAdaptation,
Annuality,
ComplementaryInformations,
)
from .tools import (
get_transactions_and_sums_for_year_and_type,
get_transactiontypes_and_total_amount_transactions,
get_transactiontype_and_sum_for_spf_export,
get_right_engagement_and_sump_spf,
get_asset_liability_and_sump_spf,
)
from comptaClub.site_config import load
import locale
EXTENSES = 0
RECETTES = 1
def comptability_export(request, accounting_year, export_type):
"""
Définit une fonction d'export.
Args:
request: La requête HTTP
accounting_year (int): L'année à exporter.
export_type: à choisir parmi `sxs` (side by side) ou `ooo` (one over other).
Returns:
render_to_response sur la page HTML correspondante.
"""
if export_type == "sxs":
return get_transaction_list_for_accountingyear_sxs(request, accounting_year)
else:
return get_transaction_list_for_accountingyear_ooo(request, accounting_year)
def get_transaction_list_for_accountingyear_ooo(request, accounting_year):
"""
Affichage et calcul des `Recettes` et `Dépenses` pour une année donnée dans deux tableaux l'un
au dessus de l'autre (one over other - ooo).
"""
transactions_list_expenses = get_transactions_and_sums_for_year_and_type(
accounting_year, EXTENSES
)
transactions_list_recettes = get_transactions_and_sums_for_year_and_type(
accounting_year, RECETTES
)
context = {
"transactions_list_expenses": transactions_list_expenses,
"accounting_year": accounting_year,
"transactions_list_recettes": transactions_list_recettes,
}
return render(request, "year_transaction_export_ooo.html", context)
# def two_table_side_by_side_export(request, accounting_year):
def get_transaction_list_for_accountingyear_sxs(request, accounting_year):
"""
Calcule et affiche la comptabilité d'une année passée en parametre dans un unique tableau (side
by side).
"""
expenses_transactiontypes_list = get_transactiontypes_and_total_amount_transactions(
accounting_year, EXTENSES
)
recettes_transactiontypes_list = get_transactiontypes_and_total_amount_transactions(
accounting_year, RECETTES
)
transactiontypes_list = zip_longest(
recettes_transactiontypes_list["transactiontypes_info_list"],
expenses_transactiontypes_list["transactiontypes_info_list"],
)
context = {
"transactiontypes_list": transactiontypes_list,
"accounting_year": accounting_year,
"total_expenses": expenses_transactiontypes_list["total"],
"total_recettes": recettes_transactiontypes_list["total"],
}
return render(request, "year_transaction_export_sxs.html", context)
def export_year_spf_finance(request, accounting_year):
"""
Calcule et affiche la comptabilité d'une année passée en parametre dans un unique tableau (side
by side).
La fonction va chercher chaque type de dépenses/recettes père (n'ayant pas de parent).
Pour chacun de ces types, … <<< on fait quoi >>> ?
"""
annuality = Annuality.objects.filter(year__year=accounting_year)
transactiontypes_list_expenses = get_transactiontype_and_sum_for_spf_export(
accounting_year, EXTENSES
)
transactiontypes_list_recettes = get_transactiontype_and_sum_for_spf_export(
accounting_year, RECETTES
)
list_sum = [
x
for x in zip_longest(
transactiontypes_list_recettes["sum"], transactiontypes_list_expenses["sum"]
)
]
# règle d'évaluation
rules_list = EvaluationRules.objects.filter(
Q(stop_date__year__lte=accounting_year) | Q(stop_date__isnull=True)
).exclude(start_date__year__gt=accounting_year)
rules_adaptation_list = EvaluationRulesAdaptation.objects.filter(
start_date__year=accounting_year
)
complementary_informations = ComplementaryInformations.objects.filter(
annuality=annuality[0]
)
liquidity = 0
assets_list = get_asset_liability_and_sump_spf(accounting_year, category=0)
for item in assets_list:
if item[0] == "Liquidité":
item[1] += (
annuality[0].opening_balance
+ transactiontypes_list_recettes["sum_total_transaction"]
- transactiontypes_list_expenses["sum_total_transaction"]
)
liquidity = item[1]
asset_liability_list = get_asset_liability_and_sump_spf(accounting_year, category=1)
asset_liability_sum = [x for x in zip_longest(assets_list, asset_liability_list)]
right_list = get_right_engagement_and_sump_spf(accounting_year, category=0)
engagement_list = get_right_engagement_and_sump_spf(accounting_year, category=1)
right_engagement_sum = [x for x in zip_longest(right_list, engagement_list)]
if len(right_engagement_sum) == 0:
right_engagement_sum = None
annuality[0].closing_balance = liquidity
annuality[0].save()
context = {
"list_sum": list_sum,
"accounting_year": accounting_year,
"total_expenses": transactiontypes_list_expenses["sum_total_transaction"],
"total_recette": transactiontypes_list_recettes["sum_total_transaction"],
"rules_list": rules_list,
"rules_adaptation_list": rules_adaptation_list,
"complementary_informations": complementary_informations,
"asset_liability_sum": asset_liability_sum,
"right_engagement_sum": right_engagement_sum,
}
return render(request, "year_transaction_export_spf.html", context)
X = 35
Y = 841.89
INDENT = 5
RIGHT_X = 595.27 - X
TITLED_X = 125
INDENTED_X = X + INDENT
INDENTED_RIGHT_X = RIGHT_X - INDENT
MIDDLE = (RIGHT_X - X) / 2
PRESTATION_COLUMN_2 = INDENTED_X + 65
PRESTATION_COLUMN_3 = INDENTED_X + 400
PRESTATION_COLUMN_4 = INDENTED_X + 455
COMMON_LINE_HEIGHT = -15
SMALL_LINE_HEIGHT = COMMON_LINE_HEIGHT + 5
LARGE_LINE_HEIGHT = COMMON_LINE_HEIGHT - 5
DOUBLE_LINE_HEIGHT = COMMON_LINE_HEIGHT * 2
BIG_LINE_HEIGHT = COMMON_LINE_HEIGHT * 3
HUGE_LINE_HEIGHT = COMMON_LINE_HEIGHT * 4
class MyDocument(object):
# http://www.reportlab.com/docs/reportlab-userguide.pdf
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
def newline(self, height=None):
"""
Passe à la ligne, la hauteur de la ligne est passée en paramètre.
"""
if height == SMALL_LINE_HEIGHT:
self.y += SMALL_LINE_HEIGHT
elif 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 download(self):
# Close the PDF object cleanly, and we're done.
self.document.showPage()
self.document.save()
def header(self, info):
rect_height = 40
self.document.rect(X, self.y, RIGHT_X - X, -rect_height, fill=0)
self.drawNewLine(INDENTED_X, "A.S.B.L. : " + info["CLUB_NAME"])
self.document.drawRightString(
INDENTED_RIGHT_X, self.y, "N° Entreprise : " + info["BCE"]
)
self.drawNewLine(INDENTED_X, "Siège social : " + info["SIEGE_SOCIAL"])
self.newline(BIG_LINE_HEIGHT)
def title(self, accounting_year):
self.drawString(
130,
"Comptes simplifiés de l'exercice " + accounting_year,
font_decoration="Bold",
font_size=20,
)
self.newline(DOUBLE_LINE_HEIGHT)
def recette_depense(self, accounting_year):
self.drawString(
X, "Etat recettes/dépenses (en €)", font_decoration="Oblique", font_size=14
)
self.__display_table_transaction(accounting_year)
self.newline()
def __display_table_transaction(self, accounting_year):
self.__display_table_header("DEPENSES", "RECETTES")
transactions_extenses = get_transactiontype_and_sum_for_spf_export(
accounting_year, EXTENSES
)
transactions_recettes = get_transactiontype_and_sum_for_spf_export(
accounting_year, RECETTES
)
self.__display_table_transaction_body(
transactions_extenses, transactions_recettes
)
self.__display_table_footer(
int(transactions_extenses["sum_total_transaction"]),
int(transactions_recettes["sum_total_transaction"]),
)
def __display_table_header(self, title_left, title_right):
self.newline()
self.document.rect(X, self.y - 5, RIGHT_X - X, -COMMON_LINE_HEIGHT, fill=0)
self.drawString(INDENTED_X, title_left, font_decoration="Bold", font_size=9)
self.drawString(
MIDDLE + INDENTED_X, title_right, font_decoration="Bold", font_size=9
)
# TODO : à revoir !!!!!!
def __display_table_transaction_body(
self, transactions_extenses, transactions_recettes
):
for i in range(4):
self.__display_table_line(
transactions_extenses["sum"][i][0],
int(transactions_extenses["sum"][i][1]),
transactions_recettes["sum"][i][0],
int(transactions_recettes["sum"][i][1]),
)
self.newline()
self.document.rect(
X, self.y - 5, MIDDLE, 5 * -COMMON_LINE_HEIGHT
) # Séparation en deux
self.document.rect(
MIDDLE, self.y - 5, MIDDLE, 5 * -COMMON_LINE_HEIGHT
) # Séparation descriptif et montant recettes
self.y -= COMMON_LINE_HEIGHT
def __display_table_footer(self, totaldepense, totalrecette):
locale.setlocale(locale.LC_ALL, "pt_br.utf-8")
self.document.rect(X, self.y - 5, RIGHT_X - X, COMMON_LINE_HEIGHT, fill=0)
self.newline()
# first column
self.drawString(
INDENTED_X, "Total des dépenses ", font_decoration="Bold", font_size=9
)
self.document.drawRightString(
MIDDLE + X - INDENT,
self.y,
str(locale.format("%d", int(totaldepense), grouping=True)),
)
# second column
self.drawString(
(MIDDLE + INDENTED_X),
"Total des recettes ",
font_decoration="Bold",
font_size=9,
)
self.document.drawRightString(
INDENTED_RIGHT_X,
self.y,
str(locale.format("%d", int(totalrecette), grouping=True)),
)
def __display_table_line(self, key1, value1, key2, value2):
locale.setlocale(locale.LC_ALL, "pt_br.utf-8")
self.newline()
self.document.rect(X, self.y - 5, RIGHT_X - X, 15, fill=0) # ligne entière
# first column
self.drawString(INDENTED_X, key1, font_size=9)
self.document.drawRightString(
MIDDLE + X - INDENT,
self.y,
str(locale.format("%d", int(value1), grouping=True)),
)
# second column
self.drawString((MIDDLE + INDENTED_X), key2, font_size=9)
self.document.drawRightString(
INDENTED_RIGHT_X,
self.y,
str(locale.format("%d", int(value2), grouping=True)),
)
def annexes(self, accounting_year):
""" Ajoute les annexes du document au PDF
Args:
accounting_year (int): année comptable
Returns:
Ne retourne rien
"""
self.drawNewLine(X, "Annexes", font_decoration="Oblique", font_size=14)
self.display_evaluation_rules(accounting_year)
self.display_modification_evaluation_rules(accounting_year)
self.display_additional_information(accounting_year)
self.display_patrimony(accounting_year)
self.display_right_engagement(accounting_year)
def display_evaluation_rules(self, accounting_year):
""" Ajoute les règles d'évaluation au PDF
Args:
accounting_year (int): année comptable
Returns:
Ne retourne rien
"""
self.drawNewLine(X, "1. Résumé des règles d'évaluation")
rules_list = EvaluationRules.objects.filter(
Q(stop_date__year__lte=accounting_year) | Q(stop_date__isnull=True)
).exclude(start_date__year__gt=accounting_year)
if rules_list:
for rule in rules_list:
self.drawNewLine(
INDENTED_X + INDENT * 2,
rule.label + " : " + rule.explanation,
font_size=9,
)
else:
self.drawNewLine(
INDENTED_X + INDENT * 2, "Pas de règle d'évaluation.", font_size=9
)
self.newline()
def display_modification_evaluation_rules(self, accounting_year):
""" Ajoute les modifications d'évaluation au PDF
Args:
accounting_year (int): année comptable
Returns:
Ne retourne rien
"""
self.drawNewLine(X, "2. Adaptation des règles d'évaluation")
rules_adaptation_list = EvaluationRulesAdaptation.objects.filter(
start_date__year=accounting_year
)
if rules_adaptation_list:
for line in rules_adaptation_list:
self.drawNewLine(
INDENTED_X + INDENT * 2,
line.label + " : " + line.information,
font_size=9,
)
else:
self.drawNewLine(
INDENTED_X + INDENT * 2,
"Pas d'adaptation des règles d'évaluation.",
font_size=9,
)
self.newline()
def display_additional_information(self, accounting_year):
""" Ajoute les informations complémentaires au PDF
Args:
accounting_year (int): année comptable
Returns:
Ne retourne rien
"""
self.drawNewLine(X, "3. Informations complémentaires")
annuality = Annuality.objects.filter(year__year=accounting_year)
complementary_informations = ComplementaryInformations.objects.filter(
annuality=annuality[0]
)
if complementary_informations:
for line in complementary_informations:
self.drawNewLine(
INDENTED_X + INDENT * 2,
line.label + " : " + line.information,
font_size=9,
)
else:
self.drawNewLine(
INDENTED_X + INDENT * 2,
"Pas d'informations complémentaires.",
font_size=9,
)
self.newline()
def display_patrimony(self, accounting_year):
""" Ajoute les informations du patrimoine au PDF
Args:
accounting_year (int): année comptable
Returns:
Ne retourne rien
"""
self.drawNewLine(X, "4. Etat du patrimoine")
annuality = Annuality.objects.filter(year__year=accounting_year)
tmp_compta_extenses = get_transactiontype_and_sum_for_spf_export(
accounting_year, EXTENSES
)
tmp_compta_recettes = get_transactiontype_and_sum_for_spf_export(
accounting_year, RECETTES
)
assets_list = get_asset_liability_and_sump_spf(accounting_year, category=0)
for item in assets_list:
if item[0] == "Liquidité":
item[1] += (
annuality[0].opening_balance
+ tmp_compta_recettes["sum_total_transaction"]
- tmp_compta_extenses["sum_total_transaction"]
)
liability_list = get_asset_liability_and_sump_spf(accounting_year, category=1)
self.__display_table_header("AVOIRS", "DETTES")
save = self.y
self.__display_table_two_column(assets_list, 1)
longest_y = self.y
self.y = save
self.__display_table_two_column(liability_list, 2)
if self.y > longest_y:
self.y = longest_y
self.newline()
def __display_table_two_column(self, list, column=1):
if column == 1:
begin_rect_line = X
begin_rect_res = MIDDLE
begin_text = INDENTED_X
begin_res = MIDDLE + X - INDENT
else:
begin_rect_line = MIDDLE + X
begin_rect_res = MIDDLE * 2
begin_text = MIDDLE + INDENTED_X
begin_res = INDENTED_RIGHT_X
total = 0
locale.setlocale(locale.LC_ALL, "pt_br.utf-8")
for line in list:
self.document.rect(
begin_rect_line, self.y - 5, MIDDLE, COMMON_LINE_HEIGHT, fill=0
)
self.document.rect(
begin_rect_res, self.y - 5, X, COMMON_LINE_HEIGHT, fill=0
)
self.drawNewLine(begin_text, line[0])
self.document.drawRightString(
begin_res, self.y, str(locale.format("%d", int(line[1]), grouping=True))
)
total += int(line[1])
return total
def display_right_engagement(self, accounting_year):
""" Ajoute les droits & engagements au PDF
Args:
accounting_year (int): année comptable
Returns:
Ne retourne rien
"""
self.drawNewLine(
X,
"5. Droits et engagements importants qui ne sont pas susceptibles d'être quantifiés",
)
# self.__display_table_header("DROITS", "ENGAGEMENT")
self.drawNewLine(
INDENTED_X + INDENT * 2, "Pas de droits ou d'engagements.", font_size=9
)
self.newline()
def generate_pdf_for_spf(request, accounting_year):
""" Génère un fichier PDF suivant les contraintes du SPF Finances
Args:
accounting_year (int): année
Returns:
???
"""
response = HttpResponse(content_type="application/pdf")
response["Content-Disposition"] = (
'attachment; filename="comptes_simplifies_annuels_' + accounting_year + '.pdf"'
)
document = MyDocument(response)
info = load(request)
document.header(info)
document.title(accounting_year)
document.recette_depense(accounting_year)
document.annexes(accounting_year)
document.download()
return response
def __get_transactions_for_accounting_year_and_kind(
accounting_year, transaction_kind=None
):
"""
"""
transactions_list = Transaction.objects.by_year(accounting_year)
if transaction_kind == "bank":
transaction_kind = "Banque"
transactions_list = transactions_list.filter(bkAmount__isnull=False).exclude(
bkAmount=0
)
elif transaction_kind == "box":
transaction_kind = "Caisse"
transactions_list = transactions_list.filter(bxAmount__isnull=False).exclude(
bxAmount=0
)
else:
transaction_kind = "Tous"
transactions_list = transactions_list.order_by("registrationDate")
return transactions_list, transaction_kind
def transaction_by_year_listing(request, accounting_year, transaction_kind=None):
""" Liste toutes les lignes de comptabilité pour une année recue en paramètre
Args:
accounting_year (int): année comptable
transaction_kind (string):
"""
(
transactions_list,
transaction_kind,
) = __get_transactions_for_accounting_year_and_kind(
accounting_year, transaction_kind
)
# pour mon dictionnaire date/total
previous_date = None
date_sum = OrderedDict()
tmp_Total = 0
tmp_Amount = 0
sum_extense = 0
sum_extense_simulated = 0
sumrecette = 0
sumrecette_simulated = 0
transaction_list_and_sum = []
for transaction in transactions_list:
# transaction type :
# 0 : dépense
# 1 : recette
# Suivant le type de la transaction, on ajoute son montant aux recettes ou au dépenses et
# on ajoute le signe ("+" ou "-") afin de pouvoir l'utiliser dans des calculs après.
tmp_Amount = transaction.totalAmount
if transaction.transaction_type.transaction_type == EXTENSES:
sum_extense_simulated += tmp_Amount
if transaction.is_simulated is not None and transaction.is_simulated == 0:
sum_extense += tmp_Amount
tmp_Amount = -tmp_Amount
else:
sumrecette_simulated += tmp_Amount
if transaction.is_simulated is not None and transaction.is_simulated == 0:
sumrecette += tmp_Amount
# Si la transaction n'est pas simulée on ajoute la transaction au total temporaire.
if (
transaction.is_simulated is not None and transaction.is_simulated == 0
) and (transaction.is_done is not None and transaction.is_done == 1):
tmp_Total += tmp_Amount
transaction_list_and_sum.append((transaction, tmp_Total))
if transaction.registrationDate.date() in date_sum:
date_sum[transaction.registrationDate.date()] += tmp_Amount
else:
if previous_date is not None:
date_sum[transaction.registrationDate.date()] = (
date_sum[previous_date] + tmp_Amount
)
else:
date_sum[transaction.registrationDate.date()] = tmp_Amount
previous_date = transaction.registrationDate.date()
total = sumrecette - sum_extense
total_simulated = sumrecette_simulated - sum_extense_simulated
nb_transaction = transactions_list.count()
context = {
"transaction_list": transaction_list_and_sum,
"accounting_year": accounting_year,
"totaldepense": sum_extense,
"totalrecette": sumrecette,
"totaldepense_simulated": sum_extense_simulated,
"totalrecette_simulated": sumrecette_simulated,
"total": total,
"total_simulated": total_simulated,
"date_sum": date_sum,
"nb_transaction": nb_transaction,
"t_type": transaction_kind,
}
return render(request, "year_transaction_listing.html", context)
def year_listing(request):
"""
Liste toutes les années pour lesquelles il y a des transactions.
"""
years = Transaction.objects.dates("registrationDate", "year")
year_list = []
for year in years:
year_list.append((year.year, Transaction.objects.by_year(year.year).count(),))
context = {"year_list": year_list}
return render(request, "year_listing.html", context)
def transaction_listing_for_year_and_type(
request, accounting_year, transaction_type_id
):
"""
Liste toutes les transactions d'un `type de transaction` et pour une année passés en paramètre.
Args:
request (???):
accounting_year (int): année
transaction_type_id (int): id d'un type de transaction
Returns:
(???)
"""
transaction_type = TransactionType.objects.get(pk=transaction_type_id)
# by_year_condition = Q(registrationDate__year=accounting_year)
by_type_condition = Q(transaction_type=transaction_type_id)
by_parent_type_condition = Q(transaction_type__parent=transaction_type_id)
transaction_list = (
Transaction.objects.by_year(accounting_year)
.filter((by_type_condition | by_parent_type_condition))
.order_by("registrationDate")
)
total = 0
total_simulated = 0
transaction_list_and_sum = []
for transaction in transaction_list:
total_simulated += transaction.totalAmount
if transaction.is_simulated is not None and transaction.is_simulated == 0:
total += transaction.totalAmount
transaction_list_and_sum.append((transaction, total))
context = {
"transaction_list": transaction_list_and_sum,
"transaction_type": str(transaction_type),
"accounting_year": accounting_year,
"total_simulated": total_simulated,
"total": total,
}
return render(request, "transaction_listing.html", context)
def transaction_details(request, transactionid):
"""
Récupère les détails d'une transaction
"""
transaction = Transaction.objects.get(pk=transactionid)
context = {"transaction": transaction}
return render(request, "year_transaction_details.html", context)