# -*- coding: utf-8 -*- from odoo import models, fields, api from odoo.exceptions import ValidationError from lxml import etree import requests import re import json import base64 import operator from functools import reduce from .http_helper import URL_NUMBERING_XML, AVANCYS_NIT, HEADERS ID_PARAM_HELP = """Provisto por el Proveedor Tecnológico""" DOCUMENT_TYPE_HELP = """ Para Notas Crédito y Débito los datos de resolución no aplican""" DOCUMENT_TYPE_SELECTION = [ ('01', 'Factura Electrónica'), ('91', 'Nota Crédito'), ('92', 'Nota Débito'), ('03', 'Contingencia') ] # Warnings NOT_ENOUGH_DATA_WARN = """ No hay suficientes datos para obtener la información, Por favor ingrese al menos los siguientes: - Número de Resolución - Prefijo - Numeración Desde - Numeración Hasta """ MULTI_RESOLUTION_WARN = """ Se encontro mas de una resolución con los mismos parametros. """ NO_RESOLUTION_WARN = """ No se encontro ninguna resolución que coincida con los datos.""" RESOLUTION_SAMPLE = """ Ejemplo: Resolución DIAN N° 00000000 desde PREFIJO 1 hasta PREFIJO 1000 """ class ElectronicInvoiceResolution(models.Model): _name = 'electronic.invoice.resolution' _description = 'Resolucion de Facturacion Electronica' name = fields.Char(string='Nombre', required=True) number = fields.Char(string='Número de Resolución') prefix = fields.Char('Prefijo', required=True) from_number = fields.Integer(string='Numeración Desde', copy=False) to_number = fields.Integer(string='Numeración Hasta', copy=False) valid_date_from = fields.Date(string='Fecha Inicio Resolución', copy=False) valid_date_to = fields.Date(string='Fecha Fin Resolución', copy=False) id_param = fields.Integer( string='ID Resolución', help=ID_PARAM_HELP, required=True, default=0) technical_key = fields.Char(string='Clave Técnica', copy=False) document_type = fields.Selection( string='Tipo de Documento', selection=DOCUMENT_TYPE_SELECTION, required=True, copy=False, help=DOCUMENT_TYPE_HELP) description = fields.Text( string='Texto de Resolución', help=RESOLUTION_SAMPLE) company_id = fields.Many2one( 'res.company', 'Company', default=lambda self: self.env.company, index=True, required=True) def get_resolution_xml(self): self.ensure_one() company = self.company_id body_dict = { "datos_conexion": { "token": company.software_token, "documento": company.partner_id.ref_num, "documentoT": (AVANCYS_NIT if company.ei_software_operation == 'provider' else company.partner_id.ref_num) }, "Datos_software": { "codigo": company.software_code_dian, "ambiente": "1" } } data = 'json_data=' + \ base64.b64encode(json.dumps(body_dict).encode( 'utf-8')).decode('utf-8') response = requests.post(URL_NUMBERING_XML, data=data, headers=HEADERS, verify=False) return str(response.content, 'utf-8') def parse_resolution(self, resolution): return { 'ResolutionNumber': re.search( r'(.*?)', resolution).group(1), 'ResolutionDate': re.search( r'(.*?)', resolution).group(1), 'Prefix': re.search(r'(.*?)', resolution).group(1), 'FromNumber': re.search(r'(.*?)', resolution).group(1), 'ToNumber': re.search(r'(.*?)', resolution).group(1), 'ValidDateFrom': re.search( r'(.*?)', resolution).group(1), 'ValidDateTo': re.search(r'(.*?)', resolution).group(1), 'TechnicalKey': re.search(r'(.*?)', resolution).group(1) } def resolucion_match(self, resolucion_match): return reduce(operator.and_, [ self.number == resolucion_match.get('ResolutionNumber', False), self.prefix == resolucion_match.get('Prefix', False), str(self.from_number) == resolucion_match.get('FromNumber', False), str(self.to_number) == resolucion_match.get('ToNumber', False), ]) def get_technical_key(self): if not self.prefix or not self.number or not self.from_number or not self.to_number: raise ValidationError(NOT_ENOUGH_DATA_WARN) self.technical_key = '' resolution_xml = self.get_resolution_xml() resolutions = re.findall( r'([\s\S.]*?)', resolution_xml) if not resolutions: return resolutions_list = list(map(self.parse_resolution, resolutions)) matching_resolution = list( filter(self.resolucion_match, resolutions_list)) if not matching_resolution: raise ValidationError(NO_RESOLUTION_WARN) if len(matching_resolution) > 1: raise ValidationError(MULTI_RESOLUTION_WARN) self.technical_key = matching_resolution[0]['TechnicalKey']