diff --git a/l10n_uy_edi_stock/data/ir_cron.xml b/l10n_uy_edi_stock/data/ir_cron.xml
new file mode 100644
index 00000000..6efb5a60
--- /dev/null
+++ b/l10n_uy_edi_stock/data/ir_cron.xml
@@ -0,0 +1,11 @@
+
+
+
+ UY: Update DGI Status - Pickings
+ 10
+ minutes
+
+ code
+ model._l10n_uy_edi_stock_cron_update_dgi_status()
+
+
diff --git a/l10n_uy_edi_stock/models/stock_picking.py b/l10n_uy_edi_stock/models/stock_picking.py
new file mode 100644
index 00000000..59d51148
--- /dev/null
+++ b/l10n_uy_edi_stock/models/stock_picking.py
@@ -0,0 +1,746 @@
+<<<<<<< HEAD
+||||||| MERGE BASE
+=======
+import base64
+import logging
+
+from lxml import etree
+from markupsafe import Markup
+from odoo import _, api, fields, models
+from odoo.addons.l10n_uy_edi.models.account_move import format_float
+from odoo.exceptions import UserError
+from odoo.tools import html2plaintext
+from odoo.tools.xml_utils import cleanup_xml_node
+
+_logger = logging.getLogger(__name__)
+
+
+class StockPicking(models.Model):
+ _name = "stock.picking"
+ _inherit = ["l10n.uy.cfe", "stock.picking"]
+
+ _inherit = "stock.picking"
+
+ # Need to make it work with document types
+ l10n_latam_document_type_id = fields.Many2one("l10n_latam.document.type", string="Document Type (UY)", copy=False)
+ l10n_latam_document_number = fields.Char(string="Document Number (UY)", readonly=True, copy=False)
+ l10n_latam_available_document_type_ids = fields.Many2many(
+ "l10n_latam.document.type",
+ compute="_compute_l10n_latam_available_document_types",
+ )
+
+ # Need to make it work with EDI (simil to what we have in invoices)
+ l10n_uy_edi_document_id = fields.Many2one("l10n_uy_edi.document", string="Uruguay E-Invoice CFE", copy=False)
+ l10n_uy_edi_cfe_uuid = fields.Char(related="l10n_uy_edi_document_id.uuid")
+ l10n_uy_edi_cfe_state = fields.Selection(related="l10n_uy_edi_document_id.state", store=True)
+ l10n_uy_edi_error = fields.Text(related="l10n_uy_edi_document_id.message")
+ l10n_uy_is_cfe = fields.Boolean(
+ compute="_compute_l10n_uy_is_cfe",
+ help="Campo tecnico para saber si es un comprobante electronico o no y usarlo en la vista para mostrar o requerir ciertos campos."
+ " por los momentos lo estamos usando solo para remitos pero podemos extenderlo para otros modelos",
+ )
+ l10n_uy_edi_addenda_ids = fields.Many2many(
+ "l10n_uy_edi.addenda",
+ string="Addenda & Disclosure",
+ domain="[('type', 'in', ['issuer', 'receiver', 'cfe_doc', 'addenda'])]",
+ help="Addendas and Mandatory Disclosure to add on the CFE. They can be added either to the issuer, receiver,"
+ " cfe doc additional info section or to the addenda section. However, the item type should not be set in"
+ " this field; instead, it should be specified in the invoice lines.",
+ ondelete="restrict",
+ )
+
+ # Solo Remito Exportacion
+ # l10n_uy_edi_cfe_sale_mode = fields.Selection([
+ # ("1", "General Regime"),
+ # ("2", "Consignment"),
+ # ("3", "Reviewable Price"),
+ # ("4", "Own goods to customs exclaves"),
+ # ("90", "General Regime - exportation of services"),
+ # ("99", "Other transactions"),
+ # ], "Sales Modality (EDI)",
+ # )
+ # l10n_uy_edi_cfe_transport_route = fields.Selection([
+ # ("1", "Maritime"),
+ # ("2", "Air"),
+ # ("3", "Ground"),
+ # ("8", "N/A"),
+ # ("9", "Other"),
+ # ], "Transportation Route",
+ # )
+
+ l10n_uy_edi_related_docs_ids = fields.Many2many(
+ "l10n_uy_edi.document",
+ string="Related Documents",
+ domain="[('picking_id', '!=', False), ('picking_id.partner_id', '=', partner_id), ('state', 'in', ['accepted', 'received', 'rejected'])]",
+ help="Related electronic documents",
+ )
+
+ l10n_uy_transfer_of_goods = fields.Selection(
+ [("1", "Venta"), ("2", "Traslados internos")],
+ string="Traslados de Bienes",
+ default=False,
+ copy=False,
+ )
+ l10n_uy_edi_place_of_delivery = fields.Char(
+ "Place of Delivery",
+ size=100,
+ help="CFE: Indication of where the merchandise is delivered or the service is provided"
+ " (Address, Branch, Port, etc.) if True then we will inform the shipping address's name and street",
+ )
+ edi_pdf_report_id = fields.Many2one(
+ comodel_name="ir.attachment",
+ string="PDF Attachment",
+ compute=lambda self: self._compute_linked_attachment_id("edi_pdf_report_id", "edi_pdf_report_file"),
+ depends=["edi_pdf_report_file"],
+ )
+ edi_pdf_report_file = fields.Binary(
+ attachment=True,
+ string="PDF File",
+ copy=False,
+ )
+ l10n_uy_edi_xml_attachment_id = fields.Many2one(
+ comodel_name="ir.attachment",
+ string="Uruguay E-Invoice XML",
+ compute="_compute_l10n_uy_edi_xml_attachment_id",
+ help="Uruguay: the most recent e-invoice XML returned by Uruware.",
+ )
+
+ l10n_uy_cfe_xml = fields.Text("Technical field to preview the xml")
+ manual_uruware_invoice = fields.Char()
+
+ # Onchange methods
+
+ @api.onchange("l10n_uy_transfer_of_goods")
+ def onchange_transfer_of_goods(self):
+ if self.l10n_uy_transfer_of_goods:
+ self.l10n_latam_document_type_id = self.l10n_latam_available_document_type_ids.filtered(
+ lambda x: x.code == "181"
+ ) # e-Delivery Guide Document
+ else:
+ self.l10n_latam_document_type_id = False
+
+ # Compute methods
+
+ @api.depends("l10n_uy_edi_document_id.state")
+ def _compute_l10n_uy_edi_xml_attachment_id(self):
+ for move in self:
+ doc = move.l10n_uy_edi_document_id
+ move.l10n_uy_edi_xml_attachment_id = doc.state in ["accepted", "received", "rejected"] and doc.attachment_id
+
+ @api.depends("l10n_latam_document_number")
+ def _compute_display_name(self):
+ """Display: 'Stock Picking Internal Sequence : Delivery Guide Number (if defined)'"""
+ super()._compute_display_name()
+ for picking in self.filtered(lambda x: x.l10n_latam_document_number):
+ picking.display_name = picking.name + ": (%s %s)" % (
+ picking.l10n_latam_document_type_id.doc_code_prefix,
+ picking.l10n_latam_document_number,
+ )
+
+ def _compute_l10n_uy_is_cfe(self):
+ for rec in self:
+ rec.l10n_uy_is_cfe = False
+ if (
+ rec.country_code == "UY"
+ and rec.picking_type_code == "outgoing"
+ and rec.l10n_latam_document_type_id.code in ["124", "181", "224", "281"]
+ ):
+ rec.l10n_uy_is_cfe = True
+
+ @api.depends("partner_id", "company_id", "picking_type_code")
+ def _compute_l10n_latam_available_document_types(self):
+ uy_pickings = self.filtered(lambda x: x.country_code == "UY" and x.picking_type_code == "outgoing")
+ uy_pickings.l10n_latam_available_document_type_ids = self.env["l10n_latam.document.type"].search(
+ self._get_l10n_latam_documents_domain()
+ )
+ (self - uy_pickings).l10n_latam_available_document_type_ids = False
+
+ def _compute_linked_attachment_id(self, attachment_field, binary_field):
+ """Helper to retreive Attachment from Binary fields
+ This is needed because fields.Many2one('ir.attachment') makes all
+ attachments available to the user.
+ """
+ attachments = self.env["ir.attachment"].search(
+ [
+ ("res_model", "=", self._name),
+ ("res_id", "in", self.ids),
+ ("res_field", "=", binary_field),
+ ]
+ )
+ move_vals = {att.res_id: att for att in attachments}
+ for move in self:
+ move[attachment_field] = move_vals.get(move._origin.id, False)
+
+ # Buttons
+
+ def action_cancel(self):
+ """El remito no puede ser modificado una vez que ya fue aceptado por DGI"""
+ if self.filtered(
+ lambda x: x.l10n_uy_is_cfe and x.l10n_uy_edi_cfe_state in ["accepted", "rejected", "received"]
+ ):
+ raise UserError(_("Can not cancel a Delivery Guide already process by DGI"))
+ return super().action_cancel()
+
+ def l10n_uy_edi_action_get_dgi_state(self):
+ self.l10n_uy_edi_document_id.action_update_dgi_state()
+ rejected_pickings = self.filtered(lambda x: x.l10n_uy_edi_cfe_state == "rejected")
+ if rejected_pickings:
+ _logger.info(
+ "Rejected picking(s): %s",
+ [(rec.id, rec.name) for rec in rejected_pickings],
+ )
+ for picking in rejected_pickings:
+ picking.message_post(
+ body=self.env._(
+ "The CFE has been rejected by DGI. Please, manually check the reject reason "
+ "and generate/send a new CFE with the fixes"
+ ),
+ message_type="comment",
+ subtype_xmlid="mail.mt_note",
+ )
+
+ def l10n_uy_edi_send_dgi(self):
+ """El E-remito tiene las siguientes partes en el xml
+ A. Encabezado
+ B. Detalle de los productos
+ C. Subtotales Informativos (opcional)
+ F. Informacion de Referencia (condicional)
+ """
+ # Filtrar solo los e-remitos
+ uy_delivery_guides = self.filtered(
+ lambda x: (
+ x.country_code == "UY"
+ and x.picking_type_code == "outgoing"
+ and x.l10n_latam_document_type_id
+ and int(x.l10n_latam_document_type_id.code) > 0
+ and x.l10n_uy_edi_cfe_state not in ["accepted", "rejected", "received"]
+ )
+ )
+
+ # If the invoice was previously validated in Uruware and need to be link to Odoo
+ # we check that the l10n_uy_edi_cfe_uuid has been manually set and we consult to get the invoice information from Uruware
+ pre_validated_in_uruware = uy_delivery_guides.filtered(
+ lambda x: (
+ x.l10n_uy_edi_cfe_uuid and not x.l10n_uy_edi_document_id.attachment_id and not x.l10n_uy_edi_cfe_state
+ )
+ )
+ if pre_validated_in_uruware:
+ pre_validated_in_uruware.uy_stock_action_get_uruware_cfe()
+ uy_delivery_guides = uy_delivery_guides - pre_validated_in_uruware
+
+ if not uy_delivery_guides:
+ return
+
+ uy_delivery_guides.mapped("company_id")._check_env()
+
+ # Send invoices to DGI and get the return info
+ msg = ""
+ for picking in uy_delivery_guides:
+ edi_doc = self.env["l10n_uy_edi.document"].create(
+ {
+ "picking_id": picking.id,
+ "uuid": self.env["l10n_uy_edi.document"]._get_uuid(picking),
+ }
+ )
+ picking.l10n_uy_edi_document_id = edi_doc
+
+ if picking.company_id.l10n_uy_edi_ucfe_env == "demo":
+ attachments = picking._l10n_uy_edi_dummy_validation()
+ msg = _(
+ "This CFE has been generated in DEMO Mode. It is considered"
+ ' as accepted and it won"t be sent to DGI.'
+ )
+ else:
+ request_data = picking._l10n_uy_stock_prepare_req_data()
+ result = edi_doc._send_dgi(request_data)
+ edi_doc._update_cfe_state(result)
+
+ response = result.get("response")
+
+ if edi_doc.message:
+ picking.message_post(
+ body=Markup("{}: {}").format(
+ ("ERROR"), edi_doc.message
+ )
+ )
+ elif edi_doc.state in ["received", "accepted"]:
+ # If everything is ok we save the return information
+ picking.l10n_latam_document_number = response.findtext(".//{*}Serie") + "%07d" % int(
+ response.findtext(".//{*}NumeroCfe")
+ )
+
+ msg = response.findtext(".//{*}MensajeRta", "")
+ msg += _("The electronic invoice was created successfully")
+
+ if response is not None:
+ attachments = picking._l10n_uy_stock_update_xml_and_pdf_file(response)
+
+ picking.with_context(no_new_invoice=True).message_post(
+ body=msg, attachment_ids=attachments.ids if attachments else False
+ )
+
+ def uy_stock_action_get_pdf(self):
+ """Permite volver a generar el PDF cuando no existe, sea que hubo error
+ porque no se creo o alguien lo borro sin querer"""
+ self.ensure_one()
+ self.edi_pdf_report_id.res_field = False
+ result = self._l10n_uy_edi_get_pdf()
+ if pdf_file := result.get("pdf_file"):
+ pdf_file.register_as_main_attachment()
+ self.invalidate_recordset(fnames=["edi_pdf_report_id", "edi_pdf_report_file"])
+
+ def l10n_uy_edi_action_download_preview_xml(self):
+ if self.l10n_uy_edi_document_id.attachment_id:
+ return self.l10n_uy_edi_document_id.action_download_file()
+
+ # Helpers
+
+ def _get_l10n_latam_documents_domain(self):
+ """return domain"""
+ codes = self._l10n_uy_get_delivery_guide_codes()
+ return [
+ ("code", "in", codes),
+ ("code", "!=", "0"),
+ ("active", "=", True),
+ ("internal_type", "=", "stock_picking"),
+ ]
+
+ def _l10n_uy_stock_update_xml_and_pdf_file(self, response):
+ """Clean up the pdf and xml fields. Create new ones with the response"""
+ self.ensure_one()
+ res_files = self.env["ir.attachment"]
+ edi_doc = self.l10n_uy_edi_document_id
+
+ # TODO KZ this should not necessary :(
+ edi_doc._compute_from_origin()
+
+ self.edi_pdf_report_id.res_field = False
+ edi_doc.attachment_id.res_field = False
+
+ xml_content = response.findtext(".//{*}XmlCfeFirmado")
+ if xml_content:
+ res_files = self.env["ir.attachment"].create(
+ {
+ "res_model": "l10n_uy_edi.document",
+ "res_field": "attachment_file",
+ "res_id": edi_doc.id,
+ "name": edi_doc._get_xml_attachment_name(),
+ "type": "binary",
+ "datas": base64.b64encode(
+ xml_content.encode()
+ if self.l10n_uy_edi_cfe_state in ["received", "accepted"]
+ else self._l10n_uy_stock_get_xml_content().encode()
+ ),
+ }
+ )
+
+ edi_doc.invalidate_recordset(["attachment_id", "attachment_file"])
+
+ # If the record has been posted automatically print and attach the legal report to the record.
+ if self.l10n_uy_edi_cfe_state and self.l10n_uy_edi_cfe_state != "error":
+ pdf_result = self._l10n_uy_edi_get_pdf()
+ if pdf_file := pdf_result.get("pdf_file"):
+ # make sure latest PDF shows to the right of the chatter
+ pdf_file.register_as_main_attachment(force=True)
+ self.invalidate_recordset(fnames=["edi_pdf_report_id", "edi_pdf_report_file"])
+ res_files |= pdf_file
+ else:
+ self._l10n_uy_edi_get_preview_xml()
+ return res_files
+
+ def _l10n_uy_edi_dummy_validation(self):
+ # COPY l10n_uy_edi (only change move_id with picking_id)
+ """When we want to skip DGI and validate only in Odoo"""
+ edi_doc = self.l10n_uy_edi_document_id
+ edi_doc.state = "accepted"
+ self.write(
+ {
+ "l10n_latam_document_number": "DE%07d" % (edi_doc.picking_id.id),
+ # "ref": "*DEMO",
+ }
+ )
+
+ return self._l10n_uy_edi_get_preview_xml()
+
+ def _l10n_uy_edi_get_preview_xml(self):
+ # COPY l10n_uy_edi
+ self.ensure_one()
+ edi_doc = self.l10n_uy_edi_document_id
+ edi_doc.attachment_id.res_field = False
+ xml_file = self.env["ir.attachment"].create(
+ {
+ "res_model": "l10n_uy_edi.document",
+ "res_field": "attachment_file",
+ "res_id": edi_doc.id,
+ "name": edi_doc._get_xml_attachment_name(),
+ "type": "binary",
+ "datas": base64.b64encode(self._l10n_uy_stock_get_xml_content().encode()),
+ }
+ )
+ edi_doc.invalidate_recordset(["attachment_id", "attachment_file"])
+ return xml_file
+
+ def _l10n_uy_edi_get_pdf(self):
+ """Call endpoint to get PDF file from Uruware (Standard Representation)
+ return: dictionary with {"errors": str(): "pdf_file"attachment object }"""
+ res = {}
+ result = self.l10n_uy_edi_document_id._get_pdf()
+ if file_content := result.get("file_content"):
+ pdf_file = self.env["ir.attachment"].create(
+ {
+ "res_model": "stock.picking",
+ "res_id": self.id,
+ "res_field": "edi_pdf_report_file",
+ "name": self.l10n_uy_edi_document_id._get_xml_attachment_name().replace(".xml", ".pdf"),
+ "type": "binary",
+ "datas": file_content,
+ }
+ )
+ res["pdf_file"] = pdf_file
+
+ if errors := result.get("errors"):
+ msg = _("Error getting the PDF file: %s", errors)
+ self.l10n_uy_edi_error = (self.l10n_uy_edi_error or "") + msg
+ self.message_post(body=msg)
+
+ return res
+
+ def _l10n_uy_edi_get_addenda(self):
+ """return string with the addenda of the remito"""
+ addenda = self.l10n_uy_edi_document_id._get_legends("addenda", self)
+ if self.origin:
+ addenda += "\n\nOrigin: %s" % self.origin
+ if self.note:
+ addenda += "\n\n%s" % html2plaintext(self.note)
+ return addenda.strip()
+
+ def _l10n_uy_get_delivery_guide_codes(self):
+ """return list of the available document type codes for uruguayan of stock picking"""
+ # self.ensure_one()
+ # if self.picking_type_code != 'outgoing':
+ # return []
+ return ["0", "181"]
+
+ # XML prepapre values
+
+ def _l10n_uy_stock_prepare_req_data(self):
+ """Creating dictionary with the request to generate a DGI EDI document"""
+ self.ensure_one()
+ edi_doc = self.l10n_uy_edi_document_id
+ xml_content = self._l10n_uy_stock_get_xml_content()
+ req_data = {
+ "Uuid": edi_doc.uuid,
+ "TipoCfe": int(self.l10n_latam_document_type_id.code),
+ "HoraReq": edi_doc.request_datetime.strftime("%H%M%S"),
+ "FechaReq": edi_doc.request_datetime.date().strftime("%Y%m%d"),
+ "CfeXmlOTexto": xml_content,
+ }
+
+ if addenda := self._l10n_uy_edi_get_addenda():
+ req_data["Adenda"] = addenda
+ return req_data
+
+ def _uy_get_cfe_lines(self):
+ self.ensure_one()
+ # Cuando está validado el remito siempre usa move_line_ids
+ # Usamos move_ids_without_package para Stock moves "not in package" (stock.move)
+ # move_line_ids para Operations (stock.move.line)
+ # move_line_ids_without_package para Operations "without package" (stock.move.line)
+ return self.move_line_ids
+
+ def _l10n_uy_stock_get_xml_content(self):
+ """Create the CFE xml structure and validate it
+ :return: string the xml content to send to DGI"""
+ self.ensure_one()
+ template_name = "l10n_uy_edi_stock." + self.l10n_uy_edi_document_id._get_cfe_tag(self) + "_template"
+ values = {
+ "cfe": self,
+ "res_model": self._name,
+ "IdDoc": self._l10n_uy_stock_cfe_A_iddoc(),
+ "emisor": self._l10n_uy_stock_cfe_A_issuer(),
+ "receptor": self._l10n_uy_stock_cfe_A_receptor(),
+ "totals_detail": self._l10n_uy_stock_cfe_A_totals(),
+ "item_detail": self._l10n_uy_stock_cfe_B_details(),
+ "referencia_lines": self._l10n_uy_edi_cfe_F_reference(),
+ "format_float": format_float,
+ }
+ cfe = self.env["ir.qweb"]._render(template_name, values=values)
+ return etree.tostring(cleanup_xml_node(cfe)).decode()
+
+ def _l10n_uy_stock_cfe_A_iddoc(self):
+ """XML Section A (Encabezado)"""
+ values = {
+ "TipoCFE": self.l10n_latam_document_type_id.code,
+ "FchEmis": self.scheduled_date.date(),
+ "TipoTraslado": self.l10n_uy_transfer_of_goods, # A5
+ }
+
+ # Solo para Remito Exportacion
+ # if self.l10n_uy_edi_document_id._is_uy_remito_exp():
+ # values.update({
+ # "ModVenta": self.l10n_uy_edi_cfe_sale_mode or None, # A14
+ # "ViaTransp": self.l10n_uy_edi_cfe_transport_route or None, # A15
+ # })
+
+ empty_values = {}.fromkeys(
+ [
+ "MntBruto",
+ "FmaPago",
+ "FchVenc",
+ "ClauVenta",
+ "InfoAdicionalDoc",
+ "ModVenta",
+ "ViaTransp",
+ ],
+ None,
+ )
+ values.update(empty_values)
+ return values
+
+ def _l10n_uy_stock_cfe_A_issuer(self):
+ return {
+ "RUCEmisor": self.company_id.vat,
+ "RznSoc": self.company_id.name[:150],
+ "CdgDGISucur": self.company_id.l10n_uy_edi_branch_code,
+ "DomFiscal": self.company_id.partner_id._l10n_uy_edi_get_fiscal_address(),
+ "Ciudad": (self.company_id.city or "")[:30] or None,
+ "Departamento": (self.company_id.state_id.name or "")[:30] or None,
+ "InfoAdicionalEmisor": self.l10n_uy_edi_document_id._get_legends("issuer", self) or None,
+ }
+
+ def _l10n_uy_stock_cfe_A_receptor(self):
+ """XML Section A (Encabezado / Receptor)"""
+ self.ensure_one()
+ doc_type = self.partner_id._l10n_uy_edi_get_doc_type()
+ values = {
+ "TipoDocRecep": doc_type or None, # A60
+ "CodPaisRecep": self.partner_id.country_id.code or ("UY" if doc_type in [2, 3] else "99"), # A61
+ "DocRecep": self.partner_id.vat if doc_type in [1, 2, 3] else None, # A62
+ "DocRecepExt": self.partner_id.vat if doc_type not in [1, 2, 3] else None, # A62.1
+ "RznSocRecep": self.partner_id.name[:150] or None, # A63
+ "DirRecep": self.partner_id._l10n_uy_edi_get_fiscal_address() or None, # A64
+ "CiudadRecep": self.partner_id.city and self.partner_id.city[:30] or None, # A65
+ "DeptoRecep": self.partner_id.state_id and self.partner_id.state_id.name[:30] or None, # A66
+ "PaisRecep": self.partner_id.country_id and self.partner_id.country_id.name or None, # A66.1
+ "InfoAdicional": self.l10n_uy_edi_document_id._get_legends("receiver", self) or None, # A68
+ "LugarDestEnt": self.l10n_uy_edi_place_of_delivery or None, # A69
+ }
+ empty_values = {}.fromkeys(["CompraID"], None)
+ values.update(empty_values)
+ return values
+
+ def _l10n_uy_stock_cfe_A_totals(self):
+ """XML Section C (SUBTOTALES INFORMATIVOS)"""
+ self.ensure_one()
+ lines = self._uy_get_cfe_lines()
+ res = {
+ "CantLinDet": len(lines), # A126
+ }
+
+ # currency_name = self.company_id.currency_id.name if self.company_id.currency_id else None
+ # Solo para Remito Exportacion
+ # if self.l10n_uy_edi_document_id._is_uy_remito_exp():
+ # values.update({
+ # "MntExpoyAsim": sum(self.move_line_ids.mapped('quantity')) or None,
+ # "TpoMoneda": currency_name if not self.l10n_latam_document_type_id.code == '181' else None, # A110
+ # 'TpoCambio': None if currency_name == "UYU" else self._l10n_uy_edi_get_used_rate() or None, # A111
+ # })
+
+ empty_values = {}.fromkeys(
+ [
+ "MntNoGrv",
+ "MntNetoIvaTasaMin",
+ "MntNetoIVATasaBasica",
+ "MntNetoIVAOtra",
+ "IVATasaMin",
+ "IVATasaBasica",
+ "MntIVATasaMin",
+ "MntIVATasaBasica",
+ "MntIVAOtra",
+ "MntTotal",
+ "MontoNF",
+ "MntPagar",
+ "TpoMoneda",
+ "TpoCambio",
+ "MntExpoyAsim",
+ ],
+ None,
+ )
+ res.update(empty_values)
+ return res
+
+ def _l10n_uy_stock_cfe_B_details(self):
+ self.ensure_one()
+ res = []
+
+ # Solo Remito de Exportacion
+ # if self.l10n_uy_edi_document_id._is_uy_remito_exp():
+ # invoice_ind = 10 # For B4
+
+ for k, line in enumerate(self.move_line_ids, start=1):
+ temp = {
+ "NroLinDet": k, # B1
+ "IndFact": None, # B4
+ "NomItem": line.display_name, # B7
+ "DscItem": line.description_picking
+ if line.description_picking and line.description_picking != line.display_name
+ else None, # B8
+ "Cantidad": line.quantity, # B9
+ "UniMed": line.product_uom_id.name[:4] if line.product_uom_id else "N/A", # B10
+ # "PrecioUnitario": line.price_unit, # B11 como encuentro el precio unitario para facturas de expo ?
+ # "MontoItem": line.price_total if tax_included else line.price_subtotal, # B24 como encuentro el precio unitario para facturas de expo ?
+ }
+ empty_values = {}.fromkeys(
+ [
+ "PrecioUnitario",
+ "DescuentoPct",
+ "DescuentoMonto",
+ "MontoItem",
+ ],
+ None,
+ )
+ temp.update(empty_values)
+ res.append(temp)
+
+ return res
+
+ def _l10n_uy_edi_cfe_F_reference(self):
+ """XML Section F (REFERENCE INFORMATION)"""
+ self.ensure_one()
+ res = []
+ related_docs = self.l10n_uy_edi_related_docs_ids
+ for k, related_cfe in enumerate(related_docs, 1):
+ cfe_serie, cfe_number = self.l10n_uy_edi_document_id._get_doc_parts(related_cfe)
+ res.append(
+ {
+ "NroLinRef": k, # F1
+ "TpoDocRef": int(related_cfe.l10n_latam_document_type_id.code), # F3
+ "Serie": cfe_serie, # F4
+ "NroCFERef": cfe_number, # F5
+ }
+ )
+ return res
+
+ # TODO need to re adapt
+
+ def uy_stock_action_preview_xml(self):
+ """En odoo oficial solo permite descargar el preview del xml si estamos en demo mode o si ocurrio un error.
+
+ Este es un nuevo boton preview que permite pre visualizar el contenido del xml en cualquier momento, incluso
+ cuando la factura aun esta en estado borrador."""
+ self.l10n_uy_cfe_xml = self._l10n_uy_stock_get_xml_content().encode()
+
+ def uy_stock_action_validate_cfe(self):
+ """Check CFE XML valid files: 350: Validación de estructura de CFE
+
+ To make the validation of the CFE and connect to uwaure we need to have a EDI document
+ For that reason if we have one we delete it and create a new one with the result of
+ the validation, since we are raising and the end of the method then the edi document
+ is rolled back"""
+ self.ensure_one()
+
+ self.l10n_uy_edi_document_id.unlink()
+ edi_doc = self.env["l10n_uy_edi.document"].create(
+ {
+ "picking_id": self.id,
+ "uuid": self.env["l10n_uy_edi.document"]._get_uuid(self),
+ }
+ )
+ self.l10n_uy_edi_document_id = edi_doc
+
+ result = edi_doc._ucfe_inbox("350", {"CfeXmlOTexto": self.l10n_uy_cfe_xml})
+ response = result.get("response")
+ if response is not None:
+ cod_rta = response.findtext(".//{*}CodRta")
+ if cod_rta != "00":
+ edi_doc._update_cfe_state(result)
+ edi_doc.message = _("Error creating CFẸ XML") + "\n\n" + edi_doc.message
+ raise UserError(
+ _(
+ "Error creating CFẸ XML\n\n %(errors)s",
+ errors=response.findtext(".//{*}MensajeRta"),
+ )
+ )
+
+ raise UserError(_("XML Valido"))
+
+ # def _l10n_uy_edi_get_used_rate(self):
+ # # COPY l10n_uy_edi
+ # self.ensure_one()
+ # # We need to use abs to avoid error on Credit Notes (amount_total_signed is negative)
+ # return abs(self.amount_total_signed) / self.amount_total
+
+ def uy_stock_action_get_uruware_cfe(self):
+ """Boton visible en la solapa DGI que permite con el dato del UUID cargar el remito creado en
+ Uruware postmorten en el Odoo
+
+ (INBOX 360 - Consulta de estado de CFE).
+
+ Los datos que sincroniza son
+
+ * numero de documento
+ * tipo de documento
+ * estado del comprobante
+ - crea el EDI document
+ - agregar el pdf de la factura
+ """
+
+ # Filtrar solo los e-remitos
+ uy_pickings = self.filtered(
+ lambda x: (
+ x.country_code == "UY"
+ and x.picking_type_code == "outgoing"
+ and x.l10n_latam_document_type_id
+ and int(x.l10n_latam_document_type_id.code) > 0
+ and x.l10n_uy_edi_cfe_state not in ["accepted", "rejected", "received"]
+ )
+ )
+
+ for picking in uy_pickings:
+ if not picking.manual_uruware_invoice:
+ raise UserError(_("You need to define 'CFE Key or UUID' in order to continue"))
+ edi_doc = self.env["l10n_uy_edi.document"].create(
+ {
+ "picking_id": picking.id,
+ "uuid": self.manual_uruware_invoice,
+ }
+ )
+ picking.l10n_uy_edi_document_id = edi_doc
+ result = edi_doc._ucfe_inbox("360", {"Uuid": edi_doc.uuid})
+ edi_doc._update_cfe_state(result)
+ response = result.get("response")
+ # EL response no me trae los datos que necesito, solamente me trajo
+ # Created: 2024-11-28T01:08:09.899Z
+ # Expires: 2024-11-28T01:13:09.899Z
+ # ErrorCode: 0
+ # CodComercio: Sumila-8485
+ # CodRta: 11
+ # CodTerminal: FC-8485
+ # FechaFirma: 2024-11-27T22:03:30.0000000-03:00
+ # TipoMensaje: 361
+ # Uuid: C2753E92-A51C-404F-A91F-5C3875862402
+ if response is not None:
+ uy_doc_code = response.findtext(".//{*}TipoCfe")
+ serie = response.findtext(".//{*}Serie")
+ doc_number = response.findtext(".//{*}NumeroCfe")
+ picking.write(
+ {
+ "l10n_latam_document_number": serie + "%07d" % int(doc_number),
+ "l10n_latam_document_type_id": picking.filtered(lambda x: x.code == uy_doc_code).id,
+ }
+ )
+ picking.uy_stock_action_get_pdf()
+
+ def _l10n_uy_edi_stock_cron_update_dgi_status(self, batch_size=10):
+ """Cron to update the DGI status of the stock pickings"""
+ domain = [
+ ("l10n_uy_is_cfe", "=", True),
+ ("l10n_uy_edi_cfe_state", "=", "received"),
+ ]
+ pickings = self.env["stock.picking"].search(domain, limit=batch_size, order="id")
+ pickings.l10n_uy_edi_action_get_dgi_state()
+
+ if self.env["stock.picking"].search_count(domain) > batch_size:
+ self.env.ref("l10n_uy_edi_stock.ir_cron_update_dgi_state_pickings")._trigger()
+
+>>>>>>> FORWARD PORTED
diff --git a/l10n_uy_edi_stock_ux/__manifest__.py b/l10n_uy_edi_stock_ux/__manifest__.py
index 3c25bd7a..1a5365b7 100644
--- a/l10n_uy_edi_stock_ux/__manifest__.py
+++ b/l10n_uy_edi_stock_ux/__manifest__.py
@@ -10,6 +10,15 @@
"l10n_uy_edi_stock",
],
"data": [
+<<<<<<< a2e5c2082c3efa885b159b92b315e0cf502518d1:l10n_uy_edi_stock_ux/__manifest__.py
+||||||| 7ba4a02d115dbb651b9f8ae98feb8bdfcbca81ce:l10n_uy_edi_stock/__manifest__.py
+ "data/l10n_latam.document.type.csv",
+ "views/cfe_template.xml",
+=======
+ "data/ir_cron.xml",
+ "data/l10n_latam.document.type.csv",
+ "views/cfe_template.xml",
+>>>>>>> 7a54e8e88b0e77cc2b1be3aa71555859d960cde1:l10n_uy_edi_stock/__manifest__.py
"views/stock_picking_views.xml",
"views/l10n_uy_edi_document_views.xml",
],