From e1f5691e8a3912780a58243eeeddf4f22798549b Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Fri, 20 Sep 2024 16:29:12 -0300 Subject: [PATCH 1/4] [IMP] l10n_br_delivery_nfe: add product volume fields --- l10n_br_delivery_nfe/__manifest__.py | 11 ++++-- l10n_br_delivery_nfe/models/__init__.py | 2 ++ .../models/product_product.py | 13 +++++++ .../models/product_template.py | 35 +++++++++++++++++++ .../views/product_product_view.xml | 13 +++++++ .../views/product_template_view.xml | 16 +++++++++ oca_dependencies.txt | 1 + 7 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 l10n_br_delivery_nfe/models/product_product.py create mode 100644 l10n_br_delivery_nfe/models/product_template.py create mode 100644 l10n_br_delivery_nfe/views/product_product_view.xml create mode 100644 l10n_br_delivery_nfe/views/product_template_view.xml diff --git a/l10n_br_delivery_nfe/__manifest__.py b/l10n_br_delivery_nfe/__manifest__.py index 35286730b2e3..1c85b71b4a96 100644 --- a/l10n_br_delivery_nfe/__manifest__.py +++ b/l10n_br_delivery_nfe/__manifest__.py @@ -10,10 +10,17 @@ "maintainers": ["mbcosta"], "website": "https://github.com/OCA/l10n-brazil", "version": "14.0.1.1.0", - "depends": ["l10n_br_nfe", "l10n_br_account", "l10n_br_delivery"], + "depends": [ + "l10n_br_nfe", + "l10n_br_account", + "l10n_br_delivery", + "product_net_weight", + ], "data": [ # Views - "views/nfe_document_view.xml" + "views/nfe_document_view.xml", + "views/product_product_view.xml", + "views/product_template_view.xml", ], "installable": True, "auto_install": True, diff --git a/l10n_br_delivery_nfe/models/__init__.py b/l10n_br_delivery_nfe/models/__init__.py index 9c01ea2470e4..9c2d7e53227a 100644 --- a/l10n_br_delivery_nfe/models/__init__.py +++ b/l10n_br_delivery_nfe/models/__init__.py @@ -1 +1,3 @@ from . import document +from . import product_product +from . import product_template diff --git a/l10n_br_delivery_nfe/models/product_product.py b/l10n_br_delivery_nfe/models/product_product.py new file mode 100644 index 000000000000..c814feab2162 --- /dev/null +++ b/l10n_br_delivery_nfe/models/product_product.py @@ -0,0 +1,13 @@ +# Copyright (C) 2024 Diego Paradeda - KMEE +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + product_volume_type = fields.Char( + string="Volume Type", + help="Type of transported volumes: Mapped onto 'nfe40_esp'", + ) diff --git a/l10n_br_delivery_nfe/models/product_template.py b/l10n_br_delivery_nfe/models/product_template.py new file mode 100644 index 000000000000..28ffc6b83484 --- /dev/null +++ b/l10n_br_delivery_nfe/models/product_template.py @@ -0,0 +1,35 @@ +# Copyright (C) 2024 Diego Paradeda - KMEE +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import api, fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + product_volume_type = fields.Char( + string="Volume Type", + help="Type of transported volumes: Mapped onto 'nfe40_esp'", + compute="_compute_product_volume_type", + inverse="_inverse_product_volume_type", + store=True, + ) + + @api.depends("product_variant_ids", "product_variant_ids.product_volume_type") + def _compute_product_volume_type(self): + unique_variants = self.filtered( + lambda template: len(template.product_variant_ids) == 1 + ) + for template in unique_variants: + template.product_volume_type = ( + template.product_variant_ids.product_volume_type + ) + for template in self - unique_variants: + template.product_volume_type = "" + + def _inverse_product_volume_type(self): + for template in self: + if len(template.product_variant_ids) == 1: + template.product_variant_ids.product_volume_type = ( + template.product_volume_type + ) diff --git a/l10n_br_delivery_nfe/views/product_product_view.xml b/l10n_br_delivery_nfe/views/product_product_view.xml new file mode 100644 index 000000000000..3ddddc3dd614 --- /dev/null +++ b/l10n_br_delivery_nfe/views/product_product_view.xml @@ -0,0 +1,13 @@ + + + + product.product + + + + + + + + + diff --git a/l10n_br_delivery_nfe/views/product_template_view.xml b/l10n_br_delivery_nfe/views/product_template_view.xml new file mode 100644 index 000000000000..8afdfdba26a5 --- /dev/null +++ b/l10n_br_delivery_nfe/views/product_template_view.xml @@ -0,0 +1,16 @@ + + + + product.template + + + + + + + + + diff --git a/oca_dependencies.txt b/oca_dependencies.txt index a2b9e179dc90..7ed4b178027b 100644 --- a/oca_dependencies.txt +++ b/oca_dependencies.txt @@ -12,3 +12,4 @@ purchase-workflow sale-workflow delivery-carrier web +product-atribute From c0abae473561b32a0ad333668911eed65e868e9b Mon Sep 17 00:00:00 2001 From: Diego Paradeda Date: Fri, 20 Sep 2024 16:30:42 -0300 Subject: [PATCH 2/4] [IMP] l10n_br_delivery_nfe: nfe40 vols from stock.picking --- l10n_br_delivery_nfe/__init__.py | 1 + l10n_br_delivery_nfe/__manifest__.py | 7 + l10n_br_delivery_nfe/models/__init__.py | 3 + l10n_br_delivery_nfe/models/stock_picking.py | 137 ++++++++++++++++++ .../models/stock_picking_lacres.py | 24 +++ .../models/stock_picking_vol.py | 53 +++++++ .../security/ir.model.access.csv | 6 + .../views/stock_picking_view.xml | 57 ++++++++ l10n_br_delivery_nfe/wizards/__init__.py | 3 + .../wizards/stock_generate_volumes.py | 71 +++++++++ .../wizards/stock_generate_volumes_view.xml | 43 ++++++ .../wizards/stock_invoice_onshipping.py | 86 +++++++++++ .../wizards/stock_invoice_onshipping_view.xml | 28 ++++ .../wizards/vol_stock_invoice_onshipping.py | 79 ++++++++++ oca_dependencies.txt | 1 + 15 files changed, 599 insertions(+) create mode 100644 l10n_br_delivery_nfe/models/stock_picking.py create mode 100644 l10n_br_delivery_nfe/models/stock_picking_lacres.py create mode 100644 l10n_br_delivery_nfe/models/stock_picking_vol.py create mode 100644 l10n_br_delivery_nfe/security/ir.model.access.csv create mode 100644 l10n_br_delivery_nfe/views/stock_picking_view.xml create mode 100644 l10n_br_delivery_nfe/wizards/__init__.py create mode 100644 l10n_br_delivery_nfe/wizards/stock_generate_volumes.py create mode 100644 l10n_br_delivery_nfe/wizards/stock_generate_volumes_view.xml create mode 100644 l10n_br_delivery_nfe/wizards/stock_invoice_onshipping.py create mode 100644 l10n_br_delivery_nfe/wizards/stock_invoice_onshipping_view.xml create mode 100644 l10n_br_delivery_nfe/wizards/vol_stock_invoice_onshipping.py diff --git a/l10n_br_delivery_nfe/__init__.py b/l10n_br_delivery_nfe/__init__.py index 0650744f6bc6..aee8895e7a31 100644 --- a/l10n_br_delivery_nfe/__init__.py +++ b/l10n_br_delivery_nfe/__init__.py @@ -1 +1,2 @@ from . import models +from . import wizards diff --git a/l10n_br_delivery_nfe/__manifest__.py b/l10n_br_delivery_nfe/__manifest__.py index 1c85b71b4a96..a314b037965d 100644 --- a/l10n_br_delivery_nfe/__manifest__.py +++ b/l10n_br_delivery_nfe/__manifest__.py @@ -15,12 +15,19 @@ "l10n_br_account", "l10n_br_delivery", "product_net_weight", + "product_brand", ], "data": [ + # Security + "security/ir.model.access.csv", + # Wizards + "wizards/stock_invoice_onshipping_view.xml", + "wizards/stock_generate_volumes_view.xml", # Views "views/nfe_document_view.xml", "views/product_product_view.xml", "views/product_template_view.xml", + "views/stock_picking_view.xml", ], "installable": True, "auto_install": True, diff --git a/l10n_br_delivery_nfe/models/__init__.py b/l10n_br_delivery_nfe/models/__init__.py index 9c2d7e53227a..23eb7fda31ca 100644 --- a/l10n_br_delivery_nfe/models/__init__.py +++ b/l10n_br_delivery_nfe/models/__init__.py @@ -1,3 +1,6 @@ from . import document from . import product_product from . import product_template +from . import stock_picking_vol +from . import stock_picking_lacres +from . import stock_picking diff --git a/l10n_br_delivery_nfe/models/stock_picking.py b/l10n_br_delivery_nfe/models/stock_picking.py new file mode 100644 index 000000000000..7828c7cebd27 --- /dev/null +++ b/l10n_br_delivery_nfe/models/stock_picking.py @@ -0,0 +1,137 @@ +# Copyright (C) 2024 Diego Paradeda - KMEE +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class StockPicking(models.Model): + _inherit = "stock.picking" + + has_vol = fields.Boolean( + string="Has Vol IDs", + help="Technical Field: True if the picking has already generated Volume IDs.", + copy=False, + ) + + vol_ids = fields.One2many( + string="Volume Data", + comodel_name="stock.picking.vol", + inverse_name="picking_id", + copy=False, + ) + + number_of_volumes = fields.Integer( + string="Number of Volumes", + default=0, + copy=False, + ) + + def _get_volume_data_package_level(self): + """Generate a single volume for packages""" + vols_data = [] + for picking_id in self: + if picking_id.package_ids: + for package_level_id in picking_id.package_level_ids: + manual_weight = package_level_id.package_id.shipping_weight + vol_data = { + "nfe40_qVol": 1, + "nfe40_esp": "", + "nfe40_marca": "", + "nfe40_pesoL": 0, + "nfe40_pesoB": (manual_weight if manual_weight else 0), + "picking_id": picking_id.id, + } + + for line in package_level_id.move_line_ids: + vol_data["nfe40_esp"] = ( + vol_data["nfe40_esp"] or line.product_id.product_volume_type + ) + product_nfe40_marca = ( + line.product_id.product_brand_id.name + if line.product_id.product_brand_id + else "" + ) + vol_data["nfe40_marca"] = ( + vol_data["nfe40_marca"] or product_nfe40_marca + ) + pesoL = line.qty_done * line.product_id.net_weight + pesoB = line.qty_done * line.product_id.weight + vol_data["nfe40_pesoL"] += pesoL + vol_data["nfe40_pesoB"] += 0 if manual_weight else pesoB + vols_data.append(vol_data) + + return vols_data + + def _get_volume_data_wo_package(self): + """Generate a single volume for lines without package""" + vols_data = [] + for picking_id in self: + # Filter out move lines with in a package + if not picking_id.move_line_ids_without_package.filtered( + lambda ml: not ml.package_level_id + ): + continue + + new_vol = { + "nfe40_qVol": 0, + "nfe40_esp": "", + "nfe40_marca": "", + "nfe40_pesoL": 0, + "nfe40_pesoB": 0, + "picking_id": picking_id.id, + } + + for line in picking_id.move_line_ids_without_package.filtered( + lambda ml: not ml.package_level_id and not ml.result_package_id + ): + new_vol["nfe40_qVol"] += line.qty_done + new_vol["nfe40_esp"] = ( + new_vol["nfe40_esp"] or line.product_id.product_volume_type + ) + product_nfe40_marca = ( + line.product_id.product_brand_id.name + if line.product_id.product_brand_id + else "" + ) + new_vol["nfe40_marca"] = new_vol["nfe40_marca"] or product_nfe40_marca + pesoL = line.qty_done * line.product_id.net_weight + pesoB = line.qty_done * line.product_id.weight + new_vol["nfe40_pesoL"] += pesoL + new_vol["nfe40_pesoB"] += pesoB + + new_vol["nfe40_qVol"] = f"{new_vol['nfe40_qVol']:.0f}" + vols_data.append(new_vol) + + return vols_data + + def _get_pre_generated_volumes(self): + """Retreive and convert already generated volumes that are stored on picking""" + vols_data = [] + for picking_id in self: + for vol_id in picking_id.vol_ids: + vol_data = vol_id.copy_data()[0] + vols_data.append(vol_data) + return vols_data + + def prepare_vols_data_from_picking(self): + pre_generated_pickings = self.filtered(lambda p: p.has_vol) + to_generate_pickings = self.filtered(lambda p: not p.has_vol) + + vols_data = [] + vols_data += pre_generated_pickings._get_pre_generated_volumes() + vols_data += to_generate_pickings._get_volume_data_package_level() + vols_data += to_generate_pickings._get_volume_data_wo_package() + return vols_data + + def _compute_number_of_volumes(self): + for picking in self: + if len(picking.invoice_ids) == 1: + picking.number_of_volumes = sum( + [ + float(v) + for v in picking.invoice_ids.mapped( + "fiscal_document_id.nfe40_vol.nfe40_qVol" + ) + ] + ) + else: + picking.number_of_volumes = 0 diff --git a/l10n_br_delivery_nfe/models/stock_picking_lacres.py b/l10n_br_delivery_nfe/models/stock_picking_lacres.py new file mode 100644 index 000000000000..1366d0b2691a --- /dev/null +++ b/l10n_br_delivery_nfe/models/stock_picking_lacres.py @@ -0,0 +1,24 @@ +# Copyright (C) 2024 Diego Paradeda - KMEE +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class StockPickingLacres(models.Model): + _name = "stock.picking.lacres" + _description = "lacres" + # _inherit = "nfe.40.lacres" TODO: consider using inherit in the future + + """ + NFe40 fields start + ################## + this section copies fields from nfe.40.vol + sadly, _name/_inherit breaks spec_model + inheriting would be better than recreating the same fields. + """ + nfe40_lacres_vol_id = fields.Many2one(comodel_name="stock.picking.vol") + nfe40_nLacre = fields.Char(string="Número dos Lacres") + """ + NFe40 fields end + ################ + """ diff --git a/l10n_br_delivery_nfe/models/stock_picking_vol.py b/l10n_br_delivery_nfe/models/stock_picking_vol.py new file mode 100644 index 000000000000..c9731614480f --- /dev/null +++ b/l10n_br_delivery_nfe/models/stock_picking_vol.py @@ -0,0 +1,53 @@ +# Copyright (C) 2024 Diego Paradeda - KMEE +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from odoo import fields, models + + +class StockPickingVol(models.Model): + _name = "stock.picking.vol" + _description = "Volume Data" + # _inherit = "nfe.40.vol" TODO: consider using inherit in the future + + """ + NFe40 fields start + ################## + this section copies fields from nfe.40.vol + sadly, _name/_inherit breaks spec_model + inheriting would be better than recreating the same fields. + TODO: learn how to inherit nfe mixin (https://github.com/OCA/l10n-brazil/pull/3091) + """ + nfe40_vol_transp_id = fields.Many2one(comodel_name="nfe.40.transp") + nfe40_qVol = fields.Char(string="Quantidade de volumes transportados") + nfe40_esp = fields.Char(string="Espécie dos volumes transportados") + nfe40_marca = fields.Char(string="Marca dos volumes transportados") + nfe40_nVol = fields.Char(string="Numeração dos volumes transportados") + + nfe40_pesoL = fields.Float( + string="Peso líquido (em kg)", + digits=( + 12, + 3, + ), + ) + + nfe40_pesoB = fields.Float( + string="Peso bruto (em kg)", + digits=( + 12, + 3, + ), + ) + + nfe40_lacres = fields.One2many( + "stock.picking.lacres", "nfe40_lacres_vol_id", string="lacres" + ) + """ + NFe40 fields end + ################ + """ + + picking_id = fields.Many2one( + comodel_name="stock.picking", + string="Stock Picking", + ) diff --git a/l10n_br_delivery_nfe/security/ir.model.access.csv b/l10n_br_delivery_nfe/security/ir.model.access.csv new file mode 100644 index 000000000000..ce32f4255c4a --- /dev/null +++ b/l10n_br_delivery_nfe/security/ir.model.access.csv @@ -0,0 +1,6 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +l10n_br_delivery_nfe_onshipping_vol,l10n_br_delivery_nfe.onshipping_vol,l10n_br_delivery_nfe.model_stock_invoice_onshipping_vol,stock.group_stock_user,1,1,1,1 +l10n_br_delivery_nfe_onshipping_lacres,l10n_br_delivery_nfe.onshipping_lacres,l10n_br_delivery_nfe.model_stock_invoice_onshipping_lacres,stock.group_stock_user,1,1,1,1 +l10n_br_delivery_nfe_stock_generate_volumes,l10n_br_delivery_nfe.stock_generate_volumes,l10n_br_delivery_nfe.model_stock_generate_volumes,stock.group_stock_user,1,1,1,1 +l10n_br_delivery_nfe_stock_picking_vol,l10n_br_delivery_nfe.stock_picking_vol,l10n_br_delivery_nfe.model_stock_picking_vol,stock.group_stock_user,1,1,1,1 +l10n_br_delivery_nfe_stock_picking_lacres,l10n_br_delivery_nfe.stock_picking_lacres,l10n_br_delivery_nfe.model_stock_picking_lacres,stock.group_stock_user,1,1,1,1 diff --git a/l10n_br_delivery_nfe/views/stock_picking_view.xml b/l10n_br_delivery_nfe/views/stock_picking_view.xml new file mode 100644 index 000000000000..36a94634c79b --- /dev/null +++ b/l10n_br_delivery_nfe/views/stock_picking_view.xml @@ -0,0 +1,57 @@ + + + stock.picking + + + + + + + + + + + + +