Script Python de orçamento pessoal

Publicado por Pedro Fernandes (última atualização em 30/01/2023)

[ Hits: 2.979 ]

Homepage: https://github.com/PedroF37

Download Orcamento-Pessoal.tar.gz




Projeto de Aplicativo em Python de Controle de Receitas e Despesas

Aplicativo permite entrar com as receitas e categorias de despesas, depois mostra a receita total, os gastos e valor restante de toda a receita. Permite editar valores  e categorias e deletar a tabela para iniciar outra nova (se isso faz algum sentido..). Projeto feito para praticar e para aprender um pouco mais de matplotlib, pandas e sqlite. Não está 100 por cento perfeito mas está perfeitamente funcional.

Usa o tkinter, matplotlib, pandas, sqlite e o pillow

Arquivos e icones e imagens estão em:
https://github.com/PedroF37/Orcamento-Pessoal

  



Esconder código-fonte

Arquivo - main.py


# --------------------------------------------------------------------------- #
# IMPORTAÇÕES


# tkinter
from tkinter import Tk, Frame, Label, Button, Entry, messagebox
from tkinter.ttk import Style, Progressbar, Treeview, Scrollbar, Combobox

# Pillow
from PIL import Image, ImageTk

# tkcalendar
from tkcalendar import DateEntry

# matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt

# view
from view import insert_category, insert_revenue, insert_expenses
from view import show_category_records, drop_tables
from view import table, bar_graph_values, update_instruction
from view import pie_graph_values, percentage_bar_values

# bd_create
from bd_create import create_category, create_revenue, create_expenses

# re
import re


# --------------------------------------------------------------------------- #
# CONSTANTES E GLOBAIS


COLOR0 = "#2e2d2b"
COLOR1 = "#feffff"
COLOR2 = "#4fa882"
COLOR3 = "#38576b"
COLOR4 = "#403d3d"
COLOR5 = "#e06636"
COLOR6 = "#038cfc"
COLOR7 = "#3fbfb9"
COLOR8 = "#263238"
COLOR9 = "#e9edf5"
COLOR10 = '#545454'
COLOR11 = '#83a9e6'

COLORS = [
    '#5588bb', '#66bbbb',
    '#99bb55', '#ee9944',
    '#444466', '#bb5555'
]


# Padrões para validações das Entries
string_pattern = r'^[A-Za-z]+( [A-Za-z]+)*$'
number_pattern = r'^\d+$'


global tree


# --------------------------------------------------------------------------- #
# FUNÇÕES


def manipulate_tables():
    """Função que cuida de deletar e criar as tabelas."""
    drop_tables()
    refresh_data()
    messagebox.showinfo(
        'Sucesso', f'Dados deletados com sucesso. A visualização' \
        f' pode ficar estranha enquanto não reiniciar o aplicativo.'
    )


def percentage():
    """Função para mostrar a percentagem."""
    percent_message_label = Label(
        middle_frame, text='Porcentagem da receita restante',
        height=1, anchor='nw', font=('Verdana 12'),
        bg=COLOR1, fg=COLOR4
    )
    percent_message_label.place(x=7, y=5)

    style = Style()
    style.theme_use('default')
    style.configure(
        'black.Horizontal.TProgressbar',
        background='#daed6b'
    )
    style.configure('TProgressbar', thickness=25)

    progressbar = Progressbar(
        middle_frame, length=180,
        style='black.Horizontal.TProgressbar'
    )
    progressbar.place(x=10, y=35)
    progressbar['value'] = percentage_bar_values()

    percent_value = percentage_bar_values()
    percent_number_label = Label(
        middle_frame, text=f'{percent_value:,.2f}%',
        anchor='nw', font=('Verdana 12'),
        bg=COLOR1, fg=COLOR4
    )
    percent_number_label.place(x=200, y=35)


