Controlando las órdenes de compra con las recepciones

Gustavo Orrillo
- 07/04/2022 - 3 min. de lectura

Una de las tareas diarias de Compras es controlar que las ordenes de compra se cumplan. Esto es, que se reciban las materias primas pedidas para conformar las facturas enviadas por los proveedores. Si esto no sucede, ocurren un descontrol administrativo donde por lo menos, la empresa pierde mucho dinero.

Odoo brinda out-of-the-box las estructuras para realizar dicho control. Odoo al validar las órdenes de compra crea las recepciones. Y al validar estos movimientos de ingreso, actualiza las cantidades recibidas en las órdenes de compra relacionadas. No nos vamos a meter en los detalles de como lo hace, lo que vamos a indicar son algunas de sus limitaciones y que solución tienen.

Por lo pronto, al validar una orden de compra se crean las recepciones correspondientes si se pidieron productos stockeables o consumibles:


Y al clickear en recepción, podemos ver la recepción relacionada:


Si vemos el pedido de compra original, podemos ver la cantidad de unidades recibidas para cada producto


Limitaciones

El enfoque provisto por Odoo cubre la mayoría de los casos y es el ideal para las pymes, donde muchas veces prima la automatización de las tareas. Hay unos pocos escenarios en los cuales esto no sirve:

  • Odoo espera que una recepción esté relacionada con una orden de compra, no con múltiples órdenes de compra. En ese caso, en una recepción no se puede indicar a que órdenes de compra está relacionada. Si se puede relacionar una orden de compra con múltiples recepciones, pero no al revés.

  • Hay proveedores que realizan pocos envíos a lo largo del año, y en cada envío satisfacen múltiples órdenes de compra. Por ejemplo un proveedor te puede enviar una bobina de acero por 800 kilogramos, que satisface tres pedidos de compra.

Que se puede hacer al respecto?

Para solucionar este problema desarrollamos el módulo receipt_multiple_purchase. Este módulo agrega a las recepciones un tab en el cual se puede seleccionar las órdenes de compra, el producto y la cantidad recibida.


Tambien provee un botón "Actualizar ordenes de compra" que al presionarlo, actualiza en las órdenes de compra las recepciones relacionadas. Y actualiza en los productos de las ordenes de compra, las cantidades recibidas.

Que le faltaría al módulo? Por lo pronto agregarle una acción al cron para que se realice esta actualización una vez al día (y no depender de la actuación del usuario, el cual valga decir es bastante olvidadizo). Y detalles de la interfaz de usuario, por ejemplo mostrar el botón y el tab de Ordenes de compra cuando la transferencia es una recepción por ejemplo.

Vale decir que hay que analizar cuando utilizar este módulo, ya que si se lo aplica en los casos que no es necesario aumenta la cantidad de trabajo administrativo en forma innecesaria.

Anexo técnico

El módulo agrega un nuevo modelo, stock.picking.purchase. En este modelo el usuario ingresa en la recepción, las ordenes de compra relacionadas junto con sus productos y cantidades. Cuenta con un constraint para asegurarse que el usuario no ingrese datos erroneos (por ejemplo productos no existentes en la orden de compra y cantidades superiores a las cantidades solicitadas).

@api.constrains('product_id','qty')
def check_product_id(self):
    if self.product_id and self.purchase_order_id:
        po_lines = self.env['purchase.order.line'].search(
            [('product_id','=',self.product_id.id),
              ('order_id','=',self.purchase_order_id.id)],
                limit=1)
        if not po_lines:
            raise ValidationError('Producto %s no esta presente en el pedido'%(self.product_id.name))
        if po_lines.product_qty < self.qty:
            raise ValidationError('La cantidad asignada no puede ser mayor a la cantidad pedida')

Despues se sobreescribe el campo picking_ids; el cual deja de ser un campo computado y será actualizado cada vez que se clickee el botón "Actualizar órdenes de compra"

picking_ids = fields.Many2many('stock.picking', string='Receptions', copy=False)

Y en el método apply_picking_purchase_ids (el que se invoca cada vez que clickeamos el botón "Actualizar órdenes de compra"), para simplificar el código en lugar de invocar el ORM ejecutamos un SQL para obtener agrupados por orden de compra, que productos y cantidades fueron ingresados (en todas las recepciones)

sql_select = sql.SQL(
    """
    select purchase_order_id,product_id,sum(qty) as qty
    from stock_picking_purchase
    group by 1,2
    """
)  self._cr.execute(sql_select)

Y debito que estamos actualizando un campo computado almacenado, la actualización la hacemos mediante SQL también:

sql_select = sql.SQL(
    """
    select purchase_order_id,product_id,sum(qty) as qty
    from stock_picking_purchase
    group by 1,2
    """
    )
self._cr.execute(sql_select)
res = self._cr.fetchall()
for record in res:
    sql_update = sql.SQL(
        """
        UPDATE purchase_order_line SET
            qty_received = %s
            WHERE order_id = %s
            AND product_id = %s
        """
        )
    self._cr.execute(sql_update, (record[2], record[0], record[1]))

La performance no es un issue ya que la actualización la estamos haciendo mediante SQL. Igualmente se le pueden agregar restricciones al SELECT para actualizar la menor cantidad de registros


Acerca de:

Gustavo Orrillo

Passionate about programming, he has implemented Odoo for different types of businesses since 2010. In Moldeo Interactive he is a founding Partner and Programmer; In addition to writing on the Blog about different topics related to the developments he makes.