diff --git a/stock_ux/__manifest__.py b/stock_ux/__manifest__.py index 93cfa265a..6fc42877d 100644 --- a/stock_ux/__manifest__.py +++ b/stock_ux/__manifest__.py @@ -19,7 +19,7 @@ ############################################################################## { "name": "Stock UX", - "version": "18.0.1.5.0", + "version": "18.0.1.5.1", "category": "Warehouse Management", "sequence": 14, "summary": "", diff --git a/stock_ux/models/__init__.py b/stock_ux/models/__init__.py index d58254965..01982affa 100644 --- a/stock_ux/models/__init__.py +++ b/stock_ux/models/__init__.py @@ -15,3 +15,4 @@ from . import stock_scrap from . import stock_location from . import stock_forecasted +from . import ir_actions_client diff --git a/stock_ux/models/ir_actions_client.py b/stock_ux/models/ir_actions_client.py new file mode 100644 index 000000000..98d5b6ee5 --- /dev/null +++ b/stock_ux/models/ir_actions_client.py @@ -0,0 +1,16 @@ +from odoo import models +from odoo.tools.safe_eval import safe_eval + + +class IRActionsClient(models.Model): + _inherit = "ir.actions.client" + + def read(self, fields=None, load='_classic_read'): + res = super().read(fields=fields, load=load) + company = self.env.company + if company and company.country_code and company.country_code == "AR": + for i, action in enumerate(res): + ctx = safe_eval(action.get("context") or "{}") + ctx["company_country_code"] = company.country_code + res[i]["context"] = ctx + return res diff --git a/stock_ux/models/stock_move.py b/stock_ux/models/stock_move.py index 7f4679bf0..dbf6cd093 100644 --- a/stock_ux/models/stock_move.py +++ b/stock_ux/models/stock_move.py @@ -53,32 +53,32 @@ def _compute_origin_description(self): @api.constrains("quantity") def _check_quantity(self): - # TODO: Odoo BTL - lock for AR - precision = self.env["decimal.precision"].precision_get("Product Unit of Measure") - if any(self.filtered(lambda x: x.scrapped)): - return - moves = self.filtered( - lambda x: x.picking_id.picking_type_id.block_additional_quantity - and float_compare(x.product_uom_qty, x.quantity, precision_digits=precision) == -1 - ) - if not moves: - return - - # Si lo ejecuta el superusuario (scheduler), revertir el cambio y loguear - if self.env.is_superuser(): - for move in moves: - # Revertir el cambio de quantity - move.quantity = move.product_uom_qty - move.picking_id.message_post( - body=_( - "Se intentó transferir una cantidad mayor a la demanda inicial en el movimiento %s durante la ejecución automática (scheduler). El sistema ignoró el cambio y mantuvo la cantidad original." + if self.env.company.country_code == 'AR': + precision = self.env["decimal.precision"].precision_get("Product Unit of Measure") + if any(self.filtered(lambda x: x.scrapped)): + return + moves = self.filtered( + lambda x: x.picking_id.picking_type_id.block_additional_quantity + and float_compare(x.product_uom_qty, x.quantity, precision_digits=precision) == -1 + ) + if not moves: + return + + # Si lo ejecuta el superusuario (scheduler), revertir el cambio y loguear + if self.env.is_superuser(): + for move in moves: + # Revertir el cambio de quantity + move.quantity = move.product_uom_qty + move.picking_id.message_post( + body=_( + "Se intentó transferir una cantidad mayor a la demanda inicial en el movimiento %s durante la ejecución automática (scheduler). El sistema ignoró el cambio y mantuvo la cantidad original." + ) + % move.display_name ) - % move.display_name - ) - return + return - # Comportamiento normal: raise si corresponde - raise ValidationError(_("You can not transfer more than the initial demand!")) + # Comportamiento normal: raise si corresponde + raise ValidationError(_("You can not transfer more than the initial demand!")) def action_view_linked_record(self): """This function returns an action that display existing sales order @@ -98,27 +98,27 @@ def action_view_linked_record(self): def default_get(self, fields_list): # We override the default_get to make stock moves created when the picking # was confirmed , this way restrict to add more quantity that initial demand - # TODO: Odoo BTL - lock for AR defaults = super().default_get(fields_list) - if self.env.context.get("default_picking_id"): - picking_id = self.env["stock.picking"].browse(self.env.context["default_picking_id"]) - if picking_id.state == "confirmed": - defaults["state"] = "confirmed" - defaults["product_uom_qty"] = 0.0 - defaults["additional"] = True + if self.env.company.country_code == 'AR': + if self.env.context.get("default_picking_id"): + picking_id = self.env["stock.picking"].browse(self.env.context["default_picking_id"]) + if picking_id.state == "confirmed": + defaults["state"] = "confirmed" + defaults["product_uom_qty"] = 0.0 + defaults["additional"] = True return defaults @api.constrains("state") def check_cancel(self): - # TODO: Odoo BTL - lock for AR - if self._context.get("cancel_from_order") or self.env.is_superuser(): - return - if self.filtered( - lambda x: x.picking_id - and x.state == "cancel" - and not self.env.user.has_group("stock_ux.allow_picking_cancellation") - ): - raise ValidationError("Only User with 'Picking cancelation allow' rights can cancel pickings") + if self.env.company.country_code == 'AR': + if self._context.get("cancel_from_order") or self.env.is_superuser(): + return + if self.filtered( + lambda x: x.picking_id + and x.state == "cancel" + and not self.env.user.has_group("stock_ux.allow_picking_cancellation") + ): + raise ValidationError("Only User with 'Picking cancelation allow' rights can cancel pickings") def _merge_moves(self, merge_into=False): # 22/04/2024: Agregamos esto porque sino al intentar confirmar compras con usuarios sin permisos, podia pasar que salga la constrain de arriba (check_cancel) @@ -146,17 +146,17 @@ def create(self, vals_list): @api.depends("state", "picking_id") def _compute_is_initial_demand_editable(self): - # TODO: Odoo BTL - lock for AR super(StockMove, self)._compute_is_initial_demand_editable() - for move in self: - if move.picking_id.picking_type_id.block_additional_quantity and move.picking_id.state != "draft": - move.is_initial_demand_editable = False + if self.env.company.country_code == 'AR': + for move in self: + if move.picking_id.picking_type_id.block_additional_quantity and move.picking_id.state != "draft": + move.is_initial_demand_editable = False def _trigger_assign(self): """To avoid to check_quantity_available when an assing in move is trigger we send a context that checks if the assign comes from this method """ - # TODO: Odoo BTL - lock for AR - if not self.env.context.get("trigger_assign"): - return super().with_context(trigger_assign=True)._trigger_assign() + if self.env.company.country_code == 'AR': + if not self.env.context.get("trigger_assign"): + return super().with_context(trigger_assign=True)._trigger_assign() return super()._trigger_assign() diff --git a/stock_ux/models/stock_move_line.py b/stock_ux/models/stock_move_line.py index 981486d69..d10b428ba 100644 --- a/stock_ux/models/stock_move_line.py +++ b/stock_ux/models/stock_move_line.py @@ -36,53 +36,53 @@ class StockMoveLine(models.Model): @api.depends_context("location") def _compute_product_uom_qty_location(self): - # TODO: Odoo BTL - lock for AR - location = self._context.get("location") - if not location: - self.update({"product_uom_qty_location": 0.0}) - return False - # because now we use location_id to select location, we have compelte - # location name. If y need we can use some code of - # _get_domain_locations on stock/product.py - location_name = location[0] - if isinstance(location[0], int): - location_name = self.env["stock.location"].browse(location[0]).name - locations = self.env["stock.location"].search([("complete_name", "ilike", location_name)]) for rec in self: - product_uom_qty_location = rec.quantity - if rec.location_id in locations: - # if location is source and destiny, then 0 - product_uom_qty_location = 0.0 if rec.location_dest_id in locations else -rec.quantity - rec.product_uom_qty_location = product_uom_qty_location + rec.product_uom_qty_location = 0.0 + if self.env.company.country_code == 'AR': + location = self._context.get("location") + if not location: + self.update({"product_uom_qty_location": 0.0}) + return False + # because now we use location_id to select location, we have compelte + # location name. If y need we can use some code of + # _get_domain_locations on stock/product.py + location_name = location[0] + if isinstance(location[0], int): + location_name = self.env["stock.location"].browse(location[0]).name + locations = self.env["stock.location"].search([("complete_name", "ilike", location_name)]) + for rec in self: + product_uom_qty_location = rec.quantity + if rec.location_id in locations: + # if location is source and destiny, then 0 + product_uom_qty_location = 0.0 if rec.location_dest_id in locations else -rec.quantity + rec.product_uom_qty_location = product_uom_qty_location @api.constrains("quantity") def _check_manual_lines(self): - # TODO: Odoo BTL - lock for AR - if self._context.get("put_in_pack", False): - return - invalid_lines = self.filtered( - lambda x: not x.location_id.should_bypass_reservation() - and x.picking_id.picking_type_id.block_manual_lines - and x._check_quantity_available() < 0 - ) - if not invalid_lines: - return - - # Si lo ejecuta el superusuario (odoobot), revertir el cambio y loguear - if self.env.is_superuser(): - for line in invalid_lines: - # Revertir el cambio de quantity - line.quantity = max(0, line._check_quantity_available() + line.quantity) - if line.picking_id: - line.picking_id.message_post( - body=_( - "Se intentó transferir una cantidad mayor al stock disponible en la línea %s durante la ejecución automática (odoobot/scheduler). El sistema ignoró el cambio y mantuvo la cantidad original." + if self.env.company.country_code == 'AR': + if self._context.get("put_in_pack", False): + return + invalid_lines = self.filtered( + lambda x: not x.location_id.should_bypass_reservation() + and x.picking_id.picking_type_id.block_manual_lines + and x._check_quantity_available() < 0 + ) + if not invalid_lines: + return + # Si lo ejecuta el superusuario (odoobot), revertir el cambio y loguear + if self.env.is_superuser(): + for line in invalid_lines: + # Revertir el cambio de quantity + line.quantity = max(0, line._check_quantity_available() + line.quantity) + if line.picking_id: + line.picking_id.message_post( + body=_( + "Se intentó transferir una cantidad mayor al stock disponible en la línea %s durante la ejecución automática (odoobot/scheduler). El sistema ignoró el cambio y mantuvo la cantidad original." + ) + % line.display_name ) - % line.display_name - ) - return - - raise ValidationError(_("You can't transfer more quantity than the quantity on stock!")) + return + raise ValidationError(_("You can't transfer more quantity than the quantity on stock!")) def _check_quantity_available(self): self.ensure_one() @@ -111,11 +111,11 @@ def create(self, vals_list): """This is to solve a bug when create the sml (the value is not completed after creation) and should be reported to odoo to solve.""" recs = super().create(vals_list) - # TODO: Odoo BTL - lock for AR - for rec in recs: - if rec.picking_id and not rec.description_picking: - product = rec.product_id.with_context(lang=rec.picking_id.partner_id.lang or rec.env.user.lang) - rec.description_picking = product._get_description(rec.picking_id.picking_type_id) + if self.env.company.country_code == 'AR': + for rec in recs: + if rec.picking_id and not rec.description_picking: + product = rec.product_id.with_context(lang=rec.picking_id.partner_id.lang or rec.env.user.lang) + rec.description_picking = product._get_description(rec.picking_id.picking_type_id) return recs def _get_aggregated_product_quantities(self, **kwargs): diff --git a/stock_ux/models/stock_picking.py b/stock_ux/models/stock_picking.py index cbeeda024..c992c6cec 100644 --- a/stock_ux/models/stock_picking.py +++ b/stock_ux/models/stock_picking.py @@ -28,57 +28,83 @@ def unlink(self): To avoid errors we block deletion of pickings in other state than draft or cancel """ - # TODO: Odoo BTL - lock for AR - not_del_pickings = self.filtered( - lambda x: x.picking_type_id.block_picking_deletion or x.state not in ("draft", "cancel") - ) - if not_del_pickings: - raise ValidationError( - _( - 'You can not delete this pickings because "Block picking ' - 'deletion" is enable on the picking type/s "%s" ' - "or the state of the picking is not draft or cancel.\n" - "Picking Ids: %s" - ) - % (",".join(not_del_pickings.mapped("picking_type_id.name")), not_del_pickings.ids) + if self.env.company.country_code == 'AR': + not_del_pickings = self.filtered( + lambda x: x.picking_type_id.block_picking_deletion or x.state not in ("draft", "cancel") ) + if not_del_pickings: + raise ValidationError( + _( + 'You can not delete this pickings because "Block picking ' + 'deletion" is enable on the picking type/s "%s" ' + "or the state of the picking is not draft or cancel.\n" + "Picking Ids: %s" + ) + % (",".join(not_del_pickings.mapped("picking_type_id.name")), not_del_pickings.ids) + ) return super().unlink() def copy(self, default=None): - # TODO: Odoo BTL - lock for AR - for picking in self: - if not default and picking.picking_type_id.block_additional_quantity: - raise UserError( - _( - 'You can not duplicate a Picking because "Block Additional Quantity" is enabled on the picking type "%(name)s"' + if self.env.company.country_code == 'AR': + for picking in self: + if not default and picking.picking_type_id.block_additional_quantity: + raise UserError( + _( + 'You can not duplicate a Picking because "Block Additional Quantity" is enabled on the picking type "%(name)s"' + ) + % {"name": picking.picking_type_id.name} ) - % {"name": picking.picking_type_id.name} - ) return super().copy(default=default) @api.onchange("location_id") def change_location(self): - # TODO: Odoo BTL - lock for AR - # we only change moves locations if picking in draft - if self.state == "draft": - self.move_ids.update({"location_id": self.location_id.id}) + if self.env.company.country_code == 'AR': + # we only change moves locations if picking in draft + if self.state == "draft": + self.move_ids.update({"location_id": self.location_id.id}) @api.onchange("location_dest_id") def change_location_dest(self): - # TODO: Odoo BTL - lock for AR - # we only change moves locations if picking in draft - if self.state == "draft": - self.move_ids.update({"location_dest_id": self.location_dest_id.id}) + if self.env.company.country_code == 'AR': + # we only change moves locations if picking in draft + if self.state == "draft": + self.move_ids.update({"location_dest_id": self.location_dest_id.id}) def _send_confirmation_email(self): - # TODO: Odoo BTL - lock for AR - for rec in self: - if rec.picking_type_id.mail_template_id: + if self.env.company.country_code == 'AR': + for rec in self: + if rec.picking_type_id.mail_template_id: + try: + rec.with_context( + email_notification_force_header=True, + email_notification_force_footer=True, + ).message_post_with_source(rec.picking_type_id.mail_template_id) + except Exception as error: + title = _("ERROR: Picking was not sent via email") + rec.message_post( + body="