def bar_graph():
    """Função que cuida do gráfico de barra."""
    category_list = ['Renda', 'Despesas', 'Saldo']
    values_list = bar_graph_values()

    # O gráfico e os eixos. Esta parte dos gráficos
    # foi muito copy/paste. matplotlib ainda é meio
    # "chinês" para mim kk.
    graph = plt.Figure(figsize=(4, 3.45), dpi=60)
    ax = graph.add_subplot(111)
    # ax.autoscale(enable=True, axis='both', tight=None)

    ax.bar(category_list, values_list, color=COLORS, width=0.9)

    c = 0
    for item in ax.patches:
        ax.text(
            item.get_x() - .001, item.get_height() + .5,
            f'{values_list[c]:,.0f}', fontsize=17,
            fontstyle='italic', verticalalignment='bottom',
            color='dimgrey'
        )

        c += 1

    ax.set_xticklabels(category_list, fontsize=16)

    # Personalizando o gráfico
    ax.patch.set_facecolor('#ffffff')
    ax.spines['bottom'].set_color('#CCCCCC')
    ax.spines['bottom'].set_linewidth(1)
    ax.spines['right'].set_linewidth(0)
    ax.spines['top'].set_linewidth(0)
    ax.spines['left'].set_color('#CCCCCC')
    ax.spines['left'].set_linewidth(1)

    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    ax.spines['left'].set_visible(False)
    ax.tick_params(bottom=False, left=False)
    ax.set_axisbelow(True)
    ax.yaxis.grid(True, color='#EEEEEE')
    ax.xaxis.grid(False)

    canvas = FigureCanvasTkAgg(graph, middle_frame)
    canvas.get_tk_widget().place(x=10, y=70)


def summary():
    """Função que cuida do sumário da renda e despesas."""
    values = bar_graph_values()

    # Renda Mensal
    line_label = Label(
        middle_frame, text='',
        width=215, height=1,
        anchor='nw', font=('Arial 1'),
        bg=COLOR10
    )
    # line_label.place(x=349, y=52)
    line_label.place(x=500, y=52)

    summary_label = Label(
        middle_frame, text='TOTAL RENDA MENSAL      ',
        anchor='nw', font=('Verdana 12'),
        bg=COLOR1, fg=COLOR11
    )
    # summary_label.place(x=349, y=33)
    summary_label.place(x=500, y=33)

    value_label = Label(
        middle_frame, text=f'R$ {values[0]:,.2f}',
        anchor='nw', font=('Arial 17'),
        bg=COLOR1, fg=COLOR10
    )
    # value_label.place(x=349, y=70)
    value_label.place(x=500, y=70)

    # Despesas Mensais
    line_label = Label(
        middle_frame, text='',
        width=215, height=1,
        anchor='nw', font=('Arial 1'),
        bg=COLOR10
    )
    # line_label.place(x=349, y=132)
    line_label.place(x=500, y=132)

    summary_label = Label(
        middle_frame, text='TOTAL DESPESAS MENSAIS',
        anchor='nw', font=('Verdana 12'),
        bg=COLOR1, fg=COLOR11
    )
    # summary_label.place(x=349, y=113)
    summary_label.place(x=500, y=113)

    value_label = Label(
        middle_frame, text=f'R$ {values[1]:,.2f}',
        anchor='nw', font=('Arial 17'),
        bg=COLOR1, fg=COLOR10
    )
    # value_label.place(x=349, y=150)
    value_label.place(x=500, y=150)

    # Saldo Total
    line_label = Label(
        middle_frame, text='',
        width=215, height=1,
        anchor='nw', font=('Arial 1'),
        bg=COLOR10
    )
    # line_label.place(x=349, y=212)
    line_label.place(x=500, y=212)

    summary_label = Label(
        middle_frame, text='SALDO TOTAL DA CAIXA   ',
        anchor='nw', font=('Verdana 12'),
        bg=COLOR1, fg=COLOR11
    )
    # summary_label.place(x=349, y=193)
    summary_label.place(x=500, y=193)

    value_label = Label(
        middle_frame, text=f'R$ {values[2]:,.2f}',
        anchor='nw', font=('Arial 17'),
        bg=COLOR1, fg=COLOR10
    )
    # value_label.place(x=349, y=230)
    value_label.place(x=500, y=230)


