Skip to content

Commit

Permalink
wip3 limpieza
Browse files Browse the repository at this point in the history
  • Loading branch information
zaoral authored and mem-adhoc committed Nov 6, 2024
1 parent aa03969 commit b6dc67e
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 160 deletions.
5 changes: 3 additions & 2 deletions l10n_uy_edi_stock/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
'sequence': 12,
'author': 'Adhoc',
'depends': [
'l10n_uy_edi',
# 'l10n_uy_ux',
'stock_account',
# 'sale_stock',
'sale_stock',
'l10n_uy_edi'
],
'data': [
'data/l10n_latam.document.type.csv',
Expand Down
2 changes: 1 addition & 1 deletion l10n_uy_edi_stock/models/l10n_latam_document_type.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from odoo import fields, models


class L10nAccountDocumentType(models.Model):
class L10nLatamDocumentType(models.Model):

_inherit = 'l10n_latam.document.type'

Expand Down
22 changes: 0 additions & 22 deletions l10n_uy_edi_stock/models/l10n_uy_edi_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ def _is_uy_remito_exp(self):
def _is_uy_remito_loc(self):
return self.l10n_latam_document_type_id.code == '181'


def _is_uy_remito_type_cfe(self):
return self.l10n_latam_document_type_id.internal_type in ['stock_picking']

Expand Down Expand Up @@ -67,9 +66,6 @@ def _uy_cfe_B4_IndFact(self, line):

def _uy_cfe_B8_DscItem(self, line):
""" B8 Descripcion Adicional del ítem. Maximo 1000 caracteres """
if self._is_uy_inv_type_cfe():
return super()._uy_cfe_B8_DscItem(line)

self.ensure_one()
res = []
for rec in line.l10n_uy_edi_addenda_ids:
Expand Down Expand Up @@ -98,24 +94,6 @@ def _uy_cfe_A_receptor(self):

return res

def _uy_cfe_C_totals(self):
self.ensure_one()
res = super()._uy_cfe_C_totals()

# A110 Tipo moneda transacción: Informar para todos menos para e-Rem loc
if self._is_uy_remito_loc():
res.pop('TpoMoneda')

# A124 Total Monto Total (NUM 17)
# - Si tipo de CFE= 124 (e-remito de exportación), Valor numérico de 15 enteros y 2 decimales:
# - sino, Valor numérico de 15 enteros y 2 decimales, ≥0 : C124 = SUM(C112:C118) + SUM(C121:C123)

# A111 Tipo de Cambio: Informar siempre que la moneda sea diferente al peso Uruguayo y no sea e-Rem Loc
if self._is_uy_remito_loc():
res.pop('TpoCambio')

return res

def _uy_get_cfe_tag(self):
self.ensure_one()
if self._is_uy_remito_loc():
Expand Down
244 changes: 150 additions & 94 deletions l10n_uy_edi_stock/models/stock_picking.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,72 @@
import base64

from odoo import api, models, fields, _

from odoo.exceptions import UserError
from odoo.tools import html2plaintext


class StockPicking(models.Model):

_inherit = 'stock.picking'

l10n_uy_cfe_id = fields.Many2one("l10n_uy_edi.document", string="Uruguay E-Resguardo CFE", copy=False)
# 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, states={'draft': [('readonly', False)]}, 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')

# Fields that need to be fill before creating the CFE
l10n_uy_edi_cfe_uuid = fields.Char(
'Key or UUID CFE', help="Unique identification per CFE in UCFE. Currently is formed by the concatenation of model name initials plust record id", copy=False)
# Need to make it work with EDI (simil to what we have in account.move)
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'])]")

l10n_latam_available_document_type_ids = fields.Many2many('l10n_latam.document.type', compute='_compute_l10n_latam_available_document_types')
"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.")
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", help="This field is used in the XML to create an Export e-Invoice")
l10n_uy_edi_cfe_transport_route = fields.Selection([
("1", "Maritime"),
("2", "Air"),
("3", "Ground"),
("8", "N/A"),
("9", "Other"),
], "Transportation Route", help="This field is used in the XML to create an Export e-Invoice")

# New fields only for pickings
l10n_uy_transfer_of_goods = fields.Selection(
[('1', 'Venta'), ('2', 'Traslados internos')],
[('1', 'Venta'),
('2', 'Traslados internos')],
string="Traslados de Bienes",
default='1'
)

