feat: Implement GUI for SPED Automático with configuration management and theme support
- Added a new GUI application using the Iced library for user interaction. - Implemented configuration loading from an INI file, including fields for model file, spreadsheet path, and reporting month. - Introduced theme management with options for auto, light, and dark modes based on system preferences. - Added functionality to browse for model and spreadsheet files. - Implemented validation and formatting for the reporting month input. - Integrated asynchronous processing of the SPED generation logic, with user feedback during execution. - Created a logging mechanism to track application events and errors. - Added tests for core functionalities including file reading/writing and data processing.
This commit is contained in:
+270
@@ -0,0 +1,270 @@
|
||||
import configparser
|
||||
import openpyxl
|
||||
import chardet
|
||||
|
||||
DEFAULT_ENCODING = 'ISO-8859-1'
|
||||
CONFIG_FILE = 'config_motoradio.ini'
|
||||
|
||||
|
||||
def detect_encoding(file_path):
|
||||
with open(file_path, 'rb') as file:
|
||||
raw_data = file.read()
|
||||
result = chardet.detect(raw_data)
|
||||
return result['encoding']
|
||||
|
||||
def read_file(file_path, encoding=DEFAULT_ENCODING):
|
||||
"""
|
||||
Lê o conteúdo completo de um arquivo de texto.
|
||||
|
||||
Args:
|
||||
file_path (str): Caminho do arquivo.
|
||||
encoding (str): Codificação do arquivo.
|
||||
|
||||
Returns:
|
||||
str: Conteúdo do arquivo como string.
|
||||
"""
|
||||
with open(file_path, 'r', encoding=encoding) as f:
|
||||
return f.read()
|
||||
|
||||
def read_lines(file_path, encoding=DEFAULT_ENCODING):
|
||||
with open(file_path, 'r', encoding=encoding) as f:
|
||||
return f.readlines()
|
||||
|
||||
def write_file(file_path, content, encoding=DEFAULT_ENCODING):
|
||||
with open(file_path, 'w', encoding=encoding) as f:
|
||||
f.write(content)
|
||||
|
||||
def write_lines(file_path, lines, encoding=DEFAULT_ENCODING):
|
||||
with open(file_path, 'w', encoding=encoding) as f:
|
||||
f.writelines(lines)
|
||||
|
||||
class Config:
|
||||
def __init__(self, config_file=CONFIG_FILE):
|
||||
self.config = configparser.ConfigParser()
|
||||
self.config.read(config_file)
|
||||
self.codigo_st = self.config['DEFAULT']['Codigo_ST']
|
||||
self.codigo_receita = self.config['DEFAULT']['Codigo_Receita']
|
||||
self.mes_apuracao = self.config['DEFAULT']['Mes_Apuracao']
|
||||
self.modelo_arquivo = self.config['DEFAULT']['ModeloArquivo']
|
||||
self.caminho_planilha = self.config['DEFAULT']['CaminhoPlanilha']
|
||||
|
||||
def contar_linhas_com_e(linhas):
|
||||
linha_completa = ''.join(linhas) # Junta todas as linhas em uma única string
|
||||
linhas_separadas = linha_completa.split('\n') # Separa novamente por linhas
|
||||
return sum(1 for linha in linhas_separadas if linha.startswith('|E'))
|
||||
|
||||
def substituir_valor(arquivo, antigo, novo):
|
||||
with open(arquivo, 'r', encoding='ISO-8859-1') as file:
|
||||
linhas = file.readlines()
|
||||
|
||||
linhas = [linha.replace(antigo, novo) for linha in linhas]
|
||||
|
||||
with open(arquivo, 'w', encoding='ISO-8859-1') as file:
|
||||
file.writelines(linhas)
|
||||
|
||||
def find_e200_states(content):
|
||||
lines = content.split('\n')
|
||||
states = set()
|
||||
for line in lines:
|
||||
if line.startswith('|E200|'):
|
||||
parts = line.split('|')
|
||||
if len(parts) > 2:
|
||||
state_code = parts[2]
|
||||
states.add(state_code)
|
||||
return list(states)
|
||||
|
||||
def count_9900_lines(content):
|
||||
lines = content.split('\n')
|
||||
count = 0
|
||||
for line in lines:
|
||||
if '|9900|' in line and '|9900|9900|' not in line:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def count_E250_lines(content):
|
||||
lines = content.split('\n')
|
||||
count = 0
|
||||
for line in lines:
|
||||
if '|E250|' in line:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def count_lines(file_path, encoding='ISO-8859-1'):
|
||||
with open(file_path, 'r', encoding=encoding) as file:
|
||||
return sum(1 for _ in file)
|
||||
|
||||
def substituir_ultimas_linhas(arquivo, novas_linhas):
|
||||
with open(arquivo, 'r', encoding='ISO-8859-1') as file:
|
||||
linhas = file.readlines()
|
||||
|
||||
# Remover as três últimas linhas
|
||||
linhas = linhas[:-2]
|
||||
|
||||
# Remover todas as linhas que contenham "|9900|9900|"
|
||||
linhas = [linha for linha in linhas if '|9900|9900|' not in linha]
|
||||
|
||||
# Adicionar as novas linhas
|
||||
linhas.extend(novas_linhas)
|
||||
|
||||
# Escrever de volta no arquivo
|
||||
with open(arquivo, 'w', encoding='ISO-8859-1') as file:
|
||||
file.writelines(linhas)
|
||||
|
||||
def substituir_linha_e990(arquivo, conteudo):
|
||||
# Abrir o arquivo para leitura
|
||||
with open(arquivo, 'r') as file:
|
||||
lines = file.readlines()
|
||||
|
||||
# Abrir o arquivo para escrita
|
||||
with open(arquivo, 'w') as file:
|
||||
count = 1
|
||||
for i, line in enumerate(lines):
|
||||
if '|E990|' in line:
|
||||
if count == 1:
|
||||
file.write(f'|E990|{contar_linhas_com_e(conteudo)}|' + '\n')
|
||||
count += 1
|
||||
# Pular a próxima linha
|
||||
continue
|
||||
file.write(line)
|
||||
|
||||
def inserir_linha_apos_9900_e210(arquivo, conteudo):
|
||||
with open(arquivo, 'r', encoding='ISO-8859-1') as file:
|
||||
linhas = file.readlines()
|
||||
|
||||
# Contar e remover a linha que começa com '|9900|E250|', se existir
|
||||
linhas_removidas = sum(1 for linha in linhas if linha.startswith('|9900|E250|'))
|
||||
linhas = [linha for linha in linhas if not linha.startswith('|9900|E250|')]
|
||||
|
||||
cont_E250 = count_E250_lines(conteudo) - linhas_removidas
|
||||
|
||||
nova_linha = f'|9900|E250|{cont_E250}|'
|
||||
for i, linha in enumerate(linhas):
|
||||
if '|9900|E210|' in linha:
|
||||
linhas.insert(i + 1, nova_linha + '\n')
|
||||
break
|
||||
|
||||
with open(arquivo, 'w', encoding='ISO-8859-1') as file:
|
||||
file.writelines(linhas)
|
||||
|
||||
def find_e2xx_state_lines(content, state_code):
|
||||
lines = content.split('\n')
|
||||
return [(index, line) for index, line in enumerate(lines) if line.startswith('|E2') and f'|{state_code}' in line]
|
||||
|
||||
def process_sheet(sheet, codigo_receita, mes_apuracao):
|
||||
new_values = []
|
||||
for linha in sheet.iter_rows(min_row=2):
|
||||
valor = linha[39].value
|
||||
valor = str(valor).replace(".", ",")
|
||||
vencimento = linha[1].value
|
||||
if vencimento is None:
|
||||
print('Vencimento é None')
|
||||
continue
|
||||
vencimento = vencimento.strftime("%d%m%Y")
|
||||
if valor == '0':
|
||||
print('Valor zerado')
|
||||
continue
|
||||
new_values.append(f'|E250|999|{valor}|{vencimento}|{codigo_receita}|||||{mes_apuracao.replace("/", "")}|')
|
||||
return new_values
|
||||
|
||||
def process_file(conteudo, config, estado):
|
||||
e2xx_state_lines = find_e2xx_state_lines(conteudo, estado)
|
||||
lines = conteudo.split('\n')
|
||||
|
||||
workbook = openpyxl.load_workbook(config.caminho_planilha)
|
||||
sheet_estado = workbook[estado]
|
||||
|
||||
new_values = process_sheet(sheet_estado, config.codigo_receita, config.mes_apuracao)
|
||||
|
||||
for index, line in e2xx_state_lines:
|
||||
for new_line in new_values:
|
||||
lines.insert(index + 2, new_line)
|
||||
|
||||
write_file(config.modelo_arquivo, '\n'.join(lines))
|
||||
|
||||
def processar_estados(estados, config):
|
||||
conteudo = read_file(config.modelo_arquivo)
|
||||
conteudo = "\n".join([linha for linha in conteudo.split("\n") if "|E250|999|" not in linha])
|
||||
write_file(config.modelo_arquivo, conteudo)
|
||||
for estado in estados:
|
||||
if estado != 'SP':
|
||||
print(f'Processando estado {estado}')
|
||||
conteudo = read_file(config.modelo_arquivo)
|
||||
process_file(conteudo, config, estado)
|
||||
|
||||
def main():
|
||||
import os
|
||||
config = Config()
|
||||
conteudo = read_file(config.modelo_arquivo)
|
||||
estados = find_e200_states(conteudo)
|
||||
processar_estados(estados, config)
|
||||
|
||||
conteudo = read_file(config.modelo_arquivo)
|
||||
substituir_linha_e990(config.modelo_arquivo, conteudo)
|
||||
inserir_linha_apos_9900_e210(config.modelo_arquivo, conteudo)
|
||||
substituir_valor(config.modelo_arquivo, '|04601|', '|046-2|')
|
||||
|
||||
# Remova as chamadas antigas de substituir_ultimas_linhas e use apenas:
|
||||
atualizar_registros_finais(config.modelo_arquivo)
|
||||
|
||||
# Salvar arquivo processado
|
||||
nome_original = config.modelo_arquivo
|
||||
nome_base, ext = os.path.splitext(nome_original)
|
||||
nome_processado = f"{nome_base}_processado{ext}"
|
||||
try:
|
||||
conteudo_final = read_file(nome_original)
|
||||
write_file(nome_processado, conteudo_final)
|
||||
print(f"Arquivo processado salvo como: {nome_processado}")
|
||||
except Exception as e:
|
||||
print(f"Erro ao salvar arquivo processado: {e}")
|
||||
|
||||
def atualizar_registros_finais(arquivo):
|
||||
"""
|
||||
Atualiza os registros finais do arquivo SPED, removendo duplicidades e
|
||||
recalculando as quantidades dos registros 9900, 9990 e 9999.
|
||||
|
||||
Args:
|
||||
arquivo (str): Caminho do arquivo a ser processado.
|
||||
|
||||
Efeito:
|
||||
O arquivo é sobrescrito com os registros finais corrigidos.
|
||||
"""
|
||||
with open(arquivo, 'r', encoding='ISO-8859-1') as file:
|
||||
linhas = file.readlines()
|
||||
|
||||
# Remove registros finais antigos para evitar duplicidade
|
||||
linhas = [linha for linha in linhas if not (
|
||||
linha.startswith('|9900|9900|') or
|
||||
linha.startswith('|9900|E990|') or
|
||||
linha.startswith('|9990|') or
|
||||
linha.startswith('|9999|')
|
||||
)]
|
||||
|
||||
# Contagem correta dos registros 9900
|
||||
total_9900 = sum(1 for linha in linhas if linha.startswith('|9900|') and not linha.startswith('|9900|9900|'))
|
||||
|
||||
# Bloco 9: começa em |9001| e termina em |9990|
|
||||
bloco9_indices = [i for i, linha in enumerate(linhas) if linha.startswith('|9001|')]
|
||||
if bloco9_indices:
|
||||
inicio_bloco9 = bloco9_indices[0]
|
||||
# Procura o fim do bloco 9 (último |9990|, se existir)
|
||||
fim_bloco9 = len(linhas)
|
||||
bloco9_linhas = linhas[inicio_bloco9:fim_bloco9]
|
||||
total_bloco9 = len(bloco9_linhas) + 4 # +4 para os registros finais que serão adicionados
|
||||
else:
|
||||
total_bloco9 = 4 # só os finais
|
||||
|
||||
total_linhas_arquivo = len(linhas) + 4 # +4 para os registros finais
|
||||
|
||||
# Adiciona os registros finais corretos
|
||||
linhas.extend([
|
||||
f'|9900|9900|{total_9900 + 2}|\n', # +2 para E990 e 9900
|
||||
'|9900|E990|1|\n',
|
||||
f'|9990|{total_bloco9}|\n',
|
||||
f'|9999|{total_linhas_arquivo}|\n'
|
||||
])
|
||||
|
||||
with open(arquivo, 'w', encoding='ISO-8859-1') as file:
|
||||
file.writelines(linhas)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user