def pie_graph():
    """Função que cuida do gráfico circular (pie graph)."""
    graph = plt.Figure(figsize=(5, 3), dpi=90)
    ax = graph.add_subplot(111)

    category_list = pie_graph_values()[0]   # Categoria
    values_list = pie_graph_values()[1]     # Valores

    explode = []
    for item in category_list:
        explode.append(0.05)

    ax.pie(
        values_list, explode=explode,
        wedgeprops=dict(width=0.2),
        autopct='%1.1f%%', colors=COLORS,
        shadow=True, startangle=90
    )
    ax.legend(
        category_list,
        loc="center right",
        bbox_to_anchor=(1.55, 0.50)
    )

    category_canvas = FigureCanvasTkAgg(graph, pie_graph_frame)
    category_canvas.get_tk_widget().place(x=130, y=10)


def show_table():
    """Função que cuida de mostrar a tabela dos dados."""
    global tree
    table_label = Label(
        middle_frame, text='Tabela Receitas e Despesas',
        anchor='nw', font=('Verdana 12'), bg=COLOR1, fg=COLOR4
    )
    table_label.place(x=5, y=309)

    table_header = ['#id', 'Categoria', 'Data', 'Quantia']
    records = table()

    tree = Treeview(
        table_frame, selectmode='extended',
        columns=table_header, show='headings'
    )

    # Barra de rolagem vertical
    vsb = Scrollbar(table_frame, orient='vertical', command=tree.yview)

    # Barra de rolagem horizontal
    hsb = Scrollbar(table_frame, orient='horizontal', command=tree.xview)

    tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)

    tree.grid(row=0, column=0, sticky='nsew')
    vsb.grid(row=0, column=1, sticky='ns')
    hsb.grid(row=1, column=0, sticky='ew')

    # Posicionamento
    hd = ['center', 'center', 'center', 'center']
    h = [30, 100, 100, 100]
    n = 0

    for item in table_header:
        tree.heading(item, text=item.title(), anchor='center')
        tree.column(item, width=h[n], anchor=hd[n])

        n += 1

    for item in records:
        tree.insert('', 'end', values=item)


def insert_new_category():
    """Função que cuida da inserção de novas categorias."""
    category_name = [new_category_entry.get()]

    # Validação
    for item in category_name:
        if item == '':
            messagebox.showerror('Erro', 'Preencha todos os campos')
            return

    # Só aceita alfabeto
    category_string = ' '.join(category_name)
    if re.fullmatch(string_pattern, category_string) is None:
        messagebox.showerror('Erro', 'Categoria aceita apenas letras')
        return
    else:
        insert_category(category_name)
        messagebox.showinfo('Sucesso', 'Dados inseridos com sucesso')
        reset_widgets(new_category_entry)

    # Pegando os valores da categoria
    categories = show_category_records()
    category = []

    for item in categories:
        # item[1] -> categoria/ item[0] -> id
        category.append(item[1])

    # atualizando dados
    expense_category_combo['values'] = (category)


def insert_new_renevue():
    """Função que cuida da inserção de novas receitas."""
    revenue_name = 'Receita'
    revenue_date = revenue_calendar_entry.get()
    revenue_value = total_value_revenue_entry.get()

    insert_revenue_list = [revenue_name, revenue_date, revenue_value]

    # Validação
    for item in insert_revenue_list:
        if item == '':
            messagebox.showerror('Erro', 'Preencha todos os campos')
            return

    # Só aceita numérico em revenue_value
    if re.fullmatch(number_pattern, revenue_value) is None:
        messagebox.showerror(
            'Erro', 'Quantia aceita apenas digitos [0-9]'
        )
        return
    else:
        insert_revenue(insert_revenue_list)
        messagebox.showinfo('Sucesso', 'Dados inseridos com sucesso')
        reset_widgets(revenue_calendar_entry, total_value_revenue_entry)

    # atualizando dados
    refresh_data()


