From 17e5df8b4544773265d8e34112e632441a75b70e Mon Sep 17 00:00:00 2001 From: pb Date: Tue, 28 Oct 2025 20:30:58 +0100 Subject: [PATCH] test changement de branche --- .$functionnal diagrams.drawio.bkp | 10 - .vscode/settings.json | 11 + __pycache__/main.cpython-311.pyc | Bin 0 -> 24457 bytes config.ori | 10 +- budget.csv => files/budget.csv | 0 budget.svg => files/budget.svg | 0 compta.svg => files/compta.svg | 0 ratios.svg => files/ratios.svg | 0 total.svg => files/total.svg | 0 libs/DBConnection.py | 41 ++ libs/__pycache__/DBConnection.cpython-311.pyc | Bin 0 -> 2607 bytes .../reporting_finance.cpython-311.pyc | Bin 0 -> 22079 bytes libs/lib_csv.py | 2 - libs/lib_mariadb.py | 8 - libs/lib_matplotlib.py | 2 - libs/reporting_finance.py | 431 ++++++++++++++ main.py | 525 +----------------- requirements.txt | 20 +- tests/__pycache__/compta_test.cpython-311.pyc | Bin 0 -> 1553 bytes tests/compta_test.py | 22 + 20 files changed, 550 insertions(+), 532 deletions(-) delete mode 100644 .$functionnal diagrams.drawio.bkp create mode 100644 .vscode/settings.json create mode 100644 __pycache__/main.cpython-311.pyc rename budget.csv => files/budget.csv (100%) rename budget.svg => files/budget.svg (100%) rename compta.svg => files/compta.svg (100%) rename ratios.svg => files/ratios.svg (100%) rename total.svg => files/total.svg (100%) create mode 100644 libs/DBConnection.py create mode 100644 libs/__pycache__/DBConnection.cpython-311.pyc create mode 100644 libs/__pycache__/reporting_finance.cpython-311.pyc delete mode 100644 libs/lib_csv.py delete mode 100644 libs/lib_mariadb.py delete mode 100644 libs/lib_matplotlib.py create mode 100644 libs/reporting_finance.py create mode 100644 tests/__pycache__/compta_test.cpython-311.pyc create mode 100644 tests/compta_test.py diff --git a/.$functionnal diagrams.drawio.bkp b/.$functionnal diagrams.drawio.bkp deleted file mode 100644 index a455408..0000000 --- a/.$functionnal diagrams.drawio.bkp +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..21e0ca1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "*test*.py" + ], + "python.testing.pytestEnabled": false, + "python.testing.unittestEnabled": true +} \ No newline at end of file diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42da89992afdbc181798cc63830ab28f74f416e6 GIT binary patch literal 24457 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 IL=go4ABj>mqW}N^ literal 0 HcmV?d00001 diff --git a/libs/__pycache__/reporting_finance.cpython-311.pyc b/libs/__pycache__/reporting_finance.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a6745821edff72a5a1f9fa585fe1d453cf7e804 GIT binary patch literal 22079 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 L@|;VUw(= ?", + (account.number, date) + ) + last_entrie = date + for piece_num, numero_compte, label_compte, doc_date, code_journal, debit, credit in self.conn : + tot_cred += credit + tot_deb += debit + if str(doc_date) > last_entrie : + last_entrie = str(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), last_entrie + + def get_income_and_expense_accounts(self): + VERBOSE = False + if VERBOSE : print("get_income_and_expense_accounts") + self.conn.execute( + "SELECT account_number, label FROM llx_accounting_account WHERE fk_pcg_version = 'PCG-BSC' AND active = 1;" + ) + accounts = [] + for account_number, label in self.conn: + tmp = account_number[0] + if (tmp == '6' or tmp == '7') and int(account_number) > 10: + accounts.append(Account(label, account_number, int(tmp))) + + if VERBOSE : [print(f'account {a.number} / {a.label}') for a in accounts] + return accounts + + + def get_bank_reserves(self, date:str): + VERBOSE = False + if VERBOSE : print('get_bank_reserves') + total = 0 + self.conn.execute( + "SELECT piece_num, numero_compte, label_compte, doc_date, code_journal, debit, credit FROM llx_accounting_bookkeeping WHERE (numero_compte = ? OR numero_compte = ?) AND doc_date = ? AND code_journal = ?;", + (5121, 5311, date, 'AN',) + ) + for piece_num, numero_compte, label_compte, doc_date, code_journal, debit, credit in self.conn: + #print(piece_num, numero_compte, label_compte, doc_date, code_journal, debit, credit, sep=" | ") + if VERBOSE : print(f'ajout de : {debit} provenant du compte {label_compte} au total') + total += debit + if VERBOSE : print(f'total = {total}') + return total + + + +class FinanceTable(): + def __init__(self): + self.ft:list[BudgetLine] + + def sort_des_table(self, li:int): + if li < len(self.ft)-1 : + ch = self.ft[li].charge + rev = self.ft[li].revenue + if (ch > 0 and ch >= self.ft[li+1].charge) or (ch == 0 and self.ft[li+1].charge ==0 and rev >=self.ft[li+1].revenue ): + self.sort_des_table(li+1) + else : + if li == 0 : + self.ft = [self.ft[li+1], self.ft[li]] + self.ft[li+2:] + self.sort_des_table( li) + elif li < len(self.ft) - 2 : + self.ft = self.ft[:li] + [self.ft[li+1], self.ft[li]] + self.ft[li+2:] + self.sort_des_table( li-1) + else : + self.ft = self.ft[:li] + [self.ft[li+1], self.ft[li]] + self.sort_des_table(li-1) + + + def get_revenue_ratios(self, 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(self, 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 get_total_charges(self, budget:list[BudgetLine]): + tot = 0 + for line in budget: + tot += line.charge + return int(tot) + + + def get_total_revenues(self, budget:list[BudgetLine]): + tot = 0 + for line in budget: + tot += line.revenue + return int(tot) + + + + +class Budget(FinanceTable): + def __init__(self, csv:str): + with open(csv, "r") as file: + content = file.read() + self.ft = [] + for line in content.split('\n'): + tmp = line.split(';') + self.ft.append( + BudgetLine( + desc=tmp[0], + charge=int(tmp[1]), + revenue=int(tmp[2]) + ) + ) + +class Finance(FinanceTable): + def __init__(self, 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) + self.table = [] + self.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 + self.table.append(BudgetLine(a.label, tot_deb - tot_cred, 0)) + elif a.type == 7: + self.table.append(BudgetLine(a.label, 0, tot_cred - tot_deb)) + if last_entrie >= self.last_update : + self.last_update = last_entrie + + + + + + +def extract_value(content:str, param:str): + tmp = re.search(rf'define...{param}.+', content) + if tmp is not None: + return tmp.group().split(',')[1].split('\'')[1] + else: + return "" + + + + +def get_budget_elements(budget:list[BudgetLine]): + nodes = [] + flows=[] + total = 0 + CL = [] + RL = [] + for line in budget: + charge = line.charge + desc = line.desc + revenue = line.revenue + if charge != 0 and revenue == 0: + total += charge + CL.append((desc, charge, {'label_pos':'right', 'color':'#007e97' })) + flows.append(('Total', desc, charge)) + + elif charge == 0 and revenue != 0: + RL.append((desc, revenue, {'label_pos':'left', 'color':'#007e97' })) + flows.append((desc, 'Total', revenue)) + + nodes = [ + RL, + [('Total', total, {'label_pos':'top', 'color':'#007e97' })], + CL + ] + + return nodes, flows + + +def create_budget_sankey( + budget:list[BudgetLine], + save:str): + + nodes, flows = get_budget_elements(budget) + plt.figure(figsize=(25, 10), dpi=144) + s = Sankey( + flows=flows, + nodes=nodes, + node_opts=dict(label_format='{label}:{value}') + ) + s.draw() + path = os.path.join(save, 'budget.svg') + plt.savefig(path) + plt.close() + +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 = [] + NC = [] + flows = [] + + # 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' + reserves = int(get_bank_reserves(cursor, first_day)) + ro_name = 'reserves au début de l\'année' + ra_name = 'reserves aujourd\'hui' + t_name = 'Total' + NR.append((ro_name, reserves, {'label_pos':'left', 'color':'#007e97'})) + flows.append((ro_name, t_name, reserves)) + + # lister les comptes de charges et produit + accounts = get_income_and_expense_accounts(cursor) + tmp_res = reserves + tot = reserves + for a in accounts: + tot_deb, tot_cred, last_entrie = get_sum_of_operations( + cursor, + first_day, + a + ) + tmp_res = tmp_res + tot_cred - tot_deb + if a.type == 6 : + tmp_tot = tot_deb-tot_cred + if tmp_tot > 0 : + NC.append((a.label, tmp_tot, {'label_pos':'right', 'color':'#007e97' })) + flows.append((t_name, a.label, tmp_tot)) + else : + tmp_tot = tot_cred-tot_deb + if tmp_tot > 0 : + tot += tmp_tot + NR.append((a.label, tmp_tot, {'label_pos':'left', 'color':'#007e97' })) + flows.append((a.label, t_name, tmp_tot)) + + NC.insert(0, (ra_name, tmp_res, {'label_pos':'right', 'color':'#007e97'})) + flows.insert(0, (t_name, ra_name, tmp_res)) + nodes = [ + NR, + [('Total', tot, {'label_pos':'top', 'color':'#007e97'})], + NC + ] + + plt.figure(figsize=(30, 10), dpi=144) + s = Sankey( + flows=flows, + nodes=nodes, + node_opts=dict(label_format='{label}:{value}') + ) + s.draw() + path = os.path.join(save_dir, 'compta.svg') + plt.savefig(path) + plt.close() + diff --git a/main.py b/main.py index 42f78c3..8c78ac2 100644 --- a/main.py +++ b/main.py @@ -1,72 +1,45 @@ # from libs import lib_mariadb as mdb # from libs import lib_csv as csv -# from libs import lib_matplotlib as mtp -import configparser as cp +from libs import reporting_finance as report +from libs.reporting_finance import Budget, FinanceDBConnection +from configparser 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(): - - def __init__(self, desc:str, charge:int, revenue:int): - self.desc = desc - self.charge = charge - self.revenue = revenue - - - - -class DBInfos(): - name:str - user:str - password:str - host:str - port:int - - -class Account(): - def __init__(self, label:str, number:int, tmp:int): - self.label = label - self.number = number - self.type = tmp - + def main(path_b:str, - path_wp:str, + client:FinanceDBConnection, save_dir:str): - budget=import_csv(path_b) - budget = sort_des_budget(budget, 0) + budget = Budget(path_b) + budget.sort_des_table(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) - 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), + + #create_accounting_sankey(client.get_conn(), save_dir) + + finances, last_update = report.import_finances(client.get_conn()) + + finances = report.sort_des_budget(finances, 0) + tot_rev = report.get_total_revenues(finances) + tot_ch = report.get_total_charges(finances) + totals = report.get_totals_lists_for_charts( + tot_ch_bud = report.get_total_charges(budget), tot_ch_real = tot_ch, - tot_re_bud = get_total_revenues(budget), + tot_re_bud = report.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) + revenue_ratios = report.get_revenue_ratios(tot_rev, finances) + charge_ratios = report.get_charge_ratios(tot_ch, finances) + report.create_total_diagram(save_dir, totals, last_update) + report.create_pie_diagrams(save_dir, revenue_ratios, charge_ratios, last_update) print('oucou') #connection à la db @@ -77,473 +50,29 @@ def main(path_b:str, # - 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 = [] - NC = [] - flows = [] - - # 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' - reserves = int(get_bank_reserves(cursor, first_day)) - ro_name = 'reserves au début de l\'année' - ra_name = 'reserves aujourd\'hui' - t_name = 'Total' - NR.append((ro_name, reserves, {'label_pos':'left', 'color':'#007e97'})) - flows.append((ro_name, t_name, reserves)) - - # lister les comptes de charges et produit - accounts = get_income_and_expense_accounts(cursor) - tmp_res = reserves - tot = reserves - for a in accounts: - tot_deb, tot_cred, last_entrie = get_sum_of_operations( - cursor, - first_day, - a - ) - tmp_res = tmp_res + tot_cred - tot_deb - if a.type == 6 : - tmp_tot = tot_deb-tot_cred - if tmp_tot > 0 : - NC.append((a.label, tmp_tot, {'label_pos':'right', 'color':'#007e97' })) - flows.append((t_name, a.label, tmp_tot)) - else : - tmp_tot = tot_cred-tot_deb - if tmp_tot > 0 : - tot += tmp_tot - NR.append((a.label, tmp_tot, {'label_pos':'left', 'color':'#007e97' })) - flows.append((a.label, t_name, tmp_tot)) - - NC.insert(0, (ra_name, tmp_res, {'label_pos':'right', 'color':'#007e97'})) - flows.insert(0, (t_name, ra_name, tmp_res)) - nodes = [ - NR, - [('Total', tot, {'label_pos':'top', 'color':'#007e97'})], - NC - ] - - plt.figure(figsize=(30, 10), dpi=144) - s = Sankey( - flows=flows, - nodes=nodes, - node_opts=dict(label_format='{label}:{value}') - ) - s.draw() - path = os.path.join(save_dir, 'compta.svg') - plt.savefig(path) - plt.close() - - -def get_income_and_expense_accounts(cursor): - VERBOSE = False - if VERBOSE : print("get_income_and_expense_accounts") - cursor.execute( - "SELECT account_number, label FROM llx_accounting_account WHERE fk_pcg_version = 'PCG-BSC' AND active = 1;" - ) - accounts = [] - for account_number, label in cursor: - tmp = account_number[0] - if (tmp == '6' or tmp == '7') and int(account_number) > 10: - accounts.append(Account(label, account_number, int(tmp))) - - if VERBOSE : [print(f'account {a.number} / {a.label}') for a in accounts] - return accounts - - -def get_bank_reserves(cursor, date:str): - VERBOSE = False - if VERBOSE : print('get_bank_reserves') - total = 0 - cursor.execute( - "SELECT piece_num, numero_compte, label_compte, doc_date, code_journal, debit, credit FROM llx_accounting_bookkeeping WHERE (numero_compte = ? OR numero_compte = ?) AND doc_date = ? AND code_journal = ?;", - (5121, 5311, date, 'AN',) - ) - for piece_num, numero_compte, label_compte, doc_date, code_journal, debit, credit in cursor: - #print(piece_num, numero_compte, label_compte, doc_date, code_journal, debit, credit, sep=" | ") - if VERBOSE : print(f'ajout de : {debit} provenant du compte {label_compte} au total') - total += debit - if VERBOSE : print(f'total = {total}') - return total - - -def connect_mariadb(db_infos:DBInfos): - try: - conn = mariadb.connect( - user=db_infos.user, - password=db_infos.password, - host=db_infos.host, - port=db_infos.port, - database=db_infos.name - - ) - except mariadb.Error as e: - print(f"Error connecting to MariaDB Platform: {e}") - sys.exit(1) - - # Get Cursor - return conn.cursor() - - -def import_csv(csv:str): - with open(csv, "r") as file: - content = file.read() - values = [] - for line in content.split('\n'): - tmp = line.split(';') - values.append( - BudgetLine( - desc=tmp[0], - charge=int(tmp[1]), - revenue=int(tmp[2]) - ) - ) - return values - - -def extract_value(content:str, param:str): - tmp = re.search(rf'define...{param}.+', content) - if tmp is not None: - return tmp.group().split(',')[1].split('\'')[1] - else: - return "" - - -def get_sum_of_operations(cursor, date:str, account:Account): - VERBOSE = False - tot_cred = tot_deb = 0 - cursor.execute( - "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 str(doc_date) > last_entrie : - last_entrie = str(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), last_entrie - - -def get_db_infos(path_wp:str): - with open(path_wp, "r") as file: - content = file.read() - db_infos = DBInfos() - db_infos.name = "doli_bsc" - db_infos.user = extract_value(content, 'DB_USER') - db_infos.password = extract_value(content, 'DB_PASSWORD') - db_infos.host = extract_value(content, 'DB_HOST') - db_infos.port = 3306 - return db_infos - - - -def get_budget_elements(budget:list[BudgetLine]): - nodes = [] - flows=[] - total = 0 - CL = [] - RL = [] - for line in budget: - charge = line.charge - desc = line.desc - revenue = line.revenue - if charge != 0 and revenue == 0: - total += charge - CL.append((desc, charge, {'label_pos':'right', 'color':'#007e97' })) - flows.append(('Total', desc, charge)) - - elif charge == 0 and revenue != 0: - RL.append((desc, revenue, {'label_pos':'left', 'color':'#007e97' })) - flows.append((desc, 'Total', revenue)) - - nodes = [ - RL, - [('Total', total, {'label_pos':'top', 'color':'#007e97' })], - CL - ] - - return nodes, flows - - -def create_budget_sankey( - budget:list[BudgetLine], - save:str): - - nodes, flows = get_budget_elements(budget) - plt.figure(figsize=(25, 10), dpi=144) - s = Sankey( - flows=flows, - nodes=nodes, - node_opts=dict(label_format='{label}:{value}') - ) - s.draw() - path = os.path.join(save, 'budget.svg') - plt.savefig(path) - plt.close() if __name__ == "__main__": VERBOSE = False - conf = cp.ConfigParser() + conf = cp() conf.read('config') path_b = conf['path']['budget'] - path_wp_config = conf['path']['wp-config'] path_save = conf['path']['save_directory'] - + + db_client = DBConnection(conf) - if os.path.exists(path_b) and os.path.exists(path_wp_config): + if os.path.exists(path_b): print("les 2 fichiers budget et wp-config ont été trouvé") - main(path_b, path_wp_config, path_save) + main(path_b, db_client, path_save) else : if not os.path.exists(path_b) : msg = "le chemin indiqué pour le paramètre budget dans le fichier config est incorrect. Le document a été supprimé ou déplacer. Merci de préciser un autre chemin" print(msg) print(f'budget = {path_b}') - if not os.path.exists(path_wp_config) : - msg = "le chemin indiqué pour le paramètre wp-config dans le fichier config est incorrect. Le document a été supprimé ou déplacer. Merci de préciser un autre chemin" - print(msg) - print(f'wp-config = {path_wp_config}') if VERBOSE : respo = input('Appuyer sur entrée pour terminer') diff --git a/requirements.txt b/requirements.txt index fd62dec..b6d9b01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,16 @@ configparser==7.2.0 -contourpy==1.3.2 +contourpy==1.3.3 cycler==0.12.1 -fonttools==4.58.2 -kiwisolver==1.4.8 -mariadb==1.1.12 -matplotlib==3.10.3 -mysql-connector-python==9.3.0 -numpy==2.3.0 +fonttools==4.60.1 +kiwisolver==1.4.9 +mariadb==1.1.14 +matplotlib==3.10.7 +mysql-connector-python==9.5.0 +numpy==2.3.4 packaging==25.0 -pandas==2.3.0 -pillow==11.2.1 -pyparsing==3.2.3 +pillow==12.0.0 +pip-review==1.3.0 +pyparsing==3.2.5 python-dateutil==2.9.0.post0 pytz==2025.2 sankeyflow==0.4.1 diff --git a/tests/__pycache__/compta_test.cpython-311.pyc b/tests/__pycache__/compta_test.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2d01abeac8f685af6cce0cf0da99b7eb6ee61af GIT binary patch literal 1553 zcma)6&1)M+6o0e3QWbrW57|nbTvX~1Z$2cFIER13O)6v;v7N_ao@~Ji&`6!+1)p9e(!tU z%)U${qJZO4`(WvJ1>i5S7>>|@*`GsZ3oNim1#)6mDx|99l&YFj2`KPASn4`h8ilep zG~QMLcJT8@E@Y7(Ko9*bmW#ULMQ0mUiMk86L%pb7t=E`q6!@dUKwnN|u-HF~%ob3{ z5essPMb;swS_)FlQqimC#q!dd;Zp7vhGxQABp|;143aHyM`RY+#??-MG{FdX3(&2y zxO$>!S18Z=-ZPk=t}s!d7xp2Ycp9gbA{UnFPczx2T9sz&`D~rl%GBjq+bPyc%&afj z^h%*tt-I!xJTGKLo;>RVGU-fx#S3%l8jbqusgYtbJ|;TBQ%Kg}k5sz*?e*Q%^!wCw zOWTOG@YzQR{HJbVAnRVo%(kEj9ieh)Y$LK0padKIm^7gr?86+~fBsl$5=(g?8_O;? zl`N0Y23FdxMT5a)!{2rL7Q5d^(SYgRfkhK^|9&pkE>g;Vo? zeH>)^#hf;rQIA0_Q=`$pBF+~p+jt>Qle%cfKOY=0QL!FkF`QTgiT4eokEqk2UbwJi zvJ&-FGtYIEi4r}nP@_dptKc=^qWW|?&BTTE;^GJyMcXl*0$n{bdSpf=A_Ct`7uH(Mt4=CtF%?>`Z&U57A?~B&|6_H_fNZt>s?wbbF?AbuT&DO-`cP=6rj; zGrc=@K`@*1eN7=`DJBKzunbuANJH6pJ@NsxwAKZ zzB_*Y)#U5Tza`(^dpGgx#NNf(?!{SQFdFD%e|(rNi(^%bddQ0yhGSN#VR%u)sMf4T zMeuRMc-Sy2zQo_{BpUn#(oXIn3nL(jINSUhlC|LJD}$LbywZiSJH$(!*Y) z;n=R&elJzA^E|_-_^;VcY1k+xdg%W_BKJ`5uF0vN#%UoCCCSu2MP~d;Zn1NSiTlX! oBk8MzkRBXglTQzBc8_}d+DU@R9wwVp?@xXAj}Uw~LMp@m3;*t5egFUf literal 0 HcmV?d00001 diff --git a/tests/compta_test.py b/tests/compta_test.py new file mode 100644 index 0000000..a85744d --- /dev/null +++ b/tests/compta_test.py @@ -0,0 +1,22 @@ +import unittest +from libs.reporting_finance import BudgetLine, import_csv + +class budget_test(unittest.TestCase): + def set_up(self): + self.budget = import_csv('files/budget.csv') + + def test_finance(self): + self.set_up() + coef = -500 + for line in self.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 + \ No newline at end of file