".join( + [ + "" + title + "", + _("Please check the email template associated with the picking type."), + "" + str(error) + "", + ] + ), + body_is_html=True, + ) + else: + super(StockPicking, self)._send_confirmation_email() + else: + super(StockPicking, self)._send_confirmation_email() + + def _action_done(self): + if self.env.company.country_code == 'AR': + for rec in self.with_context( + mail_notify_force_send=False, + email_notification_force_header=True, + email_notification_force_footer=True, + ).filtered("picking_type_id.mail_template_id"): try: - rec.with_context( - email_notification_force_header=True, - email_notification_force_footer=True, - ).message_post_with_source(rec.picking_type_id.mail_template_id) + rec.message_post_with_template(rec.picking_type_id.mail_template_id.id) except Exception as error: title = _("ERROR: Picking was not sent via email") rec.message_post( @@ -89,31 +115,7 @@ def _send_confirmation_email(self): "" + str(error) + "", ] ), - body_is_html=True, ) - else: - super(StockPicking, self)._send_confirmation_email() - - def _action_done(self): - # TODO: Odoo BTL - lock for AR - for rec in self.with_context( - mail_notify_force_send=False, - email_notification_force_header=True, - email_notification_force_footer=True, - ).filtered("picking_type_id.mail_template_id"): - try: - rec.message_post_with_template(rec.picking_type_id.mail_template_id.id) - except Exception as error: - title = _("ERROR: Picking was not sent via email") - rec.message_post( - body="