def insert_new_expenses():
    """Função que cuida da inserção de novas despesas."""

    # Não pode inserir nada se ainda não tiver receita nunhuma.
    # bar_graph_values()[0] == valor total da receita.
    if bar_graph_values()[0] == 0:
        messagebox.showerror(
            'Erro',
            'Não pode inserir despesas sem ter uma receita antes'
        )
        return
    else:
        expense_name = expense_category_combo.get()
        expense_date = expense_calendar_entry.get()
        expense_value = total_value_expense_entry.get()

        insert_expense_list = [expense_name, expense_date, expense_value]

        # Validação
        for item in insert_expense_list:
            if item == '':
                messagebox.showerror('Erro', 'Preencha todos os campos')
                return

        # Só aceita numérico em expense_value
        if re.fullmatch(number_pattern, expense_value) is None:
            messagebox.showerror(
                'Erro', 'Quantia aceita apenas digitos [0-9]'
            )
            return
        else:
            insert_expenses(insert_expense_list)
            messagebox.showinfo('Sucesso', 'Dados inseridos com sucesso')

    reset_widgets(
        expense_category_combo,
        expense_calendar_entry,
        total_value_expense_entry
    )
    # atualizando dados
    refresh_data()


def reset_widgets(*widgets):
    """Função que reseta os entries e combos depois de inserções."""
    for widget in widgets:
        widget.delete(0, 'end')
    return


def refresh_data():
    """Função que cuida de atualizar
    os dados depois de inserções/remoções."""
    percentage()
    summary()
    bar_graph()
    pie_graph()
    show_table()
    return


def edit_data():
    """Função que cuida de editar/alterar dados da tabela."""
    replaced_category = alter_category_entry.get()
    replaced_amount = alter_value_entry.get()

    if tree.focus() == '':
        messagebox.showerror(
            'Erro',
            'Tem que selecionar um registro na TABELA da esquerda para alterar'
        )
        return
    else:
        treeview_data = tree.focus()
        treeview_dict = tree.item(treeview_data)
        treeview_list = treeview_dict['values']

        # Aqui, obrigo a preencher apenas um dos campos e não os dois,
        # porque podemos querer mudar apenas o valor do aluguél por exemplo,
        # ou pudemos queres mudar apenas o nome da despesa. Ou os dois kk
        if replaced_category == '' and replaced_amount == '':
            messagebox.showerror(
                'Erro', 'Tem que preencher pelo menos um dos campos'
            )
            return
        else:
            # Agora temos que validar o ou os inputs
            if replaced_category != '':
                # Só alfabeto
                if re.fullmatch(string_pattern, replaced_category) is None:
                    messagebox.showerror(
                        'Erro', 'Categoria aceita apenas letras'
                    )
                    return

            if replaced_amount != '':
                # Só digito
                if re.fullmatch(number_pattern, replaced_amount) is None:
                    messagebox.showerror(
                        'Erro', 'Quantia aceita apenas digitos [0-9]'
                    )
                    return

        update_instruction(
            treeview_list, replaced_category, replaced_amount
        )
        messagebox.showinfo('Sucesso', 'Dados atualizados com sucesso')
        refresh_data()
        reset_widgets(alter_category_entry, alter_value_entry)


# --------------------------------------------------------------------------- #
# CRIA TABELAS CASO NECESSÁRIO


# Instrução sql em bd_create é: create if not exists
# logo se já existir não faz nada ok?!
create_category()
create_revenue()
create_expenses()


# --------------------------------------------------------------------------- #
# JANELA


window = Tk()
window.title('')
window.geometry('1320x648')
window.resizable(width=False, height=False)
# window.configure(background=COLOR9)

style = Style(window)
style.theme_use('clam')


# --------------------------------------------------------------------------- #
# FRAMES, TÍTULO E LOGO


# Frames principais
upper_frame = Frame(
    window, width=1320,
    height=50, bg=COLOR1,
    relief='flat'
)
upper_frame.grid(row=0, column=0, padx=0)

middle_frame = Frame(
    window, width=1320, height=361,
    bg=COLOR1, pady=20, relief='raised'
)
middle_frame.grid(
    row=1, column=0,
    pady=1, padx=0,
    sticky='nsew'
)

lower_frame = Frame(
    window, width=1320, height=237,
    bg=COLOR1, relief='flat'
)
lower_frame.grid(
    row=2, column=0,
    pady=0, padx=0,
    sticky='nsew'
)


# Frames dentro de Frames
pie_graph_frame = Frame(
    middle_frame, width=580,
    height=250, bg=COLOR1
)
# pie_graph_frame.place(x=415, y=5)
pie_graph_frame.place(x=720, y=5)

