Last active
April 29, 2024 14:46
-
-
Save alcidesrivera/066257039fe024851f9d to your computer and use it in GitHub Desktop.
Electronic Document SRI Ecuador
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
############################################################################## | |
# | |
# E-Invoice Module - Ecuador | |
# Copyright (C) 2014 VIRTUALSAMI CIA. LTDA. All Rights Reserved | |
# [email protected] | |
# $Id$ | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
# | |
############################################################################## | |
import time | |
import logging | |
import base64 | |
from lxml import etree | |
from xml.dom.minidom import parse, parseString | |
from osv import osv, fields | |
from tools import config | |
from tools.translate import _ | |
from tools import ustr | |
import decimal_precision as dp | |
import netsvc | |
from .xades.sri import Service as SRIService, InvoiceXML | |
from .xades.xades import Xades | |
try: | |
from suds.client import Client | |
from suds.transport import TransportError | |
except ImportError: | |
raise ImportError('Instalar Libreria suds') | |
tipoIdentificacion = { | |
'ruc' : '04', | |
'cedula' : '05', | |
'pasaporte' : '06', | |
'venta_consumidor_final' : '07', | |
'identificacion_exterior' : '08', | |
'placa' : '09', | |
} | |
codigoImpuesto = { | |
'vat': '2', | |
'vat0': '2', | |
'ice': '3', | |
'other': '5' | |
} | |
tarifaImpuesto = { | |
'vat0': '0', | |
'vat': '2', | |
'novat': '6', | |
'other': '7', | |
} | |
class AccountInvoice(osv.osv): | |
_inherit = 'account.invoice' | |
__logger = logging.getLogger(_inherit) | |
_columns = { | |
'clave_acceso': fields.char('Clave de Acceso', size=49, readonly=True, store=True), | |
'numero_autorizacion': fields.char('Número de Autorización', size=37, readonly=True, store=True), | |
'fecha_autorizacion': fields.datetime('Fecha y Hora de Autorización', readonly=True, store=True), | |
'autorizado_sri': fields.boolean('¿Autorizado SRI?', readonly=True, store=True), | |
'security_code': fields.char('Código de Seguridad', size=8), | |
'emission_code': fields.char('Tipo de Emisión', size=1), | |
} | |
def get_code(self, cr, uid, invoice): | |
""" | |
TODO: revisar la generacion | |
del codigo de 8 digitos | |
""" | |
return self.pool.get('ir.sequence').get_id(cr, uid, 51) | |
def _get_tax_element(self, invoice, access_key, emission_code): | |
""" | |
""" | |
company = invoice.company_id | |
auth = invoice.journal_id.auth_id | |
infoTributaria = etree.Element('infoTributaria') | |
etree.SubElement(infoTributaria, 'ambiente').text = SRIService.get_env_test() | |
etree.SubElement(infoTributaria, 'tipoEmision').text = emission_code | |
etree.SubElement(infoTributaria, 'razonSocial').text = company.name | |
etree.SubElement(infoTributaria, 'nombreComercial').text = company.name | |
etree.SubElement(infoTributaria, 'ruc').text = company.partner_id.ced_ruc | |
etree.SubElement(infoTributaria, 'claveAcceso').text = access_key | |
etree.SubElement(infoTributaria, 'codDoc').text = auth.type_id.code | |
etree.SubElement(infoTributaria, 'estab').text = auth.serie_entidad | |
etree.SubElement(infoTributaria, 'ptoEmi').text = auth.serie_emision | |
etree.SubElement(infoTributaria, 'secuencial').text = invoice.supplier_invoice_number.replace('-','')[6:15] | |
etree.SubElement(infoTributaria, 'dirMatriz').text = company.street | |
return infoTributaria | |
def _get_invoice_element(self, invoice): | |
""" | |
""" | |
company = invoice.company_id | |
partner = invoice.partner_id | |
infoFactura = etree.Element('infoFactura') | |
etree.SubElement(infoFactura, 'fechaEmision').text = time.strftime('%d/%m/%Y',time.strptime(invoice.date_invoice, '%Y-%m-%d')) | |
etree.SubElement(infoFactura, 'dirEstablecimiento').text = company.street2 | |
etree.SubElement(infoFactura, 'contribuyenteEspecial').text = company.company_registry | |
etree.SubElement(infoFactura, 'obligadoContabilidad').text = 'SI' | |
etree.SubElement(infoFactura, 'tipoIdentificacionComprador').text = tipoIdentificacion[partner.type_ced_ruc] | |
etree.SubElement(infoFactura, 'razonSocialComprador').text = partner.name | |
etree.SubElement(infoFactura, 'identificacionComprador').text = partner.ced_ruc | |
etree.SubElement(infoFactura, 'totalSinImpuestos').text = '%.2f' % (invoice.amount_untaxed) | |
etree.SubElement(infoFactura, 'totalDescuento').text = '%.2f' % (invoice.discount_total) | |
#totalConImpuestos | |
totalConImpuestos = etree.Element('totalConImpuestos') | |
for tax in invoice.tax_line: | |
if tax.tax_group in ['vat', 'vat0', 'ice', 'other']: | |
totalImpuesto = etree.Element('totalImpuesto') | |
etree.SubElement(totalImpuesto, 'codigo').text = codigoImpuesto[tax.tax_group] | |
etree.SubElement(totalImpuesto, 'codigoPorcentaje').text = tarifaImpuesto[tax.tax_group] | |
etree.SubElement(totalImpuesto, 'baseImponible').text = '{:.2f}'.format(tax.base_amount) | |
etree.SubElement(totalImpuesto, 'valor').text = '{:.2f}'.format(tax.tax_amount) | |
totalConImpuestos.append(totalImpuesto) | |
infoFactura.append(totalConImpuestos) | |
etree.SubElement(infoFactura, 'propina').text = '0.00' | |
etree.SubElement(infoFactura, 'importeTotal').text = '{:.2f}'.format(invoice.amount_pay) | |
etree.SubElement(infoFactura, 'moneda').text = 'DOLAR' | |
return infoFactura | |
def _get_refund_element(self, refund, invoice): | |
""" | |
""" | |
company = refund.company_id | |
partner = refund.partner_id | |
infoNotaCredito = etree.Element('infoNotaCredito') | |
etree.SubElement(infoNotaCredito, 'fechaEmision').text = time.strftime('%d/%m/%Y',time.strptime(refund.date_invoice, '%Y-%m-%d')) | |
etree.SubElement(infoNotaCredito, 'dirEstablecimiento').text = company.street2 | |
etree.SubElement(infoNotaCredito, 'tipoIdentificacionComprador').text = tipoIdentificacion[partner.type_ced_ruc] | |
etree.SubElement(infoNotaCredito, 'razonSocialComprador').text = partner.name | |
etree.SubElement(infoNotaCredito, 'identificacionComprador').text = partner.ced_ruc | |
etree.SubElement(infoNotaCredito, 'contribuyenteEspecial').text = company.company_registry | |
etree.SubElement(infoNotaCredito, 'obligadoContabilidad').text = 'SI' | |
etree.SubElement(infoNotaCredito, 'codDocModificado').text = '01' | |
etree.SubElement(infoNotaCredito, 'numDocModificado').text = invoice[0].supplier_invoice_number | |
etree.SubElement(infoNotaCredito, 'fechaEmisionDocSustento').text = time.strftime('%d/%m/%Y',time.strptime(invoice[0].date_invoice, '%Y-%m-%d')) | |
etree.SubElement(infoNotaCredito, 'totalSinImpuestos').text = '%.2f' % (refund.amount_untaxed) | |
etree.SubElement(infoNotaCredito, 'valorModificacion').text = '%.2f' % (refund.amount_untaxed) | |
etree.SubElement(infoNotaCredito, 'moneda').text = 'DOLAR' | |
#totalConImpuestos | |
totalConImpuestos = etree.Element('totalConImpuestos') | |
for tax in refund.tax_line: | |
if tax.tax_group in ['vat', 'vat0', 'ice', 'other']: | |
totalImpuesto = etree.Element('totalImpuesto') | |
etree.SubElement(totalImpuesto, 'codigo').text = codigoImpuesto[tax.tax_group] | |
etree.SubElement(totalImpuesto, 'codigoPorcentaje').text = tarifaImpuesto[tax.tax_group] | |
etree.SubElement(totalImpuesto, 'baseImponible').text = '{:.2f}'.format(tax.base_amount) | |
etree.SubElement(totalImpuesto, 'valor').text = '{:.2f}'.format(tax.tax_amount) | |
totalConImpuestos.append(totalImpuesto) | |
infoNotaCredito.append(totalConImpuestos) | |
etree.SubElement(infoNotaCredito, 'motivo').text = refund.origin | |
return infoNotaCredito | |
def _get_detail_element(self, invoice): | |
""" | |
""" | |
detalles = etree.Element('detalles') | |
for line in invoice.invoice_line: | |
detalle = etree.Element('detalle') | |
etree.SubElement(detalle, 'codigoPrincipal').text = line.product_id.default_code | |
if line.product_id.manufacturer_pref: | |
etree.SubElement(detalle, 'codigoAuxiliar').text = line.product_id.manufacturer_pref | |
etree.SubElement(detalle, 'descripcion').text = line.product_id.name | |
etree.SubElement(detalle, 'cantidad').text = '%.6f' % (line.quantity) | |
etree.SubElement(detalle, 'precioUnitario').text = '%.6f' % (line.price_unit) | |
etree.SubElement(detalle, 'descuento').text = '%.2f' % (line.discount_value) | |
etree.SubElement(detalle, 'precioTotalSinImpuesto').text = '%.2f' % (line.price_subtotal) | |
impuestos = etree.Element('impuestos') | |
for tax_line in line.invoice_line_tax_id: | |
if tax_line.tax_group in ['vat', 'vat0', 'ice', 'other']: | |
impuesto = etree.Element('impuesto') | |
etree.SubElement(impuesto, 'codigo').text = codigoImpuesto[tax_line.tax_group] | |
etree.SubElement(impuesto, 'codigoPorcentaje').text = tarifaImpuesto[tax_line.tax_group] | |
etree.SubElement(impuesto, 'tarifa').text = '%.2f' % (tax_line.amount * 100) | |
etree.SubElement(impuesto, 'baseImponible').text = '%.2f' % (line.price_subtotal) | |
etree.SubElement(impuesto, 'valor').text = '%.2f' % (line.amount_tax) | |
impuestos.append(impuesto) | |
detalle.append(impuestos) | |
detalles.append(detalle) | |
return detalles | |
def _get_detail_element_refund(self, invoice): | |
""" | |
""" | |
detalles = etree.Element('detalles') | |
for line in invoice.invoice_line: | |
detalle = etree.Element('detalle') | |
etree.SubElement(detalle, 'codigoInterno').text = line.product_id.default_code | |
if line.product_id.manufacturer_pref: | |
etree.SubElement(detalle, 'codigoAdicional').text = line.product_id.manufacturer_pref | |
etree.SubElement(detalle, 'descripcion').text = line.product_id.name | |
etree.SubElement(detalle, 'cantidad').text = '%.6f' % (line.quantity) | |
etree.SubElement(detalle, 'precioUnitario').text = '%.6f' % (line.price_unit) | |
etree.SubElement(detalle, 'descuento').text = '%.2f' % (line.discount_value) | |
etree.SubElement(detalle, 'precioTotalSinImpuesto').text = '%.2f' % (line.price_subtotal) | |
impuestos = etree.Element('impuestos') | |
for tax_line in line.invoice_line_tax_id: | |
if tax_line.tax_group in ['vat', 'vat0', 'ice', 'other']: | |
impuesto = etree.Element('impuesto') | |
etree.SubElement(impuesto, 'codigo').text = codigoImpuesto[tax_line.tax_group] | |
etree.SubElement(impuesto, 'codigoPorcentaje').text = tarifaImpuesto[tax_line.tax_group] | |
etree.SubElement(impuesto, 'tarifa').text = '%.2f' % (tax_line.amount * 100) | |
etree.SubElement(impuesto, 'baseImponible').text = '%.2f' % (line.price_subtotal) | |
etree.SubElement(impuesto, 'valor').text = '%.2f' % (line.amount_tax) | |
impuestos.append(impuesto) | |
detalle.append(impuestos) | |
detalles.append(detalle) | |
return detalles | |
def _generate_xml_invoice(self, invoice, access_key, emission_code): | |
""" | |
""" | |
factura = etree.Element('factura') | |
factura.set("id", "comprobante") | |
factura.set("version", "1.1.0") | |
# generar infoTributaria | |
infoTributaria = self._get_tax_element(invoice, access_key, emission_code) | |
factura.append(infoTributaria) | |
# generar infoFactura | |
infoFactura = self._get_invoice_element(invoice) | |
factura.append(infoFactura) | |
#generar detalles | |
detalles = self._get_detail_element(invoice) | |
factura.append(detalles) | |
return factura | |
def _generate_xml_refund(self, refund, invoice, access_key, emission_code): | |
""" | |
""" | |
notaCredito = etree.Element('notaCredito') | |
notaCredito.set("id", "comprobante") | |
notaCredito.set("version", "1.1.0") | |
# generar infoTributaria | |
infoTributaria = self._get_tax_element(refund, access_key, emission_code) | |
notaCredito.append(infoTributaria) | |
# generar infoNotaCredito | |
infoNotaCredito = self._get_refund_element(refund, invoice) | |
notaCredito.append(infoNotaCredito) | |
#generar detalles | |
detalles = self._get_detail_element_refund(refund) | |
notaCredito.append(detalles) | |
return notaCredito | |
def get_access_key(self, cr, uid, invoice): | |
auth = invoice.journal_id.auth_id | |
ld = invoice.date_invoice.split('-') | |
ld.reverse() | |
fecha = ''.join(ld) | |
# | |
tcomp = auth.type_id.code | |
ruc = invoice.company_id.partner_id.ced_ruc | |
serie = '{0}{1}'.format(auth.serie_entidad, auth.serie_emision) | |
numero = invoice.supplier_invoice_number.replace('-','')[6:15] # FIX w/ number | |
codigo_numero = self.get_code(cr, uid, invoice) | |
tipo_emision = invoice.company_id.emission_code | |
access_key = ( | |
[fecha, tcomp, ruc], | |
[serie, numero, codigo_numero, tipo_emision] | |
) | |
return access_key | |
def action_generate_einvoice(self, cr, uid, ids, context=None): | |
""" | |
""" | |
for obj in self.browse(cr, uid, ids): | |
# Codigo de acceso | |
if obj.type in [ 'out_invoice', 'out_refund']: | |
ak_temp = self.get_access_key(cr, uid, obj) | |
access_key = SRIService.create_access_key(ak_temp) | |
emission_code = obj.company_id.emission_code | |
self.write(cr, uid, [obj.id], {'clave_acceso': access_key, 'emission_code': emission_code}) | |
if obj.type == 'out_invoice': | |
# XML del comprobante electrónico: factura | |
factura = self._generate_xml_invoice(obj, access_key, emission_code) | |
else: | |
invoice_ids = self.pool.get('account.invoice').search(cr, uid, [('number','=',obj.name)]) | |
factura_origen = self.browse(cr, uid, invoice_ids, context = context) | |
# XML del comprobante electrónico: factura | |
factura = self._generate_xml_refund(obj, factura_origen, access_key, emission_code) | |
#validación del xml | |
inv_xml = InvoiceXML(factura) | |
if obj.type == 'out_invoice': | |
inv_xml.validate_invoice_xml() | |
else: | |
inv_xml.validate_refund_xml() | |
inv_xml.save(access_key) | |
# firma de XML, now what ?? | |
# TODO: zip, checksum, save, send_mail | |
xades = Xades() | |
file_pk12 = obj.company_id.electronic_signature | |
password = obj.company_id.password_electronic_signature | |
xades.apply_digital_signature(access_key, file_pk12, password) | |
# recepción del comprobante electrónico | |
self.send_receipt(cr, uid, access_key) | |
# solicitud de autorización del comprobante electrónico | |
autorizacion = self.request_authorization(cr, uid, obj, access_key) | |
if autorizacion: | |
if obj.type == 'out_invoice': | |
# envío del correo electrónico de factura al cliente | |
self.send_mail_invoice(cr, uid, obj, access_key, context) | |
else: | |
# envío del correo electrónico de nota de crédito al cliente | |
self.send_mail_refund(cr, uid, obj, access_key, context) | |
else: | |
raise osv.except_osv('Error', 'No se puede enviar el correo electrónico la factura no se encuentra autorizada por el SRI') | |
def send_receipt(self, cr, uid, access_key): | |
name = '%s%s.xml' %('/opt/facturas/', access_key) | |
cadena = open(name, mode='rb').read() | |
document = parseString(cadena.strip()) | |
xml = document.toxml('UTF-8').encode('base64') | |
client = Client(SRIService.get_ws_test()[0]) | |
result = client.service.validarComprobante(xml) | |
self.__logger.info("RecepcionComprobantes: %s" % result) | |
mensaje_error = "" | |
if (result[0] == 'DEVUELTA'): | |
comprobante = result[1].comprobante | |
mensaje_error += 'Clave de Acceso: ' + comprobante[0].claveAcceso | |
mensajes = comprobante[0].mensajes | |
i = 0 | |
mensaje_error += "\nErrores:\n" | |
while i < len(mensajes): | |
mensaje = mensajes[i] | |
mensaje_error += 'Identificador: ' + mensaje[i].identificador + '\nMensaje: ' + mensaje[i].mensaje + '\nInformacion Adicional: ' + mensaje[i].informacionAdicional + '\nTipo: ' + mensaje[i].tipo + "\n" | |
i += 1 | |
raise osv.except_osv('Error SRI', mensaje_error) | |
return True | |
def request_authorization(self, cr, uid, obj, access_key): | |
if not obj.numero_autorizacion: | |
try: | |
client_auto = Client(SRIService.get_ws_test()[1]) | |
result_auto = client_auto.service.autorizacionComprobante(access_key) | |
self.__logger.info("AutorizacionComprobantes: %s" % result_auto) | |
if result_auto[2] == '': | |
raise osv.except_osv('Error SRI', 'No existe comprobante') | |
else: | |
autorizaciones = result_auto[2].autorizacion | |
i = 0 | |
autorizado = False | |
while i < len(autorizaciones): | |
autorizacion = autorizaciones[i] | |
estado = autorizacion.estado | |
fecha_autorizacion = autorizacion.fechaAutorizacion | |
mensaje_error = '' | |
if (estado == 'NO AUTORIZADO'): | |
#comprobante no autorizado | |
mensajes = autorizacion.mensajes | |
j = 0 | |
mensaje_error += "\nErrores:\n" | |
while j < len(mensajes): | |
mensaje = mensajes[j] | |
mensaje_error += 'Identificador: ' + mensaje[j].identificador + '\nMensaje: ' + mensaje[j].mensaje + '\nTipo: ' + mensaje[j].tipo + '\n' | |
j += 1 | |
else: | |
autorizado = True | |
numero_autorizacion = autorizacion.numeroAutorizacion | |
i += 1 | |
if autorizado == True: | |
self.write(cr, uid, obj.id, {'autorizado_sri': True, 'numero_autorizacion': numero_autorizacion, 'fecha_autorizacion': fecha_autorizacion}) | |
autorizacion_xml = etree.Element('autorizacion') | |
etree.SubElement(autorizacion_xml, 'estado').text = estado | |
etree.SubElement(autorizacion_xml, 'numeroAutorizacion').text = numero_autorizacion | |
etree.SubElement(autorizacion_xml, 'fechaAutorizacion').text = str(fecha_autorizacion.strftime("%d/%m/%Y %H:%M:%S")) | |
etree.SubElement(autorizacion_xml, 'comprobante').text = etree.CDATA(autorizacion.comprobante) | |
tree = etree.ElementTree(autorizacion_xml) | |
name = '%s%s.xml' %('/opt/facturas/', access_key) | |
fichero = tree.write(name,pretty_print=True,xml_declaration=True,encoding='utf-8',method="xml") | |
else: | |
raise osv.except_osv('Error SRI', mensaje_error) | |
except TransportError as e: | |
#raise osv.except_osv('Error', str(e)) | |
raise osv.except_osv(('Warning!'), (str(e))) | |
else: | |
raise osv.except_osv('Error', 'Factura Autorizada por el SRI') | |
return numero_autorizacion | |
def send_mail_invoice(self, cr, uid, obj, access_key, context=None): | |
name = '%s%s.xml' %('/opt/facturas/', access_key) | |
cadena = open(name, mode='rb').read() | |
attachment_id = self.pool.get('ir.attachment').create(cr, uid, | |
{ | |
'name': '%s.xml' % (access_key), | |
'datas': base64.b64encode(cadena), | |
'datas_fname': '%s.xml' % (access_key), | |
'res_model': self._name, | |
'res_id': obj.id, | |
'type': 'binary' | |
}, context=context) | |
email_template_obj = self.pool.get('email.template') | |
template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'email_template_edi_invoice')[1] | |
email_template_obj.write(cr, uid, template_id, {'attachment_ids': [(6, 0, [attachment_id])]}) | |
email_template_obj.send_mail(cr, uid, template_id, obj.id, True) | |
return True | |
def send_mail_refund(self, cr, uid, obj, access_key, context=None): | |
name = '%s%s.xml' %('/opt/facturas/', access_key) | |
cadena = open(name, mode='rb').read() | |
attachment_id = self.pool.get('ir.attachment').create(cr, uid, | |
{ | |
'name': '%s.xml' % (access_key), | |
'datas': base64.b64encode(cadena), | |
'datas_fname': '%s.xml' % (access_key), | |
'res_model': self._name, | |
'res_id': obj.id, | |
'type': 'binary' | |
}, context=context) | |
email_template_obj = self.pool.get('email.template') | |
template_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'email_template_edi_refund')[1] | |
email_template_obj.write(cr, uid, template_id, {'attachment_ids': [(6, 0, [attachment_id])]}) | |
email_template_obj.send_mail(cr, uid, template_id, obj.id, True) | |
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
############################################################################## | |
# | |
# XADES | |
# Copyright (C) 2014 Cristian Salamea All Rights Reserved | |
# [email protected] | |
# $Id$ | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
# | |
############################################################################## | |
import logging | |
import os | |
from lxml import etree | |
from lxml.etree import DocumentInvalid | |
try: | |
from suds.client import Client | |
from suds.transport import TransportError | |
except ImportError: | |
raise ImportError('Instalar Libreria suds') | |
from .xades import CheckDigit | |
class InvoiceXML(object): | |
INVOICE_XSD_PATH = 'schemas/factura_v1.1.0.xsd' | |
REFUND_XSD_PATH = 'schemas/notaCredito_v1.1.0.xsd' | |
INVOICE_SCHEMA_INVALID = """El sistema generó el XML pero la factura no pasa la validación XSD del SRI. | |
\nLos errores mas comunes son:\n* RUC,Cédula o Pasaporte contiene caracteres no válidos.\n* Números de documentos están duplicados.\n\nEl siguiente error contiene el identificador o número de documento en conflicto:\n\n %s""" | |
@classmethod | |
def __init__(self, element): | |
self.invoice_element = element | |
@classmethod | |
def save(self, access_key): | |
OPT_PATH = '/opt/facturas/' | |
name = '%s%s.xml' % (OPT_PATH, access_key) | |
tree = etree.ElementTree(self.invoice_element) | |
tree.write(name, pretty_print=True, xml_declaration=True, encoding='utf-8', method="xml") | |
@classmethod | |
def validate_invoice_xml(self): | |
""" | |
""" | |
file_path = os.path.join(os.path.dirname(__file__), self.INVOICE_XSD_PATH) | |
schema_file = open(file_path) | |
xmlschema_doc = etree.parse(schema_file) | |
xmlschema = etree.XMLSchema(xmlschema_doc) | |
try: | |
xmlschema.assertValid(self.invoice_element) | |
except DocumentInvalid as e: | |
raise osv.except_osv('Error de Datos', self.INVOICE_SCHEMA_INVALID % str(e)) | |
@classmethod | |
def validate_refund_xml(self): | |
""" | |
""" | |
file_path = os.path.join(os.path.dirname(__file__), self.REFUND_XSD_PATH) | |
schema_file = open(file_path) | |
xmlschema_doc = etree.parse(schema_file) | |
xmlschema = etree.XMLSchema(xmlschema_doc) | |
try: | |
xmlschema.assertValid(self.invoice_element) | |
except DocumentInvalid as e: | |
raise osv.except_osv('Error de Datos', self.INVOICE_SCHEMA_INVALID % str(e)) | |
class Service(object): | |
__AMBIENTE_PRUEBA = '1' | |
__AMBIENTE_PROD = '2' | |
__WS_TEST_RECEIV = 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantes?wsdl' | |
__WS_TEST_AUTH = 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantes?wsdl' | |
__WS_RECEIV = 'https://cel.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantes?wsdl' | |
__WS_AUTH = 'https://cel.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantes?wsdl' | |
@classmethod | |
def get_env_test(self): | |
return self.__AMBIENTE_PRUEBA | |
@classmethod | |
def get_env_prod(self): | |
return self.__AMBIENTE_PROD | |
@classmethod | |
def get_ws_test(self): | |
return self.__WS_TEST_RECEIV, self.__WS_TEST_AUTH | |
@classmethod | |
def get_ws_prod(self): | |
return self.__WS_RECEIV, self.__WS_AUTH | |
@classmethod | |
def create_access_key(self, values): | |
""" | |
values: tuple ([], []) | |
""" | |
env = self.get_env_test() | |
dato = ''.join(values[0] + [env] + values[1]) | |
modulo = CheckDigit.compute_mod11(dato) | |
access_key = ''.join([dato, str(modulo)]) | |
return access_key |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
############################################################################## | |
# | |
# XADES | |
# Copyright (C) 2014 Cristian Salamea All Rights Reserved | |
# [email protected] | |
# $Id$ | |
# | |
# This program is free software: you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
# | |
############################################################################## | |
import os | |
import base64 | |
import StringIO | |
import hashlib | |
import datetime | |
import subprocess | |
try: | |
from OpenSSL import crypto | |
except ImportError: | |
raise ImportError('Instalar la libreria para soporte OpenSSL: pip install PyOpenSSL') | |
from pytz import timezone | |
class CheckDigit(object): | |
# Definicion modulo 11 | |
_MODULO_11 = { | |
'BASE': 11, | |
'FACTOR': 2, | |
'RETORNO11': 0, | |
'RETORNO10': 1, | |
'PESO': 2, | |
'MAX_WEIGHT': 7 | |
} | |
@classmethod | |
def _eval_mod11(self, modulo): | |
if modulo == self._MODULO_11['BASE']: | |
return self._MODULO_11['RETORNO11'] | |
elif modulo == self._MODULO_11['BASE'] - 1: | |
return self._MODULO_11['RETORNO10'] | |
else: | |
return modulo | |
@classmethod | |
def compute_mod11(self, dato): | |
""" | |
Calculo mod 11 | |
return int | |
""" | |
total = 0 | |
weight = self._MODULO_11['PESO'] | |
for item in reversed(dato): | |
total += int(item) * weight | |
weight += 1 | |
if weight > self._MODULO_11['MAX_WEIGHT']: | |
weight = self._MODULO_11['PESO'] | |
mod = 11 - total % self._MODULO_11['BASE'] | |
mod = self._eval_mod11(mod) | |
return mod | |
class Xades(object): | |
def apply_digital_signature(self, access_key, file_pk12, password): | |
""" | |
Metodo que aplica la firma digital al XML | |
""" | |
OPT_PATH = '/opt/facturas/' | |
JAR_PATH = 'firma/prctXadesBes.jar' | |
JAVA_CMD = 'java' | |
ds_document = False | |
name = '%s%s.xml' % (OPT_PATH, access_key) | |
# firma electrónica del xml | |
firma_path = os.path.join(os.path.dirname(__file__), JAR_PATH) | |
# invocación del jar de la firma electrónica | |
subprocess.call([JAVA_CMD, '-jar', firma_path, name, name, file_pk12, password]) | |
return ds_document |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment