Files
FelipeCN 8f81024a6b 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.
2025-11-17 13:42:08 -03:00

270 lines
9.2 KiB
Python

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()