table_frame = Frame(
    lower_frame, width=330,
    height=237, bg=COLOR1
)
table_frame.grid(row=0, column=0)

expenses_insert_frame = Frame(
    lower_frame, width=330,
    height=237, bg=COLOR1
)
expenses_insert_frame.grid(row=0, column=1, padx=5)

revenue_insert_frame = Frame(
    lower_frame, width=330,
    height=237, bg=COLOR1
)
revenue_insert_frame.grid(row=0, column=2)

alter_table_frame = Frame(
    lower_frame, width=330,
    height=237, bg=COLOR1
)
alter_table_frame.grid(row=0, column=3)


# Título e logo do app
img = Image.open('Icones/money-bag.png')
img = img.resize((45, 45))
img = ImageTk.PhotoImage(img)

title_label = Label(
    upper_frame, image=img,
    text=' Controle de Receitas e Despesas',
    width=1400, compound='left',
    padx=5, relief='raised',
    anchor='nw', bg=COLOR1,
    fg=COLOR4, font=('Verdana 20 bold')
)
title_label.grid(row=0, column=0)


# --------------------------------------------------------------------------- #
# DESPESAS


expenses_label = Label(
    expenses_insert_frame,
    text='Insira novas Despesas',
    height=1, anchor='nw',
    font=('Verdana 10 bold'),
    bg=COLOR1, fg=COLOR4
)
expenses_label.place(x=10, y=5)

# Categoria das despesas
category_label = Label(
    expenses_insert_frame,
    text='Categoria',
    height=1, anchor='nw',
    font=('Roboto 10'),
    bg=COLOR1, fg=COLOR4
)
category_label.place(x=10, y=40)

# Pegandos categorias
categories_list = show_category_records()
categories = []

for category in categories_list:
    categories.append(category[1])

expense_category_combo = Combobox(
    expenses_insert_frame,
    width=10, font=('Roboto 10')
)
expense_category_combo['values'] = (categories)
expense_category_combo.place(x=110, y=41)

# Data das despesas
expense_date_label = Label(
    expenses_insert_frame,
    text='Data', height=1,
    anchor='nw', font=('Roboto 10'),
    bg=COLOR1, fg=COLOR4
)
expense_date_label.place(x=10, y=70)

expense_calendar_entry = DateEntry(
    expenses_insert_frame,
    width=9, background='darkgrey',
    foreground='white', year=2023,
    borderwidth=2
)
expense_calendar_entry.place(x=110, y=71)

# Quantia total das despesas
total_value_expense_label = Label(
    expenses_insert_frame,
    text='Quantia Total',
    height=1, anchor='nw',
    font=('Roboto 10'),
    bg=COLOR1, fg=COLOR4
)
total_value_expense_label.place(x=10, y=102)

total_value_expense_entry = Entry(
    expenses_insert_frame,
    width=10, justify='left',
    relief='solid'
)
total_value_expense_entry.place(x=110, y=101)

# Botão adicionar
add_expenses_img = Image.open('Icones/add.png')
add_expenses_img = add_expenses_img.resize((17, 17))
add_expenses_img = ImageTk.PhotoImage(add_expenses_img)

insert_expenses_button = Button(
    expenses_insert_frame,
    image=add_expenses_img,
    text='ADICIONAR', width=68,
    compound='left', anchor='se',
    font=('Roboto 7 bold'), bg=COLOR1,
    fg=COLOR4, overrelief='ridge',
    command=insert_new_expenses
)
insert_expenses_button.place(x=110, y=141)


# --------------------------------------------------------------------------- #
# RECEITAS


revenues_label = Label(
    revenue_insert_frame,
    text='Insira novas Receitas/Categorias',
    height=1, anchor='nw', font=('Verdana 10 bold'),
    bg=COLOR1, fg=COLOR4
)
revenues_label.place(x=10, y=5)

# Data das receitas
revenue_date_label = Label(
    revenue_insert_frame, text='Data',
    height=1, anchor='nw', font=('Roboto 10'),
    bg=COLOR1, fg=COLOR4
)
revenue_date_label.place(x=10, y=40)

revenue_calendar_entry = DateEntry(
    revenue_insert_frame, width=9,
    background='darkgrey', foreground='white',
    borderwidth=2, year=2023
)
revenue_calendar_entry.place(x=110, y=41)