l10n_uy_cfe_sale_mod = 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', help="This field is used in the XML to create an Export e-Delivery Guide")
l10n_uy_cfe_transport_route = fields.Selection([
('1', 'Maritime'),
('2', 'Air'),
('3', 'Ground'),
('8', 'N/A'),
('9', 'Other'),
], 'Transportation Route', help="This field is used in the XML to create an Export e-Delivery Guide")
l10n_uy_place_of_delivery = fields.Char(
l10n_uy_edi_place_of_delivery = fields.Char(
"Place of Delivery",
size=100,
help="Indicación de donde se entrega la mercadería o se presta el servicio (Dirección, Sucursal, Puerto, etc,)")
l10n_uy_edi_place_of_delivery = fields.Boolean(
"Place of Delivery",
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")
" (Address, Branch, Port, etc.) if True then we will inform the shipping address's name and street"
)

# TODO KZ campo similar a lo que tenemos en el UX, revisar como se llama y usarlo igual
l10n_uy_cfe_xml = fields.Text()

def name_get(self):
""" Display: 'Stock Picking Internal Sequence : Remito (if defined)' """
""" Display: 'Stock Picking Internal Sequence : Remito Number (if defined)' """
res = []
for rec in self:
if rec.l10n_latam_document_number:
Expand All @@ -58,31 +76,31 @@ def name_get(self):
res.append((rec.id, name))
return res

def _compute_l10n_uy_is_cfe(self):
self.l10n_uy_is_cfe = False
if self.l10n_uy_edi_document_id.move_id.journal_id.l10n_uy_edi_type == 'electronic':
self.l10n_uy_is_cfe = True

@api.depends('partner_id', 'company_id', 'picking_type_code')
def _compute_l10n_latam_available_document_types(self):
uy_remitos = self.filtered(lambda x: x.country_code == 'UY' and x.picking_type_code == 'outgoing')

uy_remitos.l10n_latam_available_document_type_ids = self.env['l10n_latam.document.type'].search(
self._get_l10n_latam_documents_domain())
(self - uy_remitos).l10n_latam_available_document_type_ids = False

def _get_l10n_latam_documents_domain(self):
""" return domain """
codes = self._l10n_uy_get_remito_codes()
return [('code', 'in', codes), ('active', '=', True), ('internal_type', '=', 'stock_picking')]

# TODO KZ evaluar si estaria bueno tener un boolean como este l10n_cl_draft_status
# TODO KZ evaluar si agregar una constrains de unicidad para remitos, aplicaria para:
# 1. remitos manual o preimpresos (no electronico),
# 2. remitos generados en uruware y pasados a mano luego a oodo
# 3. remitos de proveedor? no se si los necesitamos registrar

def action_cancel(self):
# The move cannot be modified once the CFE has been accepted by the DGI
""" El remito no puede ser modificado una vez que ya fue aceptado por DGI """
remitos = self.filtered(lambda x: x.country_code == 'UY' and x.picking_type_code == 'outgoing')
remitos._uy_check_state()
if remitos.filtered(lambda x: x.l10n_uy_edi_cfe_state in ['accepted', 'rejected', 'received']):
raise UserError(_('Can not cancel a Remito already process by DGI'))
return super().action_cancel()

def uy_post_dgi_remito(self):
def l10n_uy_edi_stock_post_dgi(self):
""" El E-remito tiene las siguientes partes en el xml
A. Encabezado
B. Detalle de los productos
Expand All @@ -91,15 +109,16 @@ def uy_post_dgi_remito(self):
"""
# Filtrar solo los e-remitos
uy_remitos = self.filtered(
lambda x: x.country_code == 'UY' and x.picking_type_code == 'outgoing'
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 previosly 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_remitos.filtered(lambda x: x.l10n_uy_edi_cfe_uuid and not x.l10n_uy_cfe_file and not x.l10n_uy_edi_cfe_state)
# 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_remitos.filtered(lambda x: x.l10n_uy_edi_cfe_uuid and not x.attachment_id and not x.l10n_uy_edi_cfe_state)
if pre_validated_in_uruware:
pre_validated_in_uruware.uy_ux_action_get_uruware_cfe()
uy_remitos = uy_remitos - pre_validated_in_uruware
Expand All @@ -110,38 +129,99 @@ def uy_post_dgi_remito(self):
# Send invoices to DGI and get the return info
for remito in uy_remitos:
if remito.company_id.l10n_uy_edi_ucfe_env == "demo":
remito._uy_dummy_validation()
continue

# TODO KZ I think we can avoid this loop. review
remito._uy_dgi_post()
attachments = remito._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:
remito._uy_dgi_post()
msg += _("The electronic invoice was created successfully")

remito.with_context(no_new_invoice=True).message_post(
body=msg, attachment_ids=attachments.ids if attachments else False)

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_edi_get_xml_content().encode()),
})
edi_doc.invalidate_recordset(["attachment_id", "attachment_file"])
return xml_file

def _l10n_uy_edi_get_xml_content(self):
# COPY l10n_uy_edi
""" Create the CFE xml structure and validate it
:return: string the xml content to send to DGI """
self.ensure_one()

