diff --git a/l10n_uy_account/views/account_journal_view.xml b/l10n_uy_account/views/account_journal_view.xml
index 0b5930ee..6d7195e8 100644
--- a/l10n_uy_account/views/account_journal_view.xml
+++ b/l10n_uy_account/views/account_journal_view.xml
@@ -8,7 +8,7 @@
-
+ {'invisible': ['|', ('type', 'not in', ['sale', 'purchase']), ('l10n_latam_use_documents', '=', True), ('country_code', '=', 'UY')]}
diff --git a/l10n_uy_edi/__manifest__.py b/l10n_uy_edi/__manifest__.py
index 61f7d57d..577353b7 100644
--- a/l10n_uy_edi/__manifest__.py
+++ b/l10n_uy_edi/__manifest__.py
@@ -7,7 +7,7 @@
'author': 'ADHOC SA',
'category': 'Localization',
'license': 'LGPL-3',
- 'version': "16.0.1.6.0",
+ 'version': "16.0.1.7.0",
'depends': [
'l10n_uy_account',
'account_debit_note',
diff --git a/l10n_uy_edi/data/ir_cron.xml b/l10n_uy_edi/data/ir_cron.xml
index 5ce6e8b7..8d66e65a 100755
--- a/l10n_uy_edi/data/ir_cron.xml
+++ b/l10n_uy_edi/data/ir_cron.xml
@@ -13,6 +13,16 @@
+
+ UY: Consult vendor bills received
+ 10
+ minutes
+ -1
+
+ code
+ model.l10n_uy_action_get_l10n_uy_received_invoices()
+
+
diff --git a/l10n_uy_edi/models/account_move.py b/l10n_uy_edi/models/account_move.py
index 13e69a0b..fbd14693 100644
--- a/l10n_uy_edi/models/account_move.py
+++ b/l10n_uy_edi/models/account_move.py
@@ -11,6 +11,7 @@ class AccountMove(models.Model):
_inherit = ['account.move', 'l10n.uy.cfe']
l10n_uy_journal_type = fields.Selection(related='journal_id.l10n_uy_type')
+ l10n_uy_idreq = fields.Text('idReq', copy=False, readonly=True, groups="base.group_system", help="We add this field to the vendor bills that are created from Uruware because if we need to consult them again through the consult of notifications we need to pass that idReq to consult 600 to get the response it again.")
# This is required to be able to save defaults taking into account the document type selected
l10n_latam_document_type_id = fields.Many2one(change_default=True)
diff --git a/l10n_uy_edi/models/l10n_uy_cfe.py b/l10n_uy_edi/models/l10n_uy_cfe.py
index b5d0efd5..ffb25407 100644
--- a/l10n_uy_edi/models/l10n_uy_cfe.py
+++ b/l10n_uy_edi/models/l10n_uy_cfe.py
@@ -4,7 +4,8 @@
import base64
import stdnum.uy
import re
-
+import xml.etree.ElementTree as ET
+# from lxml import etree
from odoo import _, fields, models, api
from odoo.exceptions import UserError
from odoo.tools.safe_eval import safe_eval
@@ -578,6 +579,241 @@ def _l10n_uy_get_cfe_serie(self):
})
return res
+ #def l10n_uy_xml_to_dict(self, xml_string):
+ # """ We use this method when we make a '610 - Solicitud de datos de Notificacion' request to make the information we obtain from the notification more readable.
+ # """
+ #test = etree.fromstring(xml_string.encode('utf-8'))
+ # root = ET.fromstring(xml_string)
+ #
+ # return root
+
+ #def l10n_uy_element_to_dict(self, element):
+ # import pdb; pdb.set_trace()
+ # if len(element) == 0:
+ # return element.text
+#
+ # result = {}
+ # for child in element:
+ # child_data = self.l10n_uy_element_to_dict(child)
+ # key = child.tag.replace('xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xmlns="http://cfe.dgi.gub.uy"', '')
+ # if key in result:
+ # if type(result[key]) is list:
+ # result[key].append(child_data)
+ # else:
+ # result[key] = [result[key], child_data]
+ # else:
+ # result[key] = child_data
+#
+ # return result
+
+ def l10n_uy_verify_codrta(self, company, response):
+ """ Verify response code from notifications (vendor bills). If response code is != 0 return True (can`t create new vendor bill), else return False (continue the process and create vendor bill). """
+ if response.Resp.CodRta == '01':
+ _logger.info('There are no notifications available in the UCFE for the company %s (id: %d)' % (company.name, company.id))
+ return True
+ elif response.Resp.CodRta != '00':
+ _logger.info(_('ERROR: This is what we receive when requesting notification data (610) %s') % response)
+ return True
+ else:
+ return False
+
+ def l10n_uy_dismiss_notification(self, company, response):
+ """ This is implemented for vendor bills. Is needed to dismiss the last notification if the last vendor bill was created in Odoo from Uruware. """
+ try:
+ response3 = company._l10n_uy_ucfe_inbox_operation('620', {
+ 'IdReq': response.Resp.IdReq,
+ 'TipoNotificacion': response.Resp.TipoNotificacion})
+ except:
+ _logger.warning('We found an error when dismissing the notification: id: %d' % (response.Resp.IdReq))
+ if response3.Resp.CodRta != '00':
+ raise UserError(_('ERROR: the notification could not be dismissed %s') % response)
+
+ def l10n_uy_create_partner_from_notification(self, value, partner_vat_RUC):
+ """ In case we need to create vendor bills synchronized with Uruware through notifications and the partner from the bill does not exist id Odoo, then we create it in this method. """
+ partner_name = root.findtext('.//*RznSoc')
+ partner_city = root.findtext('.//*Ciudad')
+ partner_state_id = root.findtext('.//*Departamento')
+ partner_street = root.findtext('.//*DomFiscal')
+ state_id = self.env['res.country.state'].search([('name', 'ilike', partner_state_id)], limit=1)
+ country_id = state_id.country_id
+ ruc = self.env.ref('l10n_uy_account.it_rut').id
+ #
+ partner_vals = {'name': partner_name,
+ 'vat': partner_vat_RUC,
+ 'city': partner_city,
+ 'street': partner_street,
+ 'state_id': state_id.id,
+ 'country_id': country_id.id,
+ 'l10n_latam_identification_type_id': ruc,
+ 'is_company': True}
+ partner_id = self.env['res.partner'].create(partner_vals)
+ return partner_id
+
+ def l10n_uy_create_vendor_line_ids(self, company, invoice_line_ids):
+ """ Here are created the lines of vendor bills that are synchronized through the Uruware notification request. """
+ line_ids = []
+ for value in invoice_line_ids:
+ #IndFac
+ # agregar
+ #7: Producto o servicio no
+ #facturable negativo. . No
+ #existe validación, excepto si
+ #A-C20= 1, B-C4=6 o 7.
+ domain_tax = [('country_code', '=', 'UY'), ('company_id.id', '=', company.id)]
+ ind_fact = value.find(".//IndFact").text
+ if ind_fact == '1':
+ # Exento de IVA
+ domain_tax += [('tax_group_id.l10n_uy_vat_code', '=', 'vat_exempt'), ('name', '=', 'IVA Compras Exento')]
+ elif ind_fact == '2':
+ # Gravado a Tasa Mínima
+ domain_tax += (('tax_group_id.l10n_uy_vat_code', '=', 'vat_10'), ('name', '=', 'IVA Compras 10%'))
+ elif ind_fact == '3':
+ # Gravado a Tasa Básica
+ domain_tax += (('tax_group_id.l10n_uy_vat_code', '=', 'vat_22'), ('name', '=', 'IVA Compras 22%'))
+ price_unit = value.find(".//PrecioUnitario").text
+ price_unit_signed = float(price_unit) if ind_fact != '7' else -1*float(price_unit)
+ tax_item = self.env['account.tax'].search(domain_tax, limit=1)
+ line_vals = {'move_type': 'in_invoice',
+ 'name': value.find(".//NomItem").text,
+ 'quantity': float(value.find(".//Cantidad").text),
+ 'price_unit': price_unit_signed,
+ 'tax_ids': [(6, 0, tax_item.ids)] if ind_fact not in ['6', '7'] else []}
+ line_ids.append((0, 0, line_vals))
+ return line_ids
+
+ def l10n_uy_create_vendor_invoice(self, company, response2, transport, l10n_uy_idreq):
+ """ Here the vendor bills are created and synchronized through the Uruware notification request. """
+ response2_to_string = str(response2)
+ xml_string = response2.Resp.XmlCfeFirmado
+ if not xml_string:
+ raise UserError('There is no information to create the vendor invoice in the notification %d consulted' % (l10n_uy_idreq))
+ xml_string = xml_string.replace('ns0:', '')
+ xml_string = re.sub(r']*>', '', xml_string)
+ #xml_string = xml_string.replace('xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xmlns="http://cfe.dgi.gub.uy"', '')
+ root = ET.fromstring(xml_string)
+ invoice_line_ids = []
+ invoice_date_due = False
+ l10n_latam_document_type_id = root.findtext('.//*TipoCFE')
+ # Facturas
+ if l10n_latam_document_type_id in ['101', '111', '181', '182', '121', '124', '131', '141', '151']:
+ move_type = 'in_invoice'
+ # Notas de crédito
+ if l10n_latam_document_type_id in ['102', '112', '122', '132', '142', '152']:
+ move_type = 'in_refund'
+ if l10n_latam_document_type_id in ['103', '113', '123', '133', '143', '153']:
+ move_type = 'in_refund'
+ new_invoice = self.env['account.move'].create({'l10n_uy_idreq': l10n_uy_idreq,
+ 'move_type': move_type,
+ })
+ try:
+ partner_vat_RUC = root.findtext('.//*RUCEmisor')
+ serieCfe = root.findtext('.//*Serie')
+ l10n_latam_document_number = root.findtext('.//*Nro')
+ req_data_pdf = {'rut': company.vat,
+ 'rutRecibido': partner_vat_RUC,
+ 'tipoCfe': l10n_latam_document_type_id,
+ 'serieCfe': serieCfe,
+ 'numeroCfe': l10n_latam_document_number}
+ self.l10n_uy_create_pdf_vendor_invoice(company, new_invoice, req_data_pdf)
+ # Mejorar esto '>\n<'.join([item for item in xml_string.split('><')]) para que se muestre mejor parseado, ver los links que dejé en la tarea nota 18/10/2023 09:54:57 --> creo que al final no haría falta
+ new_invoice.write({
+ 'l10n_uy_dgi_xml_response': transport.xml_response,
+ 'l10n_uy_cfe_xml': xml_string,
+ 'l10n_uy_dgi_xml_request': transport.xml_request,
+ })
+ invoice_date = datetime.strptime(root.findtext('.//*FchEmis'), '%Y-%m-%d').date()
+ fecha_vto = root.findtext('.//*FchVenc')
+ if fecha_vto:
+ invoice_date_due = datetime.strptime(fecha_vto, '%Y-%m-%d').date()
+ invoice_currency = root.findtext('.//*TpoMoneda')
+ amount_total = root.findtext('.//*MntTotal')
+ cant_lineas = root.findtext('.//*CantLinDet')
+ if invoice_currency == 'USD':
+ currency_id = self.env['res.currency'].search([('l10n_uy_bcu_code', '=', 2225), ('symbol', '=', 'USD')], limit=1)
+ elif invoice_currency == 'UYI':
+ currency_id = self.env['res.currency'].search([('l10n_uy_bcu_code', '=', 9800), ('symbol', '=', 'UYI')], limit=1)
+ elif invoice_currency == 'EUR':
+ currency_id = self.env['res.currency'].search([('l10n_uy_bcu_code', '=', 1111), ('symbol', '=', 'EUR')], limit=1)
+ elif invoice_currency == 'ARS':
+ currency_id = self.env['res.currency'].search([('l10n_uy_bcu_code', '=', 501), ('symbol', '=', 'ARS')], limit=1)
+ else:
+ currency_id = company.currency_id
+ # que identificador único uso para el comprobante? identificador de lado de lo que
+ # recibimos de uruware
+ # l10n_uy_cfe_uuid = value['Encabezado']['IdDoc']['NroInterno']
+ partner_id = self.env['res.partner'].search([('commercial_partner_id.vat', '=', partner_vat_RUC)], limit=1)
+ # Si no existe el partner lo creamos
+ if not partner_id:
+ partner_id = self.l10n_uy_create_partner_from_notification(value, partner_vat_RUC)
+ # para iterar los items usar findall
+ invoice_line_ids = root.findall('.//*Item')
+ line_ids = self.l10n_uy_create_vendor_line_ids(company, invoice_line_ids)
+ # este if era para verificar que el comprobante que estamos por crear no exista en odoo pero creo que se podría quitar
+ #if not self.env['account.move'].search([('l10n_uy_cfe_uuid', '=', l10n_uy_cfe_uuid)]):
+ document_type = self.env['l10n_latam.document.type'].search([('code', '=', l10n_latam_document_type_id)], limit=1).id
+ move_vals = {
+ 'partner_id': partner_id.id,
+ 'invoice_date': invoice_date,
+ 'l10n_latam_document_type_id': document_type,
+ 'l10n_latam_document_number': l10n_latam_document_number,
+ 'line_ids': line_ids,
+ 'currency_id': currency_id.id,}
+ if invoice_date_due:
+ move_vals['invoice_date_due'] = invoice_date_due
+ new_invoice.write(move_vals)
+ if len(new_invoice.invoice_line_ids) != int(cant_lineas):
+ raise UserError('The number of invoice lines %s (id:%d) is invalid' % (new_invoice.name, new_invoice.id))
+ except:
+ message = _("We found an error when loading information in this invoice")
+ new_invoice.message_post(body=message)
+ _logger.warning('We found an error when loading information on the vendor invoice: %s (id: %d)' % (new_invoice.name, new_invoice.id))
+
+ def l10n_uy_create_pdf_vendor_invoice(self, company, new_invoice, req_data_pdf):
+ """ The vendor invoice pdb is created and syncronized through the Uruware notification request. """
+ response_reporte_pdf = self.env.company._l10n_uy_ucfe_query('ObtenerPdfCfeRecibido', req_data_pdf)
+ new_invoice.l10n_uy_cfe_pdf = self.env['ir.attachment'].create({
+ 'name': (new_invoice.name or prefix.get(new_invoice._name, 'OBJ')).replace('/', '_') + '.pdf',
+ 'res_model': new_invoice._name, 'res_id': new_invoice.id,
+ 'type': 'binary', 'datas': base64.b64encode(response_reporte_pdf)
+ })
+
+ def action_l10n_uy_complete_vendor_invoice(self):
+ """ Sync with Uruware and complete vendor invoice information. """
+ l10n_uy_idreq = self.l10n_uy_idreq
+ company = self.company_id
+ response2, transport = company._l10n_uy_ucfe_inbox_operation('610', {'IdReq': l10n_uy_idreq}, return_transport=1)
+ self.l10n_uy_create_vendor_invoice(company, response2, transport, l10n_uy_idreq)
+
+ def l10n_uy_get_l10n_uy_received_invoices(self):
+ # TODO test it
+
+ # 600 - Consulta de Notificacion Disponible
+ for company in self.env['res.company'].search([('account_fiscal_country_id.code', '=', 'UY'), ('l10n_uy_ucfe_commerce_code', '!=', False)]):
+ response = company._l10n_uy_ucfe_inbox_operation('600')
+ # Si guardo idReq de la solicitud 600 luego más adelante puedo volver a consultar la notificación y no hace falta descartarla
+ # Por lo tanto conviene guardar el idReq y adjuntarlo a la factura
+ l10n_uy_idreq = response.Resp.IdReq
+ if self.l10n_uy_verify_codrta(company,response):
+ continue
+ # If there is notifications
+ while response.Resp.CodRta == '00':
+ try:
+ # response.Resp.TipoNotificacion
+ # 610 - Solicitud de datos de Notificacion
+ l10n_uy_idreq = response.Resp.IdReq
+ response2, transport = company._l10n_uy_ucfe_inbox_operation('610', {'IdReq': l10n_uy_idreq}, return_transport=1)
+ except:
+ _logger.warning('Encontramos un error al momento de sincronizar comprobantes de proveedor de la compañía: %s (id: %d)' % (company.name, company.id))
+ break
+ self.l10n_uy_create_vendor_invoice(company, response2, transport, l10n_uy_idreq)
+ #if new_invoice.amount_total != float(amount_total):
+ # No puedo implementar este control aún porque hay un bug en odoo. Ver task: 34023
+ # raise UserError('El monto total de la factura %s (id:%d)' % (new_invoice.name, new_invoice.id))
+ self.l10n_uy_dismiss_notification(company, response)
+ response = company._l10n_uy_ucfe_inbox_operation('600')
+ if self.l10n_uy_verify_codrta(company,response):
+ continue
+
@api.model
def l10n_uy_get_ucfe_notif(self):
# TODO test it
@@ -1360,3 +1596,8 @@ def _l10n_uy_dgi_post(self):
# TODO comprobar. este devolvera un campo clave llamado UUID que permite identificar el comprobante, si es enviando dos vence sno genera otro CFE firmado
return response
+
+ def l10n_uy_action_get_l10n_uy_received_invoices(self):
+ """UY: Consult vendor bills received"""
+ self.l10n_uy_get_l10n_uy_received_invoices()
+
diff --git a/l10n_uy_edi/models/res_company.py b/l10n_uy_edi/models/res_company.py
index c1f437ff..63088739 100644
--- a/l10n_uy_edi/models/res_company.py
+++ b/l10n_uy_edi/models/res_company.py
@@ -118,7 +118,7 @@ def _l10n_uy_ucfe_inbox_operation(self, msg_type, extra_req={}, return_transport
""" Call Operation get in msg_type for UCFE inbox webservice """
self.ensure_one()
# TODO consumir secuencia creada en Odoo
- id_req = 1
+ id_req = extra_req.get('IdReq') or 1
now = datetime.utcnow()
company = self.sudo()
data = {'Req': {'TipoMensaje': msg_type, 'CodComercio': company.l10n_uy_ucfe_commerce_code,
@@ -129,7 +129,6 @@ def _l10n_uy_ucfe_inbox_operation(self, msg_type, extra_req={}, return_transport
'Tout': '30000'}
if extra_req:
data.get('Req').update(extra_req)
-
res = company._uy_get_client(company.l10n_uy_ucfe_inbox_url, return_transport=return_transport)
client = res[0] if isinstance(res, tuple) else res
transport = res[1] if isinstance(res, tuple) else False
diff --git a/l10n_uy_edi/views/account_move_views.xml b/l10n_uy_edi/views/account_move_views.xml
index 69cea97a..1e78e89c 100644
--- a/l10n_uy_edi/views/account_move_views.xml
+++ b/l10n_uy_edi/views/account_move_views.xml
@@ -37,6 +37,7 @@
+
@@ -51,6 +52,7 @@
+