# Quantia total das receitas
total_value_revenue_label = Label(
    revenue_insert_frame, text='Quantia Total',
    height=1, anchor='nw', font=('Roboto 10'),
    bg=COLOR1, fg=COLOR4
)
total_value_revenue_label.place(x=10, y=74)

total_value_revenue_entry = Entry(
    revenue_insert_frame, width=10,
    justify='left', relief='solid'
)
total_value_revenue_entry.place(x=110, y=71)

add_revenue_img = Image.open('Icones/add.png')
add_revenue_img = add_revenue_img.resize((17, 17))
add_revenue_img = ImageTk.PhotoImage(add_revenue_img)

insert_revenue_button = Button(
    revenue_insert_frame,
    image=add_revenue_img,
    text='ADICIONAR', width=68,
    compound='left', anchor='se',
    font=('Roboto 7 bold'),
    bg=COLOR1, fg=COLOR4,
    overrelief='ridge',
    command=insert_new_renevue
)
insert_revenue_button.place(x=110, y=111)


# --------------------------------------------------------------------------- #
# NOVA CATEGRIA


new_category_label = Label(
    revenue_insert_frame,
    text='Nova Categoria',
    height=1, anchor='nw',
    font=('Roboto 10'),
    bg=COLOR1, fg=COLOR4
)
new_category_label.place(x=10, y=163)

new_category_entry = Entry(
    revenue_insert_frame, width=10,
    justify='left', relief='solid'
)
new_category_entry.place(x=110, y=161)

add_category_img = Image.open('Icones/add.png')
add_category_img = add_category_img.resize((17, 17))
add_category_img = ImageTk.PhotoImage(add_category_img)

insert_new_category_button = Button(
    revenue_insert_frame,
    image=add_category_img,
    text='ADICIONAR', width=68,
    compound='left', anchor='se',
    font=('Roboto 7 bold'),
    bg=COLOR1, fg=COLOR4,
    overrelief='ridge',
    command=insert_new_category
)
insert_new_category_button.place(x=110, y=195)


# --------------------------------------------------------------------------- #
# ALTERAR DADOS DA TABELA


alter_data_label = Label(
    alter_table_frame,
    text='Alterar/Deletar Tabela',
    height=1, anchor='nw',
    font=('Verdana 10 bold'),
    bg=COLOR1, fg=COLOR4
)
alter_data_label.place(x=70, y=5)

# Categoria das despesas
alter_category_label = Label(
    alter_table_frame,
    text='Categoria',
    height=1, anchor='nw',
    font=('Roboto 10'),
    bg=COLOR1, fg=COLOR4
)
alter_category_label.place(x=70, y=40)

alter_category_entry = Entry(
    alter_table_frame, width=10,
    justify='left', relief='solid'
)
alter_category_entry.place(x=170, y=41)

alter_value_label = Label(
    alter_table_frame, text='Quantia Total',
    height=1, anchor='nw', font=('Roboto 10'),
    bg=COLOR1, fg=COLOR4
)
alter_value_label.place(x=70, y=108)

alter_value_entry = Entry(
    alter_table_frame, width=10,
    justify='left', relief='solid'
)
alter_value_entry.place(x=170, y=101)

alter_img = Image.open('Icones/replace.png')
alter_img = alter_img.resize((17, 17))
alter_img = ImageTk.PhotoImage(alter_img)

alter_button = Button(
    alter_table_frame,
    image=alter_img,
    text='ALTERAR     ', width=68,
    compound='left', anchor='se',
    font=('Roboto 7 bold'),
    bg=COLOR1, fg=COLOR4,
    overrelief='ridge',
    command=edit_data
)
alter_button.place(x=170, y=131)


# --------------------------------------------------------------------------- #
# DELETAR TODA A TABELA


destroy_label = Label(
    alter_table_frame,
    text='Apagar tabela',
    height=1, anchor='nw',
    font=('Roboto 10 bold'),
    bg=COLOR1, fg=COLOR4
)
destroy_label.place(x=70, y=197)

destroy_img = Image.open('Icones/delete.png')
destroy_img = destroy_img.resize((17, 17))
destroy_img = ImageTk.PhotoImage(destroy_img)

