Relacionando ingresos con ordenes de compra

24 de setiembre de 2023 por
Gustavo Orrillo
| Sin comentarios aún
 

Muchas veces necesitamos relacionar manualmente ordenes de compra con recepciones individuales. Por varios motivos, a veces error del usuario pero muchas veces porque primero se recibio la transferencia y luego se creo la orden de compra (sucede).

Vamos a mostrar como se hace eso con un módulo que agrega un wizard a las recepciones. Dicho wizard permite seleccionar una orden de compra para luego relacionarla. 

Bien, pasemos a desarrollar el módulo. Primero creamos un directorio con el nombre del módulo, y dicho directorio tendra el archivo __manifest__.py con el manifiesto:

{
"name": "picking_to_purchaseorder",
"version": "15.0.1.0.0",
"license": "AGPL-3",
"depends": ["base","stock","purchase"],
"category": "Purchase",
"data": [
'views/stock_view.xml',
'security/ir.model.access.csv',
'wizard/wizard_view.xml'
],
}

Como pueden ver, en el manifiesto se listan las dependencias stock y purchase. Y se indica que se va a leer tres archivos: dos con definiciones de vista y un archivo con definiciones de seguridad.

Seguidamente, vamos a definir en el modelo stock.picking (que administra las transferencias) un método que va a crear un wizard y mostrarlo. 

class StockPicking(models.Model):
_inherit = 'stock.picking'

def btn_link_purchase_order(self):
self.ensure_one()
if self.state != 'done' or self.picking_type_code != 'incoming':
raise ValidationError('Transferencia incorrecta')
vals = {
'picking_id': self.id,
}
wizard_id = self.env['picking.po.wizard'].create(vals)
res = {
'name': _('Picking to PO Wizard'),
'res_model': 'picking.po.wizard',
'view_mode': 'form',
'res_id': wizard_id.id,
'type': 'ir.actions.act_window',
'target': 'new',
}
return res

Como pueden ver el método primero chequea que se pueda relacionar la transferencia con la PO, luego crea un wizard con el valor de la transferencia en una de sus columnas, para luego retornar una acción de ventana que muestra el wizard.

Vamos ahora a ver los contenidos del archivo stock_view.xml donde definimos el botón que va a mostrar el wizard:

   <record model="ir.ui.view" id="stock_picking_po_form">
<field name="name">stock.picking.po.form</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"></field>
<field name="arch" type="xml">
<xpath expr="//header" position="inside" >
<button name="btn_link_purchase_order"
type="object"
attrs="{'invisible': ['|',('picking_type_code','!=','incoming'),('state','!=','done')]}"
string="Add to PO" />
</xpath>
</field>
</record>

Ahora tenemos que crear un directorio llamado wizard para definir en el mismo el modelo del wizard junto con su vista. Primero la vista del wizard que ira en el archivo xml wizard_view.xml

      <record id="picking_po__wizard_view" model="ir.ui.view">
<field name="name">Picking Purchase Order Wizard</field>
<field name="model">picking.po.wizard</field>
<field name="arch" type="xml">
<form string="Asignar PO a Transferencia">
<sheet>
<group>
<field name="picking_id" readonly="1"/>
<field name="partner_id" readonly="1"/>
<field name="purchase_order_id"
domain="[('partner_id','=',partner_id),
​('state','in',['purchase','done'])]"
options="{'no_create': True, 'no_create_edit':True}"
/>
</group>
</sheet>
<footer>
<button string="Confirm" name="btn_confirm"
​type="object" default_focus="1"
​class="btn-primary"/>
<button string="Cancel" class="btn-secondary"
​special="cancel"/>
</footer>
</form>
</field>
</record>

Como pueden ver, es un simple formulario con dos botones. Uno para cancelar el wizard, el otro para confirmar la acción (que es lo que vamos a hacer a continuación en la definición del modelo)

class PickingPOWizard(models.TransientModel):
_name = 'picking.po.wizard'
_description = 'picking.po.wizard'

picking_id = fields.Many2one('stock.picking','Transferencia')
partner_id = fields.Many2one('res.partner','Proveedor',
​related='picking_id.partner_id')
purchase_order_id = fields.Many2one('purchase.order','Orden de Compra')

def btn_confirm(self):
if not self.purchase_order_id:
raise ValidationError(_('Seleccione PO'))
for move in self.picking_id.move_ids_without_package:
pol = self.purchase_order_id.order_line.filtered(
​lambda l: l.product_id.id == move.product_id.id)
if not pol:
raise ValidationError('Producto no se encuentra en PO')
move.purchase_line_id = pol[0].id

La acción ocurre en el botón de confirmación. Cuando se clickea el botón se invoca el método btn_confirm donde para cada línea de la transferencia se busca el mismo producto en las líneas de la PO. Si no se encuentra el producto se emite un mensaje de error, ahora si lo encuentra se asigna el ID de la línea de la orden de compra a la línea de la transferencia.

Por último, agregamos un archivo ir.model.access.csv con las definiciones de seguridad:

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_wizard,pick.po.wizard,model_picking_po_wizard,stock.group_stock_user,1,1,1,1

Cómo funciona?

Supongamos tenemos una orden de compra para Azure Interior donde solicitamos cinco unidades de un producto A1


Luego de confirmarla, hacemos de forma manual una recepción por tres unidades


Clickeamos el boton "Agregar a PO" y veremos el siguiente dialog box del wizard


Donde seleccionamos la PO que querramos seleccionar. Luego si controlamos la PO veremos que la misma fue actualizada con la recepción


El código de ejemplo lo pueden encontrar en nuestro github. Este método para relacionar objetos es bastante común para relacionar transacciones creadas manualmente en forma separada. Por ejemplo facturas de proveedor y órdenes de compra. Lo que quisimos hacer con este post es mostrar el mecanismo de funcionamiento del mismo por medio de un ejemplo.


Gustavo Orrillo 24 de setiembre de 2023
Compartir
Archivar
Identificarse dejar un comentario