# TODO KZ buscar el metodo _l10n_cl_get_tax_amounts para ejemplos de como extraer la info de los impuestos en un picking. viene siempre de una
# factura
values = {
"cfe": self,
"IdDoc": self._l10n_uy_edi_cfe_A_iddoc(),
"emisor": self._l10n_uy_edi_cfe_A_issuer(),
"receptor": self._l10n_uy_edi_cfe_A_receptor(),
"item_detail": self._l10n_uy_edi_cfe_B_details(),
"totals_detail": self._l10n_uy_edi_cfe_C_totals(),
"referencia_lines": self._l10n_uy_edi_cfe_F_reference(),
"format_float": format_float,
}
cfe = self.env["ir.qweb"]._render(
"l10n_uy_edi." + self.l10n_uy_edi_document_id._get_cfe_tag(self) + "_template", values)
return etree.tostring(cleanup_xml_node(cfe)).decode()

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 _uy_get_cfe_addenda(self):
""" Add Specific MOVE model fields to the CFE Addenda if they are set:
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

* field Origin added with the prefix "Origin: ..."
* Observation
"""
def _l10n_uy_edi_cfe_C_totals(self):
self.ensure_one()
res = super()._uy_get_cfe_addenda()
if self.origin:
res += "\n\nOrigin: %s" % self.origin
if self.note:
res += "\n\n%s" % html2plaintext(self.note)
return res.strip()
currency_name = self.currency_id.name if self.currency_id else self.company_id.currency_id.name
lines = self._uy_get_cfe_lines()
res = {
'TpoMoneda': currency_name if not self._is_uy_remito_loc() else None, # A110
'TpoCambio': None if currency_name == "UYU" else self._l10n_uy_edi_get_used_rate() or None, # A111
'CantLinDet': len(lines), # A126
}
return res

def _uy_get_cfe_lines(self):
self.ensure_one()
if self._is_uy_remito_type_cfe():
# TODO KZ: Toca revisar realmente cual es el line que corresponde, el que veo en la interfaz parece ser move_ids_without_package pero no se si esto siempre aplica
# TODO KZ: Toca revisar realmente cual es el line que corresponde, el que veo en la interfaz parece ser move_ids_without_package pero no se si esto siempre aplica

# move_ids_without_package Stock moves not in package (stock.move)
# move_line_ids Operations (stock.move.line)
# move_line_ids_without_package Operations without package (stock.move.line)
return self.move_ids_without_package
# move_ids_without_package Stock moves not in package (stock.move)
# move_line_ids Operations (stock.move.line)
# move_line_ids_without_package Operations without package (stock.move.line)
return self.move_ids_without_package

def _l10n_uy_get_remito_codes(self):
""" return list of the available document type codes for uruguayan of stock picking"""
Expand Down Expand Up @@ -191,27 +271,3 @@ def _l10n_uy_edi_cfe_A_iddoc(self):

return res


# TODO KZ este metodo debemos adaptarlo para obtener el IndFact
def _uy_cfe_B4_IndFact(self, line):
""" B4: Indicador de facturación
TODO KZ: Toca revisar realmente cual es el line que corresponde, el que veo en la interfaz parece ser move_ids_without_package pero no se si esto siempre aplica
move_ids_without_package Stock moves not in package (stock.move)
move_line_ids Operations (stock.move.line)
move_line_ids_without_package Operations without package (stock.move.line)
"""
# Another cases for future
# 4: Gravado a Otra Tasa/IVA sobre fictos
# 5: Entrega Gratuita. Por ejemplo docenas de trece
# 6: Producto o servicio no facturable. No existe validación, excepto si A-C20= 1, B-C4=6 o 7.
# 7: Producto o servicio no facturable negativo. . No existe validación, excepto si A-C20= 1, B-C4=6 o 7.
# 8: Sólo para remitos: Ítem a rebajar en e-remitos y en e- remitos de exportación. En área de referencia se debe indicar el N° de remito que ajusta
# 9: Sólo para resguardos: Ítem a anular en resguardos. En área de referencia se debe indicar el N° de resguardo que anular
# 11: Impuesto percibido
# 12: IVA en suspenso
# 13: Sólo para e-Boleta de entrada y sus notas de corrección: Ítem vendido por un no contribuyente (valida que A-C60≠2)
# 14: Sólo para e-Boleta de entrada y sus notas de corrección: Ítem vendido por un contribuyente IVA mínimo, Monotributo o Monotributo MIDES (valida que A-C60=2)
# 15: Sólo para e-Boleta de entrada y sus notas de corrección: Ítem vendido por un contribuyente IMEBA (valida A-C60 = 2)
# 16: Sólo para ítems vendidos por contribuyentes con obligación IVA mínimo, Monotributo o Monotributo MIDES. Si A-C10=3, no puede utilizar indicadores 1, 2, 3, 4, 11 ni 12
return super()._uy_cfe_B4_IndFact(line)
Loading

0 comments on commit b6dc67e

Please sign in to comment.