# Os espaços são propositais. Para os icones
# adicionar e deletar ficarem alinhados.
destroy_button = Button(
    alter_table_frame,
    image=destroy_img,
    text='DELETAR     ',
    width=68, compound='left',
    anchor='se', font=('Roboto 7 bold'),
    bg=COLOR1, fg=COLOR4, overrelief='ridge',
    command=manipulate_tables
)
destroy_button.place(x=170, y=191)


# --------------------------------------------------------------------------- #
# LOOP


percentage()
summary()
bar_graph()
pie_graph()
show_table()
window.mainloop()

# -------------------------------------------------------------------------------------------------------------- #

Arquivo - view.py

# --------------------------------------------------------------------------- #
# IMPORTAÇÕES


import sqlite3
import pandas as pd


# --------------------------------------------------------------------------- #
# CONEXÃO


connection = sqlite3.connect('data.db')


# --------------------------------------------------------------------------- #
# INSERÇÕES


def insert_category(category):
    """Função para insterir categoria em tabela"""
    with connection:
        cursor = connection.cursor()
        insertion = 'insert into Categoria (nome) values (?)'
        cursor.execute(insertion, category)


def insert_revenue(revenue):
    """Função para insterir receita em tabela"""
    with connection:
        cursor = connection.cursor()
        insertion = '''
            insert into Receitas(
                categoria,
                adicionado_em, valor
            )
            values (?, ?, ?)
        '''
        cursor.execute(insertion, revenue)


def insert_expenses(expenses):
    """Função para insterir gastos em tabela"""
    with connection:
        cursor = connection.cursor()
        insertion = '''
            insert into Gastos(
                categoria,
                retirado_em, valor
            )
            values (?, ?, ?)
        '''
        cursor.execute(insertion, expenses)


# --------------------------------------------------------------------------- #
# EDIÇÃO/ATUALIZAÇÃO


def update_instruction(record, category, amount):
    """Função que cuida da atualização dos dados da tabela."""

    # Monta a instrução de update, de acordo com os campos preenchidos.
    if record[1] == 'Receita':
        with connection:
            cursor = connection.cursor()
            update = 'update Receitas set valor = (?) where id = (?)'
            cursor.execute(update, [amount, record[0]])
    else:
        # Aqui, não tem perigo de fazer deste jeito, pois
        # no arquivo main já impeço de ter dois campos vazios
        # antes de chamar esta função.
        if category == '':
            new_category = record[1]
            new_amount = amount
        elif amount == '':
            new_category = category
            new_amount = record[3]
        else:
            new_category = category
            new_amount = amount

        with connection:
            cursor = connection.cursor()
            update_expense = '''
                update Gastos
                set categoria = (?),
                valor = (?)
                where id = (?)
                limit 1
            '''
            update_category = '''
                update Categoria
                set nome = (?)
                where id = (?)
            '''
            cursor.execute(
                update_expense,
                [new_category, new_amount, record[0]]
            )
            cursor.execute(update_category, [new_category, record[0]])


# --------------------------------------------------------------------------- #
# REMOÇÃO DAS TABELAS


def drop_tables():
    """Função que cuida de apagar a tabela toda."""
    with connection:
        cursor = connection.cursor()

        for item in ('Categoria', 'Receitas', 'Gastos'):
            cursor.execute(f'delete from {item}')


# --------------------------------------------------------------------------- #
# VISUALIZAÇÕES


def show_category_records():
    """Função para mostrar todos os registros da tabela categorias."""
    datalist = []
    with connection:
        cursor = connection.cursor()
        cursor.execute('select * from Categoria')
        records = cursor.fetchall()

        for record in records:
            datalist.append(record)

    return datalist


def show_revenue_records():
    """Função para mostrar todos os registros da tabela receitas."""
    datalist = []
    with connection:
        cursor = connection.cursor()
        cursor.execute('select * from Receitas')
        records = cursor.fetchall()

        for record in records:
            datalist.append(record)

    return datalist


def show_expenses_records():
    """Função para mostrar todos os registros da tabela gastos."""
    datalist = []
    with connection:
        cursor = connection.cursor()
        cursor.execute('select * from Gastos')
        records = cursor.fetchall()

        for record in records:
            datalist.append(record)

    return datalist