".join( - [ - "" + title + "", - _("Please check the email template associated with the picking type."), - "" + str(error) + "", - ] - ), - ) return super()._action_done() def new_force_availability(self): @@ -159,17 +161,17 @@ def write(self, vals): :return: Result of the superclass write method. """ - if "picking_type_id" in vals: - user = self.env.user - # TODO: Odoo BTL - lock for AR - if user.has_group("stock_ux.group_restrict_edit_picking_type"): - for picking in self: - if picking.picking_type_id: - raise UserError( - _( - "You cannot change the Operation Type once it has been set. " - "This action is restricted for your user. " - "Please contact your Inventory Manager if you need to perform this operation." + if self.env.company.country_code == 'AR': + if "picking_type_id" in vals: + user = self.env.user + if user.has_group("stock_ux.group_restrict_edit_picking_type"): + for picking in self: + if picking.picking_type_id: + raise UserError( + _( + "You cannot change the Operation Type once it has been set. " + "This action is restricted for your user. " + "Please contact your Inventory Manager if you need to perform this operation." + ) ) - ) return super().write(vals) diff --git a/stock_ux/models/stock_rule.py b/stock_ux/models/stock_rule.py index 32652cf8a..71ee8ce41 100644 --- a/stock_ux/models/stock_rule.py +++ b/stock_ux/models/stock_rule.py @@ -24,19 +24,18 @@ def _run_pull(self, procurements): """ result = super()._run_pull(procurements) - # Extract orderpoints from procurements and recompute their qty_to_order_computed - # procurement is a namedtuple: - # (product_id, product_qty, product_uom, location_id, name, origin, company_id, values) - # TODO: Odoo BTL - lock for AR - orderpoints = self.env["stock.warehouse.orderpoint"] - for procurement, rule in procurements: - # Access values dict from the namedtuple (index 7 or .values attribute) - values = procurement.values - if values.get("orderpoint_id"): - orderpoints |= values["orderpoint_id"] - - # Recompute only the affected orderpoints for performance - if orderpoints: - orderpoints.sudo()._compute_qty_to_order_computed() - + if self.env.company.country_code == 'AR': + # Extract orderpoints from procurements and recompute their qty_to_order_computed + # procurement is a namedtuple: + # (product_id, product_qty, product_uom, location_id, name, origin, company_id, values) + orderpoints = self.env["stock.warehouse.orderpoint"] + for procurement, rule in procurements: + # Access values dict from the namedtuple (index 7 or .values attribute) + values = procurement.values + if values.get("orderpoint_id"): + orderpoints |= values["orderpoint_id"] + + # Recompute only the affected orderpoints for performance + if orderpoints: + orderpoints.sudo()._compute_qty_to_order_computed() return result diff --git a/stock_ux/models/stock_warehouse_orderpoint.py b/stock_ux/models/stock_warehouse_orderpoint.py index 81e5da4c7..f7f2af176 100644 --- a/stock_ux/models/stock_warehouse_orderpoint.py +++ b/stock_ux/models/stock_warehouse_orderpoint.py @@ -131,15 +131,15 @@ def write(self, vals): """When archive a replenishment rule set min, max and multiple quantities in 0. """ - # TODO: Odoo BTL - lock for AR - if "active" in vals and not vals["active"]: - self.write( - { - "product_min_qty": 0.0, - "product_max_qty": 0.0, - "qty_multiple": 0.0, - } - ) + if self.env.company.country_code == 'AR': + if "active" in vals and not vals["active"]: + self.write( + { + "product_min_qty": 0.0, + "product_max_qty": 0.0, + "qty_multiple": 0.0, + } + ) return super().write(vals) def _get_orderpoint_action(self): @@ -182,7 +182,7 @@ def update_qty_to_order(self): def _cron_compute_rotation(self): """Cron method to compute the rotation of orderpoints.""" - # TODO: Odoo BTL - lock for AR - orderpoints = self.with_context(active_test=False).search([]) - orderpoints._compute_rotation() - return True + if self.env.company.country_code == 'AR': + orderpoints = self.with_context(active_test=False).search([]) + orderpoints._compute_rotation() + return True diff --git a/stock_ux/static/src/forecasted_buttons.xml b/stock_ux/static/src/forecasted_buttons.xml index 17a0ab190..54d8ad7f3 100644 --- a/stock_ux/static/src/forecasted_buttons.xml +++ b/stock_ux/static/src/forecasted_buttons.xml @@ -2,12 +2,14 @@ - + + + diff --git a/stock_ux/views/stock_picking_views.xml b/stock_ux/views/stock_picking_views.xml index b430295fa..a78e9dfd0 100644 --- a/stock_ux/views/stock_picking_views.xml +++ b/stock_ux/views/stock_picking_views.xml @@ -108,14 +108,6 @@ - - - - - - - - stock.picking.form stock.picking