def adicionar_linha(app): ''' Adiciona uma linha com base nos campos de texto. ''' # lemos todos os campos de texto campos = [ dest.atualizar_destino(), date.update(app), vg.atualizar_vagas(), it.atualizar_inteira() ] for campo in campos: if popup.campos_invalidos[campo](): return # aqui, todos os campos de texto possuem valores válidos, então vamos criar a linha linhas = app.linhas destino = monitoradas.destino.get() tempo_exibido = monitoradas.tempo_exibido vagas = monitoradas.vagas.get() inteira = monitoradas.inteira.get() linha = gerar_id_linha(destino, tempo_exibido) # antes de inseri-la graficamente, temos que verificar se ela já existe if linha in estado.linhas_entradas: # Linha desabilitada. Deseja reabilitá-la? if linha not in estado.linhas_visiveis: texto = 'Linha desabilitada, deseja reabilitá-la?.' resposta = messagebox.askyesno(title='Linha existe', message=texto, default='yes') if resposta: app.linhas.reattach(linha, '', 0) estado.linhas_visiveis.add(linha) onibus_filhos_visiveis = set(app.linhas.get_children(linha)) estado.onibus_invisiveis -= onibus_filhos_visiveis monitoradas.onibus_visiveis.update(onibus_filhos_visiveis) ctrl.update_action(app, 'Restaurar') monitoradas.historico_redo.clear() monitoradas.historico_undo.append(['show', [0], [linha]]) app.linhas.selection_set(linha) app.contador_linhas['text'] = fm.form_cont_linhas( len(estado.linhas_visiveis)) app.contador_onibus['text'] = fm.form_cont_onibus( len(monitoradas.onibus_visiveis)) # nova ação: reabilitar else: app.linhas.see(linha) app.linhas.selection_set(linha) return h = minutos_dia(tempo_exibido) estado.linhas_possiveis.discard((destino, h)) estado.horarios_linhas[h] = estado.horarios_linhas.get(h, set()).union( {linha}) dest.add_destino(app) ctrl.update_action(app, 'add') linhas.insert(parent='', index=0, values=(destino, fm.form_tempo(tempo_exibido), vagas, inteira), iid=linha) estado.linhas_visiveis.add(linha) app.contador_linhas['text'] = fm.form_cont_linhas( len(estado.linhas_visiveis)) estado.linhas_entradas[linha] = cte.ESTRUTURA_LINHA( destino, tempo_exibido, int(vagas) // 2, round(float(inteira) * 100 - 300), dict()) data_atual = monitoradas.data_atual.replace(second=0, microsecond=0) for d in range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1): partida = (data_atual + dt.timedelta(d)).replace( hour=tempo_exibido.hour, minute=tempo_exibido.minute) if data_atual <= partida <= data_atual + dt.timedelta( cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA): onibus = gerar_id_onibus(linha, partida) linhas.insert(parent=linha, index='end', values=('-', fm.form_data(partida), vagas, '-'), iid=onibus) monitoradas.onibus_visiveis.add(onibus) estado.linhas_entradas[linha].onibus[onibus] = dict() app.contador_onibus['text'] = fm.form_cont_onibus( len(monitoradas.onibus_visiveis)) app.contador_linhas['text'] = fm.form_cont_linhas( len(estado.linhas_visiveis)) app.contador_onibus['text'] = fm.form_cont_onibus( len(monitoradas.onibus_visiveis)) historico_undo = monitoradas.historico_undo historico_redo = monitoradas.historico_redo historico_redo.clear() historico_undo.append(['add', [[estado.linhas_entradas[linha], list()]]]) linhas.selection_set(linha)
def editar_linha(app): ''' Edita informações sobre uma linha. ''' selecao = app.linhas.selection() if len(selecao) != 1 or app.linhas.parent(selecao[0]) != '': texto = 'Selecione uma única linha.' messagebox.showwarning(title='Impossível Editar', message=texto) return [linha] = selecao dados_linha = estado.linhas_entradas[linha] # a condição a seguir é True sse existe algum ônibus com reservas na linha selecionada. if any(len(x) != 0 for x in dados_linha.onibus.values()): texto = 'Linha possui reservas.' messagebox.showwarning(title='Impossível Editar', message=texto) return campos = [ dest.atualizar_destino(), date.update(app), vg.atualizar_vagas(), it.atualizar_inteira() ] for campo in campos: if popup.campos_invalidos[campo](): return destino_alterada = monitoradas.destino.get() tempo_exibido = monitoradas.tempo_exibido vagas_alterada = monitoradas.vagas.get() inteira_alterada = monitoradas.inteira.get() linha_alterada = gerar_id_linha(destino_alterada, tempo_exibido) h = minutos_dia(estado.linhas_entradas[linha].horario) if linha_alterada in estado.linhas_entradas: if linha_alterada in estado.linhas_visiveis: if linha != linha_alterada: texto = 'Linha já existe.' messagebox.showwarning(title='Impossível Editar', message=texto) elif dados_linha[2:4] != [ int(vagas_alterada) // 2, round(float(inteira_alterada) * 100 - 300) ]: dados_linha_alterada = estado.linhas_entradas[ linha] = cte.ESTRUTURA_LINHA( *dados_linha[:2], int(vagas_alterada) // 2, round(float(inteira_alterada) * 100 - 300), dados_linha[-1]) app.linhas.item( linha, values=campos_linha_formatados(dados_linha_alterada)) app.linhas.selection_set(linha_alterada) monitoradas.historico_redo.clear() monitoradas.historico_undo.append( ['change', dados_linha, dados_linha_alterada, False]) app.contador_linhas['text'] = fm.form_cont_linhas( len(estado.linhas_visiveis)) app.contador_onibus['text'] = fm.form_cont_onibus( len(monitoradas.onibus_visiveis)) else: texto = 'Linha desabilitada, deseja reabilitá-la?' resposta = messagebox.askyesno(title='Impossível Editar', message=texto, default='no') if resposta: app.linhas.reattach(linha_alterada, '', app.linhas.index(linha)) app.linhas.item(linha_alterada, open=app.linhas.item(linha, 'open')) estado.linhas_visiveis.add(linha_alterada) onibus_filhos_visiveis = set( app.linhas.get_children(linha_alterada)) estado.onibus_invisiveis -= onibus_filhos_visiveis monitoradas.onibus_visiveis.update(onibus_filhos_visiveis) ctrl.update_action(app, 'Restaurar') estado.horarios_linhas[h].remove(linha) app.linhas.delete(linha) estado.linhas_visiveis.remove(linha) for onibus in estado.linhas_entradas[linha].onibus: monitoradas.onibus_visiveis.discard(onibus) estado.onibus_invisiveis.discard(onibus) monitoradas.historico_redo.clear() monitoradas.historico_undo.append([ 'change', estado.linhas_entradas[linha], estado.linhas_entradas[linha_alterada], True ]) estado.linhas_possiveis.add((dados_linha.destino.title(), h)) del estado.linhas_entradas[linha] app.linhas.selection_set(linha_alterada) app.contador_linhas['text'] = fm.form_cont_linhas( len(estado.linhas_visiveis)) app.contador_onibus['text'] = fm.form_cont_onibus( len(monitoradas.onibus_visiveis)) return h_alt = tempo_exibido.hour * 60 + tempo_exibido.minute estado.linhas_possiveis.remove((destino_alterada, h_alt)) estado.linhas_possiveis.add((dados_linha.destino.title(), h)) estado.horarios_linhas[h].remove(linha) estado.horarios_linhas[h_alt] = estado.horarios_linhas.get( h_alt, set()).union({linha_alterada}) dest.add_destino(app) ctrl.update_action(app, 'change') indice = app.linhas.index(linha) app.linhas.insert(parent='', index=indice, values=(destino_alterada, fm.form_tempo(tempo_exibido), vagas_alterada, inteira_alterada), iid=linha_alterada, open=app.linhas.item(linha, 'open')) app.linhas.delete(linha) estado.linhas_visiveis.remove(linha) estado.linhas_visiveis.add(linha_alterada) onibus_filhos = dict() data_atual = monitoradas.data_atual.replace(second=0, microsecond=0) for d in range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1): partida = (data_atual + dt.timedelta(d)).replace( hour=tempo_exibido.hour, minute=tempo_exibido.minute) if data_atual <= partida <= data_atual + dt.timedelta( cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA): onibus = gerar_id_onibus(linha_alterada, partida) app.linhas.insert(parent=linha_alterada, index='end', values=('-', fm.form_data(partida), vagas_alterada, '-'), iid=onibus) monitoradas.onibus_visiveis.add(onibus) onibus_filhos[onibus] = dict() estado.linhas_entradas[linha_alterada] = cte.ESTRUTURA_LINHA( destino_alterada, tempo_exibido, int(vagas_alterada) // 2, round(float(inteira_alterada) * 100 - 300), onibus_filhos) for onibus in estado.linhas_entradas[linha].onibus: monitoradas.onibus_visiveis.discard(onibus) estado.onibus_invisiveis.discard(onibus) monitoradas.historico_redo.clear() monitoradas.historico_undo.append([ 'change', estado.linhas_entradas[linha], estado.linhas_entradas[linha_alterada], False ]) del estado.linhas_entradas[linha] app.contador_linhas['text'] = fm.form_cont_linhas( len(estado.linhas_visiveis)) app.contador_onibus['text'] = fm.form_cont_onibus( len(monitoradas.onibus_visiveis)) ctrl.update_action(app, 'change') app.linhas.selection_set(linha_alterada)
) # texto do widget que contém o preço da passagem inteira indice_inteira = 60 # veja o módulo inteira.py para explicação # texto do widget que contém o texto da atual ação (veja update_actions em control.py) cur_action = tk.StringVar() destino = tk.StringVar() # atual destino selecionado no widget # O texto do último destino selecionado. Utilizado quando o usuário digita um destino inválido # e queremos alterar o valor do widget para o último texto válido. texto_anterior = '' if len(estado.destinos) == 0 else estado.destinos[0] # O histórico da aplicação é utilizado para responder aos eventos CTRL+Z e CTRL+SHIFT+Z # cada item da lista é uma tupla (NOME_DA_AÇÃO, INDICES_DAS_LINHAS, ITENS) historico_undo = [] historico_redo = [] previous_selection = set() pressed = False num_entradas = 0 ocupar_assentos = tk.IntVar(value=0) indice_linhas_geradas = 49 janela_reservas = None onibus_visiveis = set() # ids dos ônibus habilitados q ainda não partiram arrecadado = dict() # linha: lista cujos índices representam dias da semana, 0 é domingo ocupacao_media_semanal = dict() tempo_formatado.set(fm.form_tempo(tempo_exibido)) vagas.set(str(2 * (indice_vagas + 1))) inteira.set('%.2f' % (3 + indice_inteira * .01)) cur_action.set('') destino.set(texto_anterior)
def adicionar_linha(app, indice, linha, data_atual=monitoradas.data_atual): ''' Altera o estado da aplicação (incluindo a interface gráfica) para incluir a linha especificada no argumento. ''' destino = linha.campus.title() if destino not in estado.destinos: # adicionamos o destino da linha do argumento, caso ele não exista estado.destinos.append(destino) estado.linhas_possiveis.update({(destino, t) for t in range(1440)}) estado.destinos.sort() # mantemos a ordenação app.entrada_destino['values'] = estado.destinos monitoradas.destino.set(estado.destinos[0]) t = minutos_dia(linha.horario) estado.horarios_linhas[t] = estado.horarios_linhas.get( t, set()).union({linha.id}) # incluimos a linha na interface gráfica estado.linhas_possiveis.discard((destino, t)) app.linhas.insert(parent='', index=indice, values=(destino, fm.form_tempo(linha.horario), 2 * linha.fileiras, it.inteira_termo(linha.inteira)), iid=linha.id) estado.linhas_visiveis.add(linha.id) app.contador_linhas['text'] = fm.form_cont_linhas( len(estado.linhas_visiveis)) estado.linhas_entradas[linha.id] = cte.ESTRUTURA_LINHA( destino, linha.horario, linha.fileiras, linha.inteira, dict()) minutos_atual = minutos_dia(data_atual) # calculamos o período de dias em que haverá ônibus partindo if t == minutos_atual: periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1) elif t < minutos_atual: # aqui, o horário da linha é menor que o horário atual, # então não vamos criar um ônibus para o dia de hoje, pois ele já partiu periodo = range(1, cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1) else: # aqui, o horário da linha é maior que o horário atual, # então vamos incluir o dia de hoje periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA) for d in periodo: # para cada dia no período calculado, vamos adicionar um ônibus # note que d = 0 representa o dia de hoje, d = 1 é amanhã, etc. partida = (data_atual + dt.timedelta(d) ).replace(hour=linha.horario.hour, minute=linha.horario.minute) onibus = gerar_id_onibus(linha.id, partida) app.linhas.insert(parent=linha.id, index='end', values=( '-', fm.form_data(partida), 2*linha.fileiras, '-'), iid=onibus) monitoradas.onibus_visiveis.add(onibus) # todos os assentos estão livres estado.linhas_entradas[linha.id].onibus[onibus] = dict() app.contador_onibus['text'] = fm.form_cont_onibus( len(monitoradas.onibus_visiveis)) app.contador_linhas['text'] = fm.form_cont_linhas( len(estado.linhas_visiveis)) app.contador_onibus['text'] = fm.form_cont_onibus( len(monitoradas.onibus_visiveis))
def gerar(event=None): ''' Gera as linhas conforme a quantidade indicada pelo spin_linhas. É executado quando o usuário clica no botão de gerar, ou aperta ENTER. ''' if not atualizar_num(): # aqui, atualizar_num retornou False, o que significa que a entrada é inválida messagebox.showwarning(title='Entrada Inválida', message='Valor inválido!', parent=configuracoes) return from random import choice, randrange, sample ocupar = monitoradas.ocupar_assentos.get() # Uma sequência em que o valor assento_coordenadas(i, j) indica o número do assento na linha i e coluna j. # Note que para cada linha, há 4 colunas (índice j varia de 0 a 3). # As colunas 0 e 3 são adjacentes à janela e as colunas 1 e 2 são adjacentes ao corredor. # Essa sequência pode ser vista ao tentar reservar um assento. def assento_coordenadas(i, j): return 2*i + j + \ 1 if j <= 1 else 2*(num_fileiras + i + 2) - j dados = [] data_atual = monitoradas.data_atual minutos_atual = data_atual.hour*60 + data_atual.minute # termo(monitoradas.indice_linhas_geradas) é o número de linhas indicado pelo spin_linhas # note que minutes[:termo(monitoradas.indice_linhas_geradas)] seleciona # justamente a quantidade de linhas que queremos! # como minutes foi permutado aleatoriamente, os horários em minutes escolhidos # pelo slicing são aleatórios e diferentes entre si for destino, t in sample(estado.linhas_possiveis, termo(monitoradas.indice_linhas_geradas)): estado.linhas_possiveis.discard((destino, t)) # t é uma quantidade de minutos da lista minutes, que usaremos para calcular o horário dessa linha que # iremos cadastrar! interval = dt.timedelta(minutes=t) horario = data_atual.replace(hour=0, minute=0) + interval if data_atual > horario: horario += dt.timedelta(1) linha = gerar_id_linha(destino, horario) indice_vagas = choice(range(cte.MAXIMO_NUMERO_DE_FILEIRAS)) num_fileiras = indice_vagas + 1 vagas = vg.vagas_termo(indice_vagas) indice_inteira = choice(range(301)) inteira = it.inteira_termo(indice_inteira) estado.horarios_linhas[t] = estado.horarios_linhas.get( t, set()).union({linha}) app.linhas.insert(parent='', index=0, values=(destino, fm.form_tempo( horario), vagas, inteira), iid=linha) estado.linhas_entradas[linha] = cte.ESTRUTURA_LINHA( destino, horario, num_fileiras, indice_inteira, dict()) estado.linhas_visiveis.add(linha) reservas = list() # calculamos o período de dias em que haverá ônibus partindo if t == minutos_atual: periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1) elif t < minutos_atual: # aqui, o horário da linha é menor que o horário atual, # então não vamos criar um ônibus para o dia de hoje, pois ele já partiu periodo = range(1, cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA + 1) else: # aqui, o horário da linha é maior que o horário atual, # então vamos incluir o dia de hoje periodo = range(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA) for d in periodo: # para cada dia no período calculado, vamos adicionar um ônibus # note que d = 0 representa o dia de hoje, d = 1 é amanhã, etc. partida = data_atual + dt.timedelta(d) onibus = gerar_id_onibus(linha, partida) assentos_disponiveis = randrange(vagas+1) if ocupar else vagas app.linhas.insert(parent=linha, index='end', values=( '-', fm.form_data(partida), assentos_disponiveis, '-'), iid=onibus) estado.linhas_entradas[linha].onibus[onibus] = dict() if assentos_disponiveis < vagas: # nesse caso, significa que o usuário pediu para "Ocupar assentos" # e o valor aleatorio assentos disponíveis é menor que a quantidade de vagas # então, devemos fazer algumas reservas aleatórias! # primeiro, vamos escolher quais assentos serão escolhíveis # (afinal, metade dos assentos serão disponíveis, então vamos escolher qual metade) shift = choice(range(2)) # os assentos escolhíveis respeitam as condições impostas pela pandemia # basicamente, selecionamos (i, (i + shift) % 2) e (i, 2 + (i + shift) % 2) para todo i # por exemplo, # i = 0, shift = 0 -> selecionamos (0, 0) e (0, 2) # i = 1, shift = 0 -> selecionamos (1, 1) e (1, 3) # i = 2, shift = 0 -> selecionamos (2, 0) e (2, 2) # Note que a primeira posição é um assento da seção da esquerda # e a segunda posição é um assento da seção da direita # e selecionamos dois assentos por linha assentos_visiveis = {assento_coordenadas(i, 2 * j + (i + shift) % 2) for j in range(2) for i in range(num_fileiras)} # a função sample seleciona aleatoriamente números de assentos escolhíveis para # fazermos reserva for assento in sample(assentos_visiveis, vagas - assentos_disponiveis): # a variável assento é um número de assento escolhível aleatório # devemos reservar esse assento! passagem = choice([2]*3+[1]*2+[0]) estado.linhas_entradas[linha].onibus[onibus][assento] = passagem reserva = gerar_id_reserva(onibus, assento, passagem) estado.reservas.add(reserva) reservas.insert(0, reserva) app.reservas.insert(parent='', index=0, values=campos_reserva_formatados( reserva), iid=reserva) monitoradas.onibus_visiveis.add(onibus) dados.append([estado.linhas_entradas[linha], reservas]) ctrl.update_action(app, 'add') historico_undo = monitoradas.historico_undo historico_redo = monitoradas.historico_redo historico_redo.clear() historico_undo.append(['add', dados]) app.contador_linhas['text'] = fm.form_cont_linhas( len(estado.linhas_visiveis)) app.contador_onibus['text'] = fm.form_cont_onibus( len(monitoradas.onibus_visiveis)) app.contador_reservas['text'] = fm.form_cont_reservas( len(estado.reservas)) configuracoes.destroy()
def exibir_relatorio(app): ''' Constrói e exibe a janela do relatório. ''' data_atual = monitoradas.data_atual janela = tk.Toplevel() janela.grab_set() janela.title('Relatório') janela.resizable(True, True) janela.minsize(700, 700) janela.transient(app.root) frame = ttk.Frame(janela, padding=10) frame.pack(fill='both', expand=True) def toggle_states(master, children): master_value = master.get() for state in children: state.set(master_value) if master_value: janela.minsize(700, 700) botao_exportar.state(['!disabled']) painel_arrecadado.grid() painel_passagens.grid() painel_ocupacao.grid() else: janela.minsize(700, 250) botao_exportar.state(['disabled']) painel_arrecadado.grid_remove() painel_passagens.grid_remove() painel_ocupacao.grid_remove() master_state = tk.IntVar(value=1) checkbox_master = ttk.Checkbutton(frame, variable=master_state) descriptive_label = ttk.Label(frame, text='Informações', anchor='center') distribuicao_passageiros = [0, 0, 0] # [gratuita, meia, inteira] linha_dia_reservas = dict() monitoradas.arrecadado = dict() monitoradas.ocupacao_media_semanal = dict() total_passageiros = len(estado.reservas) def hide_widget(widget, show=False): x, y = janela.minsize() if not show: janela.minsize(x, y - 150) widget.grid_remove() if not any(x.get() for x in [arrecadado_state, passagens_state, ocupacao_state]): botao_exportar.state(['disabled']) return botao_exportar.state(['!disabled']) janela.minsize(x, y + 150) widget.grid() def form_arrecadado(n): return ' Total Arrecadado: R$ %s ' % fm.separar_milhares_decimal(n) painel_arrecadado = ttk.LabelFrame(frame, text='Total Arrecadado', padding=10) arrecadado_state = tk.IntVar(value=1) checkbox_arrecadado = ttk.Checkbutton( frame, variable=arrecadado_state, command=lambda: hide_widget(painel_arrecadado, arrecadado_state.get())) treeview_arrecadado = ttk.Treeview(painel_arrecadado, columns=('Linha', 'Arrecadado'), show='headings', selectmode='none', name='arrecadado') for i, coluna in enumerate(('Linha', 'Arrecadado (R$)')): treeview_arrecadado.heading( i, text=coluna.title(), command=lambda col=i: util.treeview_sort_column( app, treeview_arrecadado, col, False)) treeview_arrecadado.column(i, width=0, minwidth=len(coluna) * 15, anchor='center') treeview_arrecadado.bind( '<Motion>', lambda ev: util.last_separator(ev, treeview_arrecadado)) treeview_arrecadado.bind( '<1>', lambda ev: util.last_separator(ev, treeview_arrecadado)) treeview_arrecadado_scroller_v = ttk.Scrollbar( painel_arrecadado, orient='vertical', command=treeview_arrecadado.yview) treeview_arrecadado_scroller_h = ttk.Scrollbar( painel_arrecadado, orient='horizontal', command=treeview_arrecadado.xview) treeview_arrecadado['yscrollcommand'] = treeview_arrecadado_scroller_v.set treeview_arrecadado['xscrollcommand'] = treeview_arrecadado_scroller_h.set passagens_state = tk.IntVar(value=1) painel_passagens = ttk.LabelFrame(frame, text=' Distribuição de Passagens ', padding=10) checkbox_passagens = ttk.Checkbutton( frame, variable=passagens_state, command=lambda: hide_widget(painel_passagens, passagens_state.get())) label_passageiros = ttk.Label(painel_passagens, text='Total de Passageiros: %s' % fm.separar_milhares(total_passageiros), anchor='center') label_inteira = ttk.Label(painel_passagens, anchor='center') label_meia = ttk.Label(painel_passagens, anchor='center') label_gratuita = ttk.Label(painel_passagens, anchor='center') def form_ocupacao(n): return ' Ocupação Média: %s%% ' % fm.separar_milhares_decimal(n) painel_ocupacao = ttk.LabelFrame(frame, text=form_ocupacao(0), padding=10) ocupacao_state = tk.IntVar(value=1) checkbox_ocupacao = ttk.Checkbutton( frame, variable=ocupacao_state, command=lambda: hide_widget(painel_ocupacao, ocupacao_state.get())) treeview_ocupacao = ttk.Treeview(painel_ocupacao, columns=('Linha', 'Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab'), show='headings', selectmode='none', name='ocupacao') for i, coluna in enumerate( ('Linha', 'Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab')): treeview_ocupacao.heading( i, text=coluna.title(), command=lambda col=i: util.treeview_sort_column( app, treeview_ocupacao, col, False)) treeview_ocupacao.column(i, width=0, minwidth=len(coluna) * 15, anchor='center') treeview_ocupacao.bind( '<Motion>', lambda ev: util.last_separator(ev, treeview_ocupacao)) treeview_ocupacao.bind( '<1>', lambda ev: util.last_separator(ev, treeview_ocupacao)) treeview_ocupacao_scroller_v = ttk.Scrollbar( painel_ocupacao, orient='vertical', command=treeview_ocupacao.yview) treeview_ocupacao_scroller_h = ttk.Scrollbar( painel_ocupacao, orient='horizontal', command=treeview_ocupacao.xview) treeview_ocupacao['yscrollcommand'] = treeview_ocupacao_scroller_v.set treeview_ocupacao['xscrollcommand'] = treeview_ocupacao_scroller_h.set checkbox_master['command'] = lambda: toggle_states( master_state, [arrecadado_state, passagens_state, ocupacao_state]) def exportar_relatorio(): ''' Exporta o relatório como um arquivo .txt ''' # pede um caminho ao usuário endereco = filedialog.asksaveasfilename(title='Exportar Relatório', defaultextension='.txt', filetypes=[('txt', '*.txt'), ('csv', '*.csv'), ('todos', '*')], parent=janela) if endereco == '': return # cria o arquivo no endereço especificado pelo usuário with open(endereco, 'w') as arquivo_relatorio: # as variáveis *_state são IntVar, que possuem valor 1 se o check está selecionado, e 0 caso contrário # vamos escrever no arquivo .txt apenas as seções em que os checks estão marcados if arrecadado_state.get(): arquivo_relatorio.write('{:*^101}\n'.format(' ARRECADAÇÃO ')) arquivo_relatorio.write('%s\n' % painel_arrecadado['text']) arquivo_relatorio.write('{:*^49} | {:*^49}\n'.format( ' LINHA ', ' ARRECADADO (R$) ')) for linha in treeview_arrecadado.get_children(): arquivo_relatorio.write( '{Linha:^50}|{Arrecadado:^50}\n'.format_map( treeview_arrecadado.set(linha))) if passagens_state.get(): if arrecadado_state.get(): arquivo_relatorio.write('\n\n') arquivo_relatorio.write( '{:*^101}\n'.format(' DISTRIBUIÇÃO DE PASSAGENS ')) arquivo_relatorio.write('{:^49} : {:^49}\n'.format( 'TOTAL', fm.separar_milhares(total_passageiros))) arquivo_relatorio.write('{:^49} : {:^49}\n'.format( 'INTEIRA', fm.separar_milhares_precisao(percentual_inteira) + ' %')) arquivo_relatorio.write('{:^49} : {:^49}\n'.format( 'MEIA', fm.separar_milhares_precisao(percentual_meia) + ' %')) arquivo_relatorio.write('{:^49} : {:^49}\n'.format( 'GRATUITA', fm.separar_milhares_precisao(percentual_gratuita) + ' %')) if ocupacao_state.get(): if passagens_state.get(): arquivo_relatorio.write('\n\n') arquivo_relatorio.write( '{:*^167}\n'.format(' OCUPAÇÃO MÉDIA SEMANAL (%) ')) arquivo_relatorio.write( ('|'.join(['{:*^20}'] * 8) + '\n').format( ' LINHA ', ' DOM ', ' SEG ', ' TER ', ' QUAR ', ' QUI ', ' SEX ', ' SAB ')) for linha in treeview_ocupacao.get_children(): nome_linha = treeview_ocupacao.set(linha, 'Linha') arquivo_relatorio.write( ('|'.join(['{:*^20}'] * 8) + '\n').format( f' {nome_linha} ', *map( lambda x: ' ' + fm.separar_milhares_decimal( 100 * x) + ' ', monitoradas.ocupacao_media_semanal[linha]))) botao_exportar = ttk.Button(frame, text='Exportar', command=exportar_relatorio) checkbox_master.grid(row=0, column=0) descriptive_label.grid(row=0, column=1) checkbox_arrecadado.grid(row=1, column=0) painel_arrecadado.grid(row=1, column=1, sticky='news') treeview_arrecadado.grid(row=0, column=0, sticky='news') treeview_arrecadado_scroller_v.grid(row=0, column=1, sticky='ns') treeview_arrecadado_scroller_h.grid(row=1, column=0, sticky='we') painel_arrecadado.rowconfigure(0, weight=1) painel_arrecadado.columnconfigure(0, weight=1) checkbox_passagens.grid(row=2, column=0) painel_passagens.grid(row=2, column=1, sticky='ns') label_passageiros.grid(pady=(0, 10)) label_inteira.grid() label_meia.grid() label_gratuita.grid() for i in range(4): painel_passagens.rowconfigure(i, weight=1) painel_passagens.columnconfigure(0, weight=1) checkbox_ocupacao.grid(row=3, column=0) painel_ocupacao.grid(row=3, column=1, sticky='news') treeview_ocupacao.grid(row=0, column=0, sticky='news') treeview_ocupacao_scroller_v.grid(row=0, column=1, sticky='ns') treeview_ocupacao_scroller_h.grid(row=1, column=0, sticky='we') painel_ocupacao.rowconfigure(0, weight=1) painel_ocupacao.columnconfigure(0, weight=1) botao_exportar.grid(row=4, column=0, columnspan=2) for i in range(3): frame.rowconfigure(i + 1, weight=1) frame.columnconfigure(1, weight=1) for reserva in estado.reservas: onibus = reserva[:-6] linha = onibus[:-7] dados_reserva = decodificar_id_reserva(reserva) passagem, inteira = dados_reserva[-1] monitoradas.arrecadado[linha] = monitoradas.arrecadado.get( linha, 0) + it.centavos(inteira, passagem) distribuicao_passageiros[passagem] += 1 linha_dia_reservas[linha] = linha_dia_reservas.get(linha, [0] * 7) linha_dia_reservas[linha][decodificar_id_onibus(onibus).isoweekday() % 7] += 1 if total_passageiros == 0: percentual_inteira = percentual_meia = percentual_gratuita = 0 else: (percentual_gratuita, percentual_meia, percentual_inteira) = [ 100 * distribuicao_passageiros[i] / total_passageiros for i in range(3) ] label_inteira['text'] = 'Inteira: %s%%' % fm.separar_milhares_decimal( percentual_inteira) label_meia['text'] = 'Meia: %s%%' % fm.separar_milhares_decimal( percentual_meia) label_gratuita['text'] = 'Gratuita: %s%%' % fm.separar_milhares_decimal( percentual_gratuita) total_arrecadado = 0 def onibus_por_dia(linha): data_cadastro = estado.linhas_entradas[linha].horario dia_da_semana = data_cadastro.isoweekday() intervalo = (data_atual + dt.timedelta(cte.MAXIMO_NUMERO_DE_DIAS_ATE_RESERVA) - data_cadastro).days onibus_diarios = [0] * 7 for i in range(7): if i in range((intervalo + 1) % 7): num_onibus = (intervalo + 1) // 7 + 1 else: num_onibus = (intervalo + 1) // 7 onibus_diarios[(dia_da_semana + i) % 7] = num_onibus return onibus_diarios.copy() vagas_ofertadas_totais = 0 for linha in estado.linhas_entradas: destino, horario = estado.linhas_entradas[linha][:2] nome_linha = '%s-%s' % (destino.upper(), fm.form_tempo(horario)) monitoradas.arrecadado[linha] = monitoradas.arrecadado.get(linha, 0) quantia = monitoradas.arrecadado[linha] / 100 total_arrecadado += quantia treeview_arrecadado.insert( '', 0, linha, values=(nome_linha, fm.separar_milhares_decimal(quantia))) onibus_diarios = onibus_por_dia(linha) capacidade_linha = estado.linhas_entradas[linha].fileiras * 2 vagas_ofertadas_totais += sum(onibus_diarios) * capacidade_linha # print(f'Ônibus diários: {onibus_diarios}\nCapacidade linha: {capacidade_linha}') monitoradas.ocupacao_media_semanal[linha] = [ linha_dia_reservas.get(linha, {d: 0})[d] / (onibus_diarios[d] * capacidade_linha) if onibus_diarios[d] != 0 else 0 for d in range(7) ] treeview_ocupacao.insert( '', 0, linha, values=(nome_linha, *map( lambda x: '%s%%' % fm.separar_milhares_decimal(100 * x), monitoradas.ocupacao_media_semanal[linha]))) painel_ocupacao['text'] = form_ocupacao( 100 * len(estado.reservas) / vagas_ofertadas_totais if vagas_ofertadas_totais != 0 else 0) painel_arrecadado['text'] = form_arrecadado(total_arrecadado)