# --------------------------------------------------------------------------- #
# ATUALIZAÇÃO DOS GRÁFICOS, SUMÁRIO E TABELA


def table():
    """Função que cuida dos dados da tabela."""
    expenses = show_expenses_records()
    revenues = show_revenue_records()

    table_list = []

    for item in expenses:
        table_list.append(item)

    for item in revenues:
        table_list.append(item)

    return table_list


def bar_graph_values():
    """Função que cuida dos dados do gráfico de barra e do sumário."""

    # Receita total
    revenues = show_revenue_records()
    revenues_list = []

    for item in revenues:
        revenues_list.append(item[3])

    total_revenue = sum(revenues_list)

    # Despesas totais
    expenses = show_expenses_records()
    expenses_list = []

    for item in expenses:
        expenses_list.append(item[3])

    total_expenses = sum(expenses_list)

    # Saldo total
    total_credit = total_revenue - total_expenses
    return (total_revenue, total_expenses, total_credit)


def pie_graph_values():
    """Cuida dos dados do gráfico circular (pie)"""
    expenses = show_expenses_records()
    table_list = []

    for item in expenses:
        table_list.append(item)

    dataframe = pd.DataFrame(
        table_list,
        columns=[
            'id', 'categoria',
            'Data', 'valor'
        ]
    )

    dataframe = dataframe.groupby('categoria')['valor'].sum()

    amount_list = dataframe.values.tolist()
    category_list = []

    for item in dataframe.index:
        category_list.append(item)

    return ([category_list, amount_list])


def percentage_bar_values():
    """Cuida dos valores da barra de porcentagem."""

    # Aqui, se não tiver dados, exemplo, se for a primeira vez
    # que executa o script ou se tiver deletado a tabela, dá erro
    # por causa da divisão por zero (ZeroDivision). Logo, se
    # bar_graph_values()[1] == gastos_totais for igual a zero
    # retorna apenas a receita.
    if bar_graph_values()[1] == 0:
        return bar_graph_values()[0]
    else:
        return (
            (
                bar_graph_values()[0] - bar_graph_values()[1]
            ) / bar_graph_values()[0]) * 100

# ----------------------------------------------------------------------------------------------------------------- #

Arquivo - bd_create.py (main.py executa este arquivo, não precisa rodar manualmente)

import sqlite3


# Conexão
connection = sqlite3.connect('data.db')

'''
Segundo o que li na documentação do sqlite3, não é recomendado
usar o atributo autoincrement. Até porque pelo vi, o sqlite3
incrementa automaticamente o id (rowid), quando especificamos
id integer primary key.
'''

# tabela categoria
def create_category():
    with connection:
        cursor = connection.cursor()
        cursor.execute(
            '''create table if not exists Categoria(
                    id integer primary key,
                    nome text
            )'''
        )

# tabela receita
def create_revenue():
    with connection:
        cursor = connection.cursor()
        cursor.execute(
            '''create table if not exists Receitas(
                    id integer primary key,
                    categoria text,
                    adicionado_em date,
                    valor decimal
            )'''
        )

# tabela de gastos
def create_expenses():
    with connection:
        cursor = connection.cursor()
        cursor.execute(
            '''create table if not exists Gastos(
                    id integer primary key,
                    categoria text,
                    retirado_em date,
                    valor decimal
            )'''
        )

Scripts recomendados

Interface para qemu

Script de Inventário em Python

Gerador de páginas .html

Calculadora para números complexos

SnakeSQL - Crie consultas SQL facilmente


  

Comentários
[1] Comentário enviado por maurixnovatrento em 12/02/2023 - 13:02h


muito bom.

___________________________________________________________
Conhecimento não se Leva para o Túmulo.
https://github.com/mxnt10

[2] Comentário enviado por sabe nada em 12/02/2023 - 20:16h


[1] Comentário enviado por mauricio123 em 12/02/2023 - 13:02h


muito bom.

___________________________________________________________
Conhecimento não se Leva para o Túmulo.
https://github.com/mxnt10


Obrigado


Contribuir com comentário




Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts