What Monkey Patching Is and Its Application in Odoo 17

Odoo, an open-source ERP and business applications platform, provides a versatile framework for customization and extension. This article delves into the concept of "monkey patching" as a method used to enhance existing functionality in Odoo 17. Let's explore what monkey patching involves and how it can be applied within the Odoo 17 environment.

What constitutes Monkey Patching

Monkey Patching is a technique that allows for the modification or extension of the behavior of existing code at runtime. This approach enables changes to be made to the code without directly altering the source code itself. It is useful for introducing new features, addressing bugs, or overriding existing code without making direct modifications to the original source code. Monkey Patching is typically applied in a structured manner.

# Original class
class MyClass:
    def original_method(self):
        print("This is the original method")
# Monkey patching the class with a new method
def new_method(self):
    print("This is the new method")
MyClass.original_method = new_method

By assigning new_method to MyClass.original_method, the code is substituted with the new method. Consequently, whenever the original method is invoked, the new method is executed, thereby effecting changes without modifying the original method itself.

Example: Splitting Deliveries in Sale Orders

Let's explore a scenario involving a sales order where the objective is to divide deliveries for each product listed in the order. A singular delivery, referred to as stock picking, is generated for the entire sales order encompassing all products. To accomplish this, it is necessary to introduce a field in the order line to indicate the delivery date for each product. Furthermore, a modification of the _assign_picking method within the stock.move model is required. This method is responsible for crafting a pick for each move.

Crafting a compelling narrative imbued with personality can forge a stronger connection with potential clients. Infusing your story with unique word choices and phrases, as well as writing from your own perspective rather than relying on someone else's experience, adds depth and authenticity. This approach allows you to showcase your individuality and establish a genuine rapport with your audience, fostering a sense of trust and understanding.

  """Inheriting sale order line to add delivery date field"""
    _inherit = 'sale.order.line'
    delivery_date = fields.Date("Delivery Date")

Integrate the field into the views.

<odoo>
   <record id="view_order_form" model="ir.ui.view">
       <field name="name">sale.order.inherit.monkey.patching</field>
       <field name="inherit_id" ref="sale.view_order_form"/>
       <field name="model">sale.order</field>
       <field name="arch" type="xml">
           <xpath expr="//field[@name='order_line']/tree/field[@name='price_unit']"
                  position="before">
               <field name="delivery_date"/>
           </xpath>
       </field>
   </record>
</odoo>

To ensure that the scheduled delivery date aligns with the one specified in each product's order line when dividing deliveries, we need to modify the _prepare_procurement_values method of the sale order line model using monkey patching.Injecting personality into your 

writing can create a compelling narrative that resonates with potential clients, fostering a deeper connection. Infusing your story with unique word choices and phrases adds character, making it more engaging and memorable. It's essential to write from your own perspective, offering a genuine and authentic account rather than borrowing from someone else's experience.

def prepare_procurement_values(self, group_id=False):
           Order_date = self.date_order
           Order_id = self.order_id
           deadline_date = self.delivery_date or (
                           order_id.order_date + 
                           timedelta(Days= self.customer_lead or 0.0)
                           )
        planned_date = deadline_date - timedelta(
            days=self.order_id.companyid.security_lead)
        values = {
            'group_id': group_id,
            'sale_line_id': self.id,
            'date_planned': planned_date,
            'date_deadline': deadline_date,
            'route_ids': self.route_id,
            'warehouse_id': order_id.warhouse_id or False,
            'product_description_variants': self.with_context(
                lang=order_id.partner_id.lang).
            _get_sale_order_line_multiline_description_variants(),
            'company_id': order_id.company_id,
            'product_packaging_id': self.product_packaging_id,
            'sequence': self.sequence,
 }
         return values
    SaleOrderLine._prepare_procurement_values = _prepare_procurement_values

In this scenario, we've modified the date_deadline value to correspond with the delivery date indicated in the respective order line. If the delivery date isn't specified, we default it to the order date, factoring in the customer lead time of the product. Next, we assign the newly created method to the SaleOrderLine class using the following statement:

SaleOrderLine._prepare_procurement_values = _prepare_procurement_values

Extending the Stock Move Model

Next, we enhance the capabilities of the stock move model by extending it through inheritance, utilizing the _inherit attribute.

class StockMoveInherits(models.Model):
    """inheriting the stock move model"""
    _inherit = 'stock.move

Afterward, we proceed to adjust the _assign_picking method to achieve the desired splitting of deliveries.

def _assign_picking(self):
        pickings = self.env['stock.picking']
        grouped_moves = groupby(self, key=lambda m:          m._key_assign_picking())
        for group, moves in grouped_moves:
            moves = self.env['stock.move'].concat(*moves)
            new_picking = False
            pickings = moves[0]._search_picking_for_assignation()
            if pickings:
                vals = {}
                if any(pickings.partner_id.id != m.partner_id.id
                       for m in moves):
                    vals['partner_id'] = False
                if any(pickings.origin != m.origin for m in moves):
                    vals['origin'] = False
                if vals:
                    pickings.write(vals)
            else:
                moves = moves.filtered(lambda m: float_compare(
                    m.product_uom_qty, 0.0, precision_rounding=
                    m.product_uom.rounding) >= 0)
                if not moves:
                    continue
                new_picking = True
                pick_values = moves._get_new_picking_values()
                sale_order = self.env['sale.order'].search([
                    ('name', '=', pick_values['origin'])])
                for move in moves:
                        picking = picking.create(
                        move._get_new_picking_values())
                        move.write({
                                  'picking_id': pickings.id
                                   })
                        move._assign_picking_post_process(
                                     new=new_picking)
                return True
    StockMove._assign_picking = _assign_picking

Now, let's provide an explanation of the code.

pickings = self.env['stock.picking']

First, we initialize a variable named "picking" as an empty record of the 'stock.picking' model.

grouped_moves = groupby(self, key=lambda m: m._key_assign_picking())
        for group, moves in grouped_moves:
            moves = self.env['stock.move'].concat(*moves)

The variable "grouped_moves" is created using the groupby function, which organizes the moves into groups based on a key obtained from the _key_assign_picking attribute of each move. Then, it iterates through each group of moves and combines them.

pickings = moves[0]._search_picking_for_assignation()

Next, it utilizes the first move to search for a picking by employing the _search_picking_for_assignation method.

if pickings:
                vals = {}
                if any(pickings.partner_id.id != m.partner_id.id
                       for m in moves):
                    vals['partner_id'] = False
                if any(pickings.origin != m.origin for m in moves):
                    vals['origin'] = False
                if vals:
                    pickings.write(vals)

If a picking is found, the fields partner_id and origin are updated accordingly. If no picking is found, the code checks for negative moves and filters them out.

else:
                moves = moves.filtered(
                    lambda m: float_compare(
                    m.product_uom_qty, 0.0, precision_rounding=
                    m.product_uom.rounding) >= 0)
                if not moves:
                    continue
                new_picking = True
                pick_values = moves._get_new_picking_values()
                sale_order = self.env['sale.order'].search([
                    ('name', '=', pick_values['origin'])])

If there are any remaining moves after filtering, a new picking is created, using the picking values obtained from the _get_new_picking_values method.

for move-in moves:
    picking =picking.create(move._get_new_picking_values())
                        move.write({
                                    'picking_id': picking.id
                                    })
                        move._assign_picking_post_process(
                                        new=new_picking)

Afterward, a separate picking is created for each move.

StockMove._assign_picking = _assign_picking

In summary, we've integrated the newly created function into the _assign_picking method of the StockMove class. By employing these monkey patches, we've effectively achieved the desired functionality of dividing deliveries within the sale order. It's recommended to consider alternative approaches as needed. This outlines the process of implementing monkey patching in Odoo 17.

Creating a Flutter Application with Shimmer Animation and Buttons