diff --git a/.gitignore b/.gitignore index 21d0b89..a43aaf3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .venv/ +config diff --git a/compta.svg b/compta.svg index c36f6be..25331fa 100644 --- a/compta.svg +++ b/compta.svg @@ -1,12 +1,12 @@ - + - 2025-06-21T21:06:46.225677 + 2025-06-29T12:13:37.019521 image/svg+xml @@ -21,316 +21,170 @@ - - +" style="fill: #ffffff"/> - +" clip-path="url(#p5d1f3076d5)" style="fill: #007e97"/> - +" clip-path="url(#p5d1f3076d5)" style="fill: #007e97"/> - +" clip-path="url(#p5d1f3076d5)" style="fill: #999999"/> - +" clip-path="url(#p5d1f3076d5)" style="fill: #999999"/> - +" clip-path="url(#p5d1f3076d5)" style="fill: #007e97"/> - +" clip-path="url(#p5d1f3076d5)" style="fill: #007e97"/> - - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - + + diff --git a/config b/config index a13aef1..29b540b 100644 --- a/config +++ b/config @@ -1,4 +1,4 @@ [path] -wp-config=/var/www/bricolesocialclub.org/wp-config.php +wp-config=wp-config.php budget=budget.csv -save_directory=/var/www/bsc.pbblanc.fr/wp-content/uploads/compta +save_directory=. diff --git a/main.py b/main.py index 5338421..8ad527d 100644 --- a/main.py +++ b/main.py @@ -5,11 +5,14 @@ import configparser as cp import os from sankeyflow import Sankey import matplotlib.pyplot as plt +import matplotlib.patches as mpatches import re import datetime import mariadb import sys - +import math +import numpy as np +from colorsys import hls_to_rgb #node_central = 'total' class BudgetLine(): @@ -18,6 +21,8 @@ class BudgetLine(): self.desc = desc self.charge = charge self.revenue = revenue + + class DBInfos(): @@ -39,11 +44,30 @@ def main(path_b:str, path_wp:str, save_dir:str): budget=import_csv(path_b) - create_budget_sankey(budget,save_dir) + budget = sort_des_budget(budget, 0) + #create_budget_sankey(budget,save_dir) db_infos = get_db_infos(path_wp) - cursor = connect_mariadb(db_infos) - create_accounting_sankey(cursor, save_dir) - + #cursor = connect_mariadb(db_infos) + #create_accounting_sankey(cursor, save_dir) + + #finances, last_update = import_finances(cursor) + finances = import_csv(path_b) + finances = test_finance(finances) + last_update = rf'2025-01-01' + finances = sort_des_budget(finances, 0) + tot_rev = get_total_revenues(finances) + tot_ch = get_total_charges(finances) + totals = get_totals_lists_for_charts( + tot_ch_bud = get_total_charges(budget), + tot_ch_real = tot_ch, + tot_re_bud = get_total_revenues(budget), + tot_re_real = tot_rev + ) + revenue_ratios = get_revenue_ratios(tot_rev, finances) + charge_ratios = get_charge_ratios(tot_ch, finances) + create_total_diagram(save_dir, totals, last_update) + create_pie_diagrams(save_dir, revenue_ratios, charge_ratios, last_update) + print('oucou') #connection à la db # Lister les comptes qui m'intéressent @@ -51,7 +75,243 @@ def main(path_b:str, # Pour chaque compte 7 puis 6 : # - calculer somme entre le 1 er janvier et ajd # - ajouter valeur dans flow et nodes + + +def test_finance(budget:list[BudgetLine]): + coef = -500 + for line in budget: + if coef > 0 : + if line.revenue == 0 : + line.charge += coef + else : + line.revenue += coef + if coef < 0 : + if line.revenue == 0 and line.charge > abs(coef) : + line.charge += coef + elif line.charge == 0 and line.revenue > abs(coef) : + line.revenue += coef + return budget + + +def sort_des_budget(budget:list[BudgetLine], li:int): + if li < len(budget)-1 : + ch = budget[li].charge + rev = budget[li].revenue + if (ch > 0 and ch >= budget[li+1].charge) or (ch == 0 and budget[li+1].charge ==0 and rev >=budget[li+1].revenue ): + budget = sort_des_budget(budget, li+1) + else : + if li == 0 : + budget = [budget[li+1], budget[li]] + budget[li+2:] + budget = sort_des_budget(budget, li) + elif li < len(budget) - 2 : + budget = budget[:li] + [budget[li+1], budget[li]] + budget[li+2:] + budget = sort_des_budget(budget, li-1) + else : + budget = budget[:li] + [budget[li+1], budget[li]] + budget = sort_des_budget(budget, li-1) + + return budget + + +def get_revenue_ratios(tot:int, budget:list[BudgetLine]): + values = {} + oth = 0 + coef = 4 + i = 0 + for line in budget: + if line.revenue >0 : + if i < coef: + values.update( + {line.desc:line.revenue} + ) + i+=1 + else: + oth += line.revenue + if oth > 0 : + values.update( + {'Autres':oth} + ) + return values + + +def get_charge_ratios(tot:int, budget:list[BudgetLine]): + values = {} + oth = 0 + coef = 4 + i = 0 + for line in budget: + if line.charge >0 : + if i < coef: + values.update( + {line.desc:line.charge} + ) + i+=1 + else: + oth += line.charge + if oth > 0 : + values.update( + {'Autres':oth} + ) + return values + +def import_finances(cursor): + # extraire le compte et la caisse au 1 er janvier de l'année + year = datetime.datetime.now().year + first_day = rf'{year}-01-01' + accounts = get_income_and_expense_accounts(cursor) + finances = [] + last_update = first_day + for a in accounts: + tot_deb, tot_cred, last_entrie = get_sum_of_operations( + cursor, + first_day, + a + ) + if a.type == 6 : + #charge + finances.append(BudgetLine(a.label, tot_deb - tot_cred, 0)) + elif a.type == 7: + finances.append(BudgetLine(a.label, 0, tot_cred - tot_deb)) + if last_entrie >= last_update : + last_update = last_entrie + return finances, last_update + + +def get_total_charges(budget:list[BudgetLine]): + tot = 0 + for line in budget: + tot += line.charge + return int(tot) + + +def get_total_revenues(budget:list[BudgetLine]): + tot = 0 + for line in budget: + tot += line.revenue + return int(tot) + + +def get_totals_lists_for_charts(tot_ch_bud, tot_ch_real, tot_re_bud, tot_re_real): + rest_ch = abs(tot_ch_bud - tot_ch_real) + rest_re = abs(tot_re_bud - tot_re_real) + if tot_ch_bud >= tot_ch_real and tot_re_bud >= tot_re_real: + + values = { + f'actuel' : [tot_re_real, tot_ch_real], + f'budget' : [rest_re, rest_ch] + } + else : + values = { + f'bot_budget' : [int(0.98 * tot_re_bud), int(0.98 * tot_ch_bud)], + f'budget' : [int(0.02 *tot_re_bud), int(0.02 *tot_ch_bud)], + f'actuel' : [rest_re, rest_ch] + } + return values + + +def hls_to_rgbs(s_hsl): + rgb = hls_to_rgb(s_hsl["Hue"], s_hsl["Luminosité"], s_hsl["Saturation"] ) + return "#" + "".join("%02X" % round(i*255) for i in rgb ) + + +def get_color_list(nb, s_hsl, sens = "Blanc") : + temp = s_hsl + luminosite = temp["Luminosité"] + + if sens == "Blanc": + ecart = (1-luminosite ) /(nb +1) + else : + ecart = luminosite / (nb + 1) + + l_couleur = [] + for x in range(nb) : + l_couleur.append(hls_to_rgbs(temp )) + if sens == "Blanc" : + luminosite += ecart + else : + print(luminosite) + luminosite -= ecart + temp["Luminosité"] = luminosite + return l_couleur + + +def create_pie_diagrams(path:str, + revenue_ratios:dict[str, int], + charge_ratios:dict[str, int], + last_update:str): + + bleu_hls = {"Hue":190 / 360, + "Saturation":100/100, + "Luminosité":30/100} + + marron_hls = {"Hue":23 / 360, + "Saturation":72/100, + "Luminosité":30/100} + + fig, axs = plt.subplots(ncols=2,figsize=(10, 4), layout='constrained') + labels, values = transform_dict_for_diag(revenue_ratios) + colors = get_color_list(len(values),bleu_hls) + p = axs[0].pie(x=values, labels=labels, colors=colors) + + labels, values = transform_dict_for_diag(charge_ratios) + colors = get_color_list(len(values),marron_hls) + p = axs[1].pie(x=values, labels=labels, colors=colors) + + axs[1].set_title('Répartition des dépenses réalisées') + axs[0].set_title('Répartition des recettes réalisées') + path = os.path.join(path, 'ratios.svg') + plt.savefig(path) + plt.close() + + +def transform_dict_for_diag(dicts:dict): + labels = [] + values = [] + for label, value in dicts.items(): + labels.append(f'{label}\n{value}€') + values.append(value) + + return labels, values + + +def create_total_diagram(path:str, + totals: dict[str, list[int]], + last_upadate:str): + + fig, ax = plt.subplots() + plt.grid(True, 'major', 'y', color="#555555") + width = 0.6 + types = ('dépenses', 'revenus') + leg = [mpatches.Patch(color='#007e97', label=f'au {last_upadate}'), + mpatches.Patch(color='#999999', label='budgeté') + ] + bottom = np.zeros(2) + if len(totals) ==2 : + col = ['#007e97', '#999999'] + else: + col = ['#007e97', '#999999', '#007e97'] + + x = 0 + for position, values in totals.items(): + p = ax.bar(types, values, width, label=position, bottom=bottom, color=col[x] ) + bottom += values + x+=1 + + #ax.invert_yaxis() # labels read top-to-bottom + + ax.set_title('Total des dépenses et des revenus de 2025') + box = ax.get_position() + ax.set_position( + [box.x0, box.y0 + box.height * 0.1, + box.width, box.height * 0.9]# type: ignore + ) + ax.legend(handles=leg, loc='upper center', bbox_to_anchor=(0.5, -0.07), + fancybox=True, shadow=True, ncol=2) + path = os.path.join(path, 'total.svg') + plt.savefig(path) + plt.close() + def create_accounting_sankey(cursor, save_dir): NR = [] @@ -73,7 +333,7 @@ def create_accounting_sankey(cursor, save_dir): tmp_res = reserves tot = reserves for a in accounts: - tot_deb, tot_cred = get_sum_of_operations( + tot_deb, tot_cred, last_entrie = get_sum_of_operations( cursor, first_day, a @@ -110,6 +370,7 @@ def create_accounting_sankey(cursor, save_dir): plt.savefig(path) plt.close() + def get_income_and_expense_accounts(cursor): VERBOSE = False if VERBOSE : print("get_income_and_expense_accounts") @@ -191,12 +452,15 @@ def get_sum_of_operations(cursor, date:str, account:Account): "SELECT piece_num, numero_compte, label_compte, doc_date, code_journal, debit, credit FROM llx_accounting_bookkeeping WHERE numero_compte = ? AND doc_date >= ?", (account.number, date) ) + last_entrie = date for piece_num, numero_compte, label_compte, doc_date, code_journal, debit, credit in cursor : tot_cred += credit tot_deb += debit + if doc_date > last_entrie : + last_entrie = doc_date if VERBOSE : print(f'pour le compte {account.number} : credit = {tot_cred} / debit = {tot_deb} à partir du {date}') - return int(tot_deb), int(tot_cred) + return int(tot_deb), int(tot_cred), last_entrie def get_db_infos(path_wp:str): diff --git a/ratios.svg b/ratios.svg new file mode 100644 index 0000000..52f46b7 --- /dev/null +++ b/ratios.svg @@ -0,0 +1,1212 @@ + + + + + + + + 2025-06-30T21:47:52.936528 + image/svg+xml + + + Matplotlib v3.10.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/total.svg b/total.svg new file mode 100644 index 0000000..0ba119f --- /dev/null +++ b/total.svg @@ -0,0 +1,1024 @@ + + + + + + + + 2025-06-30T21:47:52.656624 + image/svg+xml + + + Matplotlib v3.10.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +