Commit ec0d11b5 authored by Jorge Sanabria's avatar Jorge Sanabria
Browse files

Update patch

parent 9bb2483d
l10n_co_edi_jorels
------------------
Copyright (c) - Jorels S.A.S.
Copyright (2019-2020) - Jorels SAS
[info@jorels.com](mailto:info@jorels.com)
......
l10n_co_edi_jorels
------------------
Copyright (c) - Jorels S.A.S.
Copyright (2019-2020) - Jorels SAS
[info@jorels.com](mailto:info@jorels.com)
......
......@@ -24,7 +24,7 @@
'name': "Free electronic invoicing for Colombia by Jorels",
'summary': 'Free electronic invoicing for Colombia by Jorels',
'description': "Manages electronic invoicing management for companies in Colombia",
'author': "Jorels",
'author': "Jorels SAS",
'license': "LGPL-3",
'category': 'Invoicing & Payments',
'version': '12.0.0.1',
......@@ -52,7 +52,9 @@
'views/config/account_product_view.xml',
'views/config/account_taxes_view.xml',
'views/account_invoice_view.xml',
'views/res_partner_view.xml',
'report/report_invoice.xml',
'data/mail_template_data.xml',
],
# 'external_dependencies': {
# 'python': [
......
<?xml version="1.0" ?>
<odoo>
<data noupdate="1">
<record id="account.email_template_edi_invoice" model="mail.template">
<field name="name">Invoice: Send by email</field>
<field name="model_id" ref="account.model_account_invoice"/>
<!-- <field name="email_from">${object.user_id.email_formatted |safe}</field>-->
<field name="email_from">${object.company_id.email_edi}</field>
<field name="email_to">${object.partner_id.email_edi}</field>
<field name="partner_to">${object.partner_id.id}</field>
<field name="subject">${object.company_id.vat or ''};${object.company_id.business_name or ''};${object.number or ''};${object.ei_type_document_id.code or ''};${object.company_id.business_name or ''};</field>
<field name="body_html" type="html">
<div style="margin: 0px; padding: 0px;">
<p style="margin: 0px; padding: 0px; font-size: 13px;">
Dear ${object.partner_id.name}
% if object.partner_id.parent_id:
(${object.partner_id.parent_id.name})
% endif
<br /><br />
Here is your
% if object.number:
invoice <strong>${object.number}</strong>
% else:
invoice
%endif
% if object.origin:
(with reference: ${object.origin})
% endif
amounting in <strong>${format_amount(object.amount_total, object.currency_id)}</strong>
from ${object.company_id.name}.
% if object.state=='paid':
This invoice is already paid.
% else:
Please remit payment at your earliest convenience.
% endif
<br /><br />
Do not hesitate to contact us if you have any question.
</p>
</div>
</field>
<field name="report_template" ref="account.account_invoices"/>
<field name="report_name">Invoice_${(object.number or '').replace('/','_')}${object.state == 'draft' and '_draft' or ''}</field>
<field name="lang">${object.partner_id.lang}</field>
<field name="user_signature" eval="False"/>
<field name="auto_delete" eval="True"/>
</record>
</data>
</odoo>
......@@ -23,3 +23,5 @@
from . import listings
from . import config
from . import account_invoice
from . import res_partner
from . import mail_template
......@@ -22,6 +22,12 @@
import logging
from odoo import api, fields, models, tools
from odoo.exceptions import Warning
import json
import requests
from pathlib import Path
_logger = logging.getLogger(__name__)
......@@ -33,15 +39,18 @@ class ResCompany(models.Model):
string="Tipo de documento de identificación")
type_organization_id = fields.Many2one(comodel_name='l10n_co_edi_jorels.type_organizations',
string="Tipo de organización")
type_regime_id = fields.Many2one(comodel_name='l10n_co_edi_jorels.type_regimes',
string="Tipo de regimen")
type_liability_id = fields.Many2one(comodel_name='l10n_co_edi_jorels.type_liabilities',
string="Tipo de responsabilidad")
business_name = fields.Char(string="Nombre", compute='_compute_business_name', store=True)
type_regime_id = fields.Many2one('l10n_co_edi_jorels.type_regimes', compute='_compute_edi',
inverse="_inverse_type_regime_id", string="Tipo de regimen")
type_liability_id = fields.Many2one('l10n_co_edi_jorels.type_liabilities', compute='_compute_edi',
inverse="_inverse_type_liability_id", string="Tipo de responsabilidad")
business_name = fields.Char(string="Razón social para facturar")
merchant_registration = fields.Char(string="Registro mercantil", compute='_compute_merchant_registration',
store=True)
municipality_id = fields.Many2one(comodel_name='l10n_co_edi_jorels.municipalities',
string="Municipalidad")
municipality_id = fields.Many2one('l10n_co_edi_jorels.municipalities', compute='_compute_edi',
inverse="_inverse_municipality_id", string="Municipalidad")
# Electronic invoice sender Mail
email_edi = fields.Char(related='partner_id.email_edi', store=True, readonly=False)
# address -> street
# phone -> phone
......@@ -68,14 +77,94 @@ class ResCompany(models.Model):
# Report
report_custom_text = fields.Html(string="Custom text")
@api.multi
@api.depends('name')
def _compute_business_name(self):
for rec in self:
rec.business_name = rec.name
def _compute_edi(self):
for company in self.filtered(lambda company: company.partner_id):
if company.partner_id.municipality_id:
type_regime_id = company.partner_id.type_regime_id
type_liability_id = company.partner_id.type_liability_id
municipality_id = company.partner_id.municipality_id
company.update({
'type_regime_id': type_regime_id,
'type_liability_id': type_liability_id,
'municipality_id': municipality_id,
})
def _inverse_type_regime_id(self):
for company in self:
company.partner_id.type_regime_id = company.type_regime_id
def _inverse_type_liability_id(self):
for company in self:
company.partner_id.type_liability_id = company.type_liability_id
def _inverse_municipality_id(self):
for company in self:
company.partner_id.municipality_id = company.municipality_id
# @api.multi
# @api.depends('name')
# def _compute_business_name(self):
# for rec in self:
# rec.business_name = rec.name
@api.multi
@api.depends('company_registry')
def _compute_merchant_registration(self):
for rec in self:
rec.merchant_registration = rec.company_registry
# Actualización de entorno
@api.multi
def update_environment(self, environment):
try:
success = False
for rec in self:
api_file_path = Path(__file__).parents[2] / 'static' / 'api.json'
with open(api_file_path) as api_file:
requests_data = json.loads(api_file.read())
requests_data['environment']['type_environment_id'] = environment
_logger.debug("Request environment DIAN: %s",
json.dumps(requests_data['environment'], indent=2, sort_keys=False))
token = rec.api_key
api_url = rec.api_url
header = {"accept": "application/json", "Content-Type": "application/json"}
api_url = api_url + "/api/ubl2.1/config/environment"
header.update({'Authorization': 'Bearer ' + token})
response = requests.put(api_url, json.dumps(requests_data['environment']), headers=header).json()
_logger.debug('API Response PUT environment: %s', response)
if 'message' in response:
rec.env.user.notify_info(message=response['message'])
response = requests.get(api_url, headers=header).json()
_logger.debug('API Response GET environment: %s', response)
if 'type_environment_id' in response:
if environment == response['type_environment_id']:
rec.env.user.notify_info(message="Se ha actualizado el entorno." \
"Ahora, sincronice las resoluciones")
success = True
if 'message' in response:
rec.env.user.notify_info(message=response['message'])
except Exception as e:
_logger.debug("Error de comunicación: %s", e)
return success
@api.multi
def write(self, vals):
for rec in self:
if 'is_not_test' in vals:
if vals['is_not_test'] != rec.is_not_test:
if vals['is_not_test']:
environment = 1
else:
environment = 2
if not self.update_environment(environment):
vals['is_not_test'] = not vals['is_not_test']
return super(ResCompany, self).write(vals)
......@@ -165,15 +165,14 @@ class ResConfigSettings(models.TransientModel):
response = requests.get(api_url, headers=header).json()
_logger.debug('API Response: %s', response)
# No es posible comprobar la firma digital, solo el password
# No es posible comprobar la firma digital, ni el password
if 'id' in response:
if rec.certificate_password == response['password']:
rec.certificate_message = "¡Genial, la información coincide con la API!. " \
"Sin embargo, si tiene problemas de autenticación, " \
"pruebe actualizando su archivo de firma electrónica"
if 'name' in response:
rec.certificate_message = "El nombre del certificado actualmente almacenado en la API es: " + \
response['name']
else:
rec.certificate_message = "Lo siento, la información no coincide con la API. " \
"¡Intente nuevamente!"
rec.certificate_message = "No se ha encontrado un certificado. " \
Suba uno o Intente nuevamente!"
elif 'message' in response:
if response['message'] == 'Unauthenticated.' or response['message'] == '':
rec.certificate_message = 'No es posible la autenticación con la API. ' \
......@@ -182,6 +181,7 @@ class ResConfigSettings(models.TransientModel):
rec.certificate_message = response['message']
else:
rec.certificate_message = 'Algo sucede. No es posible comunicarse con la API'
except Exception as e:
rec.certificate_message = "¡Error de conexión con la API!"
_logger.debug("Error de conexión: %s", e)
......@@ -316,48 +316,79 @@ class ResConfigSettings(models.TransientModel):
raise Warning(response['message'])
else:
# First delete resolutions on database
self._cr.execute("""DELETE FROM l10n_co_edi_jorels_resolution""")
# self._cr.execute("""DELETE FROM l10n_co_edi_jorels_resolution""")
# Now create new resolutions
for resolution in response:
self._cr.execute(
"INSERT INTO l10n_co_edi_jorels_resolution (" \
"resolution_type_document_id," \
"resolution_prefix," \
"resolution_resolution," \
"resolution_resolution_date," \
"resolution_technical_key," \
"resolution_from," \
"resolution_to," \
"resolution_date_from," \
"resolution_date_to," \
"resolution_id," \
"resolution_number," \
"resolution_next_consecutive," \
"create_uid," \
"create_date," \
"write_uid," \
"write_date" \
") VALUES (%d, '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', %d, %d, '%s', %d, %s, %d, %s)" %
(
resolution['type_document_id'],
resolution['prefix'],
resolution['resolution'],
resolution['resolution_date'],
resolution['technical_key'],
resolution['from'],
resolution['to'],
resolution['date_from'],
resolution['date_to'],
resolution['id'],
resolution['number'],
resolution['next_consecutive'],
self.env.user.id,
'NOW()',
self.env.user.id,
'NOW()'
if resolution['resolution_date']:
if int(resolution['resolution_date'].split('-')[0]) < 2000:
resolution['resolution_date'] = "'2000-01-01'"
else:
resolution['resolution_date'] = "'" + resolution['resolution_date'] + "'"
else:
resolution['resolution_date'] = 'NULL'
if resolution['date_from']:
if int(resolution['date_from'].split('-')[0]) < 2000:
resolution['date_from'] = "'2000-01-01'"
else:
resolution['date_from'] = "'" + resolution['date_from'] + "'"
else:
resolution['date_from'] = 'NULL'
if resolution['date_to']:
if int(resolution['date_to'].split('-')[0]) < 2000:
resolution['date_to'] = "'2000-01-01'"
else:
resolution['date_to'] = "'" + resolution['date_to'] + "'"
else:
resolution['date_to'] = 'NULL'
# Sincronizando Odoo con la API
resolution_search = self.env['l10n_co_edi_jorels.resolution'].search(
[('resolution_id', '=', resolution['id'])])
# TO DO: Actualizar con UPDATE si ya existe
# Si no está ya en la base de datos, entonces la agrega
if not resolution_search:
self._cr.execute(
"INSERT INTO l10n_co_edi_jorels_resolution (" \
"resolution_type_document_id," \
"resolution_prefix," \
"resolution_resolution," \
"resolution_resolution_date," \
"resolution_technical_key," \
"resolution_from," \
"resolution_to," \
"resolution_date_from," \
"resolution_date_to," \
"resolution_id," \
"resolution_number," \
"resolution_next_consecutive," \
"create_uid," \
"create_date," \
"write_uid," \
"write_date" \
") VALUES (%d, '%s', NULLIF('%s','None'), %s, NULLIF('%s','None'), %d, %d, %s, %s, %d, %d, '%s', %d, %s, %d, %s)" %
(
resolution['type_document_id'],
resolution['prefix'],
resolution['resolution'],
resolution['resolution_date'],
resolution['technical_key'],
resolution['from'],
resolution['to'],
resolution['date_from'],
resolution['date_to'],
resolution['id'],
resolution['number'],
resolution['next_consecutive'],
self.env.user.id,
'NOW()',
self.env.user.id,
'NOW()'
)
)
)
except Exception as e:
raise Warning(e)
......@@ -368,3 +399,36 @@ class ResConfigSettings(models.TransientModel):
"res_model": "l10n_co_edi_jorels.resolution",
"views": [[False, "tree"], [False, "form"]],
}
# Actualización de entorno
@api.multi
def button_put_environment(self):
try:
for rec in self:
if rec.is_not_test:
environment = 1
else:
environment = 2
api_file_path = Path(__file__).parents[2] / 'static' / 'api.json'
with open(api_file_path) as api_file:
requests_data = json.loads(api_file.read())
requests_data['environment']['type_environment_id'] = environment
_logger.debug("Request environment DIAN: %s",
json.dumps(requests_data['environment'], indent=2, sort_keys=False))
token = rec.api_key
api_url = rec.api_url
header = {"accept": "application/json", "Content-Type": "application/json"}
api_url = api_url + "/api/ubl2.1/config/environment"
header.update({'Authorization': 'Bearer ' + token})
response = requests.put(api_url, json.dumps(requests_data['environment']), headers=header).json()
_logger.debug('API Response PUT environment: %s', response)
if 'message' in response:
rec.env.user.notify_info(message=response['message'])
else:
rec.env.user.notify_info(message="Ahora, sincronice las resoluciones")
except Exception as e:
_logger.debug("Error de comunicación: %s", e)
......@@ -36,19 +36,22 @@ _logger = logging.getLogger(__name__)
class Resolution(models.Model):
_name = 'l10n_co_edi_jorels.resolution'
_description = 'Electronic invoice resolution'
_rec_name = 'resolution_resolution'
_rec_name = 'name'
name = fields.Char(string="Name", compute='_compute_name')
# Range Resolution DIAN
resolution_type_document_id = fields.Many2one(comodel_name="l10n_co_edi_jorels.type_documents", string='Tipo de documento',
resolution_type_document_id = fields.Many2one(comodel_name="l10n_co_edi_jorels.type_documents",
string='Tipo de documento',
required=True)
resolution_prefix = fields.Char(string="Prefijo", required=True)
resolution_resolution = fields.Char(string="Resolución", required=True)
resolution_resolution_date = fields.Date(string="Fecha de la resolución", required=True)
resolution_technical_key = fields.Char(string="Llave tecnica", required=True)
resolution_prefix = fields.Char(string="Prefijo")
resolution_resolution = fields.Char(string="Resolución")
resolution_resolution_date = fields.Date(string="Fecha de la resolución")
resolution_technical_key = fields.Char(string="Llave tecnica")
resolution_from = fields.Integer(string="Desde", required=True)
resolution_to = fields.Integer(string="Hasta", required=True)
resolution_date_from = fields.Date(string="Fecha Desde", required=True)
resolution_date_to = fields.Date(string="Fecha Hasta", required=True)
resolution_date_from = fields.Date(string="Fecha Desde")
resolution_date_to = fields.Date(string="Fecha Hasta")
resolution_id = fields.Integer(string="Api ID", readonly=True, copy=False, index=True)
resolution_number = fields.Integer(string="Numero", readonly=True, copy=False)
......@@ -56,6 +59,11 @@ class Resolution(models.Model):
resolution_message = fields.Char(string="Mensaje", readonly=True)
def _compute_name(self):
for rec in self:
rec.name = str(rec.resolution_id) + ' - ' + \
rec.resolution_type_document_id.name + ' [' + rec.resolution_type_document_id.code + ']'
@api.model
def create(self, vals):
vals, success = self.post_resolution(vals)
......@@ -90,14 +98,42 @@ class Resolution(models.Model):
requests_data = json.loads(api_file.read())
requests_data['resolucion']['type_document_id'] = vals['resolution_type_document_id']
requests_data['resolucion']['prefix'] = vals['resolution_prefix']
requests_data['resolucion']['resolution'] = vals['resolution_resolution']
requests_data['resolucion']['resolution_date'] = vals['resolution_resolution_date']
requests_data['resolucion']['technical_key'] = vals['resolution_technical_key']
if vals['resolution_prefix']:
requests_data['resolucion']['prefix'] = vals['resolution_prefix']
else:
del requests_data['resolucion']['prefix']
if vals['resolution_resolution']:
requests_data['resolucion']['resolution'] = vals['resolution_resolution']
else:
del requests_data['resolucion']['resolution']
if vals['resolution_resolution_date']:
requests_data['resolucion']['resolution_date'] = vals['resolution_resolution_date']
else:
del requests_data['resolucion']['resolution_date']
if vals['resolution_technical_key']:
requests_data['resolucion']['technical_key'] = vals['resolution_technical_key']
else:
del requests_data['resolucion']['technical_key']
requests_data['resolucion']['from'] = vals['resolution_from']
requests_data['resolucion']['to'] = vals['resolution_to']
requests_data['resolucion']['date_from'] = vals['resolution_date_from']
requests_data['resolucion']['date_to'] = vals['resolution_date_to']
if vals['resolution_date_from']:
requests_data['resolucion']['date_from'] = vals['resolution_date_from']
else:
del requests_data['resolucion']['date_from']
if vals['resolution_date_to']:
requests_data['resolucion']['date_to'] = vals['resolution_date_to']
else:
del requests_data['resolucion']['date_to']
_logger.debug("Request create resolution DIAN: %s",
json.dumps(requests_data['resolucion'], indent=2, sort_keys=False))
token = str(self.env.user.company_id.api_key)
api_url = str(self.env.user.company_id.api_url)
......@@ -173,7 +209,27 @@ class Resolution(models.Model):
len_prefix = len('resolution_')
for val in vals:
requests_data['resolucion'][val[len_prefix:]] = vals[val]
_logger.debug("Send to API: %s", requests_data['resolucion'])
if not requests_data['resolucion']['prefix']:
requests_data['resolucion']['prefix'] = ''
if not requests_data['resolucion']['resolution']:
requests_data['resolucion']['resolution'] = ''
if not requests_data['resolucion']['resolution_date']:
requests_data['resolucion']['resolution_date'] = ''
if not requests_data['resolucion']['technical_key']:
requests_data['resolucion']['technical_key'] = ''
if not requests_data['resolucion']['date_from']:
requests_data['resolucion']['date_from'] = ''
if not requests_data['resolucion']['date_to']:
requests_data['resolucion']['date_to'] = ''
_logger.debug("Request update resolution DIAN: %s",
json.dumps(requests_data['resolucion'], indent=2, sort_keys=False))
token = str(self.env.user.company_id.api_key)
api_url = str(self.env.user.company_id.api_url)
......
from odoo import api, models
class MailTemplate(models.Model):
_inherit = 'mail.template'
@api.multi
def generate_email(self, res_ids, fields=None):
res = super(MailTemplate, self).generate_email(res_ids, fields)
if self.env.context.get('attach_ei_zip_file'):
for res_id, template in self.get_email_template(res_ids).items():
invoice = self.env['account.invoice'].browse(res_id)
zip_filename = invoice.ei_zip_name
zip_string = invoice.ei_zip_base64_bytes
zip_attachments = invoice._generate_email_attachment(zip_filename, zip_string)
if len(zip_attachments) == 1 and template.report_name:
report_name = self._render_template(
template.report_name, template.model, res_id)
ext = '.zip'
if not report_name.endswith(ext):
report_name += ext
attachments = [(report_name, zip_attachments.datas)]
else:
attachments = [(a.name, a.datas) for a in zip_attachments]
res[res_id]['attachments'] += attachments
return res