#!/usr/bin/env python3 import getpass import uuid from calendar import monthrange from datetime import datetime, timedelta from pathlib import Path from time import sleep from .cfdi_cert import SATCertificate from .sat_web import SATWebService from settings import log def today(): return datetime.today() def validate_date(year, month, day): try: datetime(year, month, day, 0, 0, 0) result = True except ValueError: result = False return result def is_dir(path): return Path(path).is_dir() def join(*paths): return Path(paths[0]).joinpath(*paths[1:]) def _validate_fiel_args(args, need_key=False): fiel_path = args.fiel_dir fiel_name = args.fiel_nombre if not fiel_path: msg = 'El directorio con la FIEL es requerido' log.error(msg) return False, {} if not is_dir(fiel_path): msg = f'La ruta no existe o no es un directorio. \nRuta: {fiel_path}' log.error(msg) return False, {} path_cer = join(fiel_path, f'{fiel_name}.cer') path_key = join(fiel_path, f'{fiel_name}.key') path_enc = join(fiel_path, f'{fiel_name}.enc') path_pem = join(fiel_path, f'{fiel_name}.pem') if not path_cer.is_file(): msg = f'No se encontró el archivo CER. \nRuta: {path_cer}' log.error(msg) return False, {} if need_key and not path_key.is_file(): msg = f'No se encontró el archivo KEY. \nRuta: {path_key}' log.error(msg) return False, {} data = { 'path_cer': path_cer, 'path_key': path_key, 'path_enc': path_enc, 'path_pem': path_pem, } return True, data def fiel_validar(args): result, data = _validate_fiel_args(args, True) if not result: return try: password = getpass.getpass('Introduce la contraseña del archivo KEY: ') except KeyboardInterrupt: msg = 'Proceso cancelado' log.info(msg) return if not password: msg = 'La contraseña es requerida para validar la FIEL' log.error(msg) return cer = data['path_cer'].read_bytes() key = data['path_key'].read_bytes() cert = SATCertificate(cer, key, password=password) if cert.error: msg = f'{cert.error}\n\nNo podrás conectarte el SAT.' log.error(msg) return if not cert.is_fiel: msg = 'El certificado no es FIEL' log.error(msg) return data['path_enc'].write_bytes(cert.key_enc) msg = 'Los datos del certificado son:' log.info(msg) log.info(f'\n{cert}') msg = 'Ya puedes descargar del SAT' log.info(msg) return def base_datos(): db.create_tables() return def _get_cert(data): key = b'' pem = b'' cer = data['path_cer'].read_bytes() if data['path_enc'].is_file(): key = data['path_enc'].read_bytes() elif data['path_pem'].is_file(): pem = data['path_pem'].read_bytes() cert = SATCertificate(cer, key, pem) return cert def _to_date(str_date, end=False): error = '' dt = None try: parts = str_date.split('-') if end: dt = datetime(int(parts[0]), int(parts[1]), int(parts[2]), 23, 59, 59) else: dt = datetime(int(parts[0]), int(parts[1]), int(parts[2]), 0, 0, 0) except Exception as e: error = 'Fecha inválida' return error, dt def _validate_requests_args(args): result, data = _validate_fiel_args(args) if not result: return False, {} if not data['path_enc'].is_file() and not data['path_pem'].is_file(): msg = f"No se encontró la FIEL [enc|pem].\nRuta: {data['path_enc']}" log.error(msg) return False, {} cert = _get_cert(data) if not cert.is_valid_time: msg = 'La FIEL no es vigente' log.error(msg) return False, {} data['cert'] = cert data['type'] = args.type data['year'] = args.year data['month'] = args.month data['day'] = args.day data['metadata'] = args.metadata now = today() if args.last_days: date_start = now.replace(hour=0, minute=0, second=0, microsecond=0) \ - timedelta(days=args.last_days) date_end = now.replace(hour=23, minute=59, second=59, microsecond=0) data['date_start'] = date_start data['date_end'] = date_end return True, data if args.date_start and args.date_end: error, date_start = _to_date(args.date_start) if error: log.error(error) return False, {} error, date_end = _to_date(args.date_end, True) if error: log.error(error) return False, {} data['date_start'] = date_start data['date_end'] = date_end return True, data if data['day']: if not validate_date(data['year'], data['month'], data['day']): msg = 'Fecha inválida' log.error(msg) return False, {} month1 = month2 = data['month'] if month1 == 0: month1 = 1 month2 = 12 if data['day']: day1 = day2 = data['day'] else: day1 = 1 day2 = monthrange(data['year'], month2)[1] data['date_start'] = datetime(data['year'], month1, day1) data['date_end'] = datetime(data['year'], month2, day2, 23, 59, 59) return True, data def _validate_verificar_args(args): result, data = _validate_fiel_args(args) if not result: return False, {} if not data['path_enc'].is_file() and not data['path_pem'].is_file(): msg = f"No se encontró la FIEL [enc|pem].\nRuta: {data['path_enc']}" log.error(msg) return False, {} cert = _get_cert(data) if not cert.is_valid_time: msg = 'La FIEL no es vigente' log.error(msg) return False, {} if not args.id_request: msg = 'El ID de solicitud de descarga es requerido' log.error(msg) return False, {} data['cert'] = cert data['id'] = args.id_request return True, data def _validate_download_args(args): result, data = _validate_fiel_args(args) if not result: return False, {} if not data['path_enc'].is_file() and not data['path_pem'].is_file(): msg = f"No se encontró la FIEL [enc|pem].\nRuta: {data['path_enc']}" log.error(msg) return False, {} cert = _get_cert(data) if not cert.is_valid_time: msg = 'La FIEL no es vigente' log.error(msg) return False, {} if not args.id_request and not args.id_file: msg = 'El ID de solicitud o ID de archivo de descarga es requerido' log.error(msg) return False, {} if not args.path_download: msg = 'La ruta de descarga es requerida [-dd]' log.error(msg) return False, {} data['cert'] = cert data['id'] = args.id_request data['id_file'] = args.id_file data['path'] = args.path_download return True, data def _request_download(sat, data, key): data['rfc'] = key result = sat.request_download(data) # ~ Usando un simple print, permite capturarlo desde cualquier lenguaje print(result) return result def solicitar_descarga(args): result, data = _validate_requests_args(args) if not result: return sat = SATWebService(data['cert']) if not sat.is_authenticate: log.error(sat.error) return if data['type'] == 'e': _request_download(sat, data, 'RfcEmisor') elif data['type'] == 'r': _request_download(sat, data, 'RfcReceptor') else: _request_download(sat, data, 'RfcEmisor') _request_download(sat, data, 'RfcReceptor') return def verificar_descarga(args): result, data = _validate_verificar_args(args) if not result: return sat = SATWebService(data['cert']) if not sat.is_authenticate: log.error(sat.error) return result = sat.verify(data) # ~ Usando un simple print, permite capturarlo desde cualquier lenguaje print(result) return def descargar_archivos(args): result, data = _validate_download_args(args) if not result: return sat = SATWebService(data['cert']) if not sat.is_authenticate: log.error(sat.error) return if args.id_file: files = (args.id_file,) else: result = sat.verify(data) files = result['files'] if result['EstadoSolicitud'] in ('1', '2'): msg = 'Solicitud aún no aceptada...' log.error(msg) return for f in files: data['id_file'] = f result, file_data = sat.download(data) if file_data is None: log.debug(result) else: path_zip = join(data['path'], f'{f}.zip') msg = f'Guardando: {path_zip}' log.info(msg) with open(path_zip, 'wb') as f: f.write(file_data) msg = f's\tArchivo guardado correctamente' log.info(msg) return def _validate_args(args): result, data = _validate_requests_args(args) if not result: return False, {} if not args.path_download: msg = 'La ruta de descarga es requerida [-dd]' log.error(msg) return False, {} data['path'] = args.path_download return True, data def _download(data, key): OK = '5000' sat = SATWebService(data['cert']) result = _request_download(sat, data, key) if result['CodEstatus'] != OK: log.error(result) return data['id'] = result['IdSolicitud'] msg = f"Descarga aceptada con el ID: {data['id']}\n\nEsperando un minuto para verificar la descarga..." log.info(msg) for i in range(60, 0, -1): print(f'\r{i}', end=' ') sleep(1) while True: sat = SATWebService(data['cert']) result = sat.verify(data) if result['EstadoSolicitud'] in ('1', '2'): msg = 'Esperando un minuto más para volver a verificar...' log.info(msg) for i in range(60, 0, -1): print(f'\r{i}', end=' ') sleep(1) continue if result['EstadoSolicitud'] == '3': for f in result['files']: data['id_file'] = f result, file_data = sat.download(data) if file_data is None: log.debug(result) else: path_zip = join(data['path'], f'{f}.zip') msg = f'Guardando: {path_zip}' log.info(msg) with open(path_zip, 'wb') as f: f.write(file_data) msg = f'\tArchivo guardado correctamente' log.info(msg) break log.error(result) break return def descargar(args): result, data = _validate_args(args) if not result: return sat = SATWebService(data['cert']) if not sat.is_authenticate: log.error(sat.error) return if data['type'] == 'e': _download(data, 'RfcEmisor') elif data['type'] == 'r': _download(data, 'RfcReceptor') else: _download(data, 'RfcEmisor') _download(data, 'RfcReceptor') return