Como crear órdenes de compra con un archivo Excel

12 de julio de 2023 por
Gustavo Orrillo
| Sin comentarios aún
 

A veces debemos crear documentos o datos maestros a partir de un archivo Excel (por lo general es actualizar datos maestros). Pueden ser facturas, órdenes de venta, actualizar precios de productos (esta es una realidad argentina muy frecuente, la distribuidora que actualiza precios en base a un archivo de un iportador), etc. 

Como podemos hacer esto en Odoo? En estos casos debemos desarrollar módulos para hacerlo (implementarlo por medio de procedimientos administrativos e importando manualmente archivos es una receta para el fracaso). Estos módulos implementarán una lógica similar a la siguiente. Supongamos que recibimos un archivo Excel con los items de de la orden de compra, el cual tiene el siguiente formato.



En este archivo tenemos una línea por cada artículo que se compra. Y en la primer columna el proveedor indica el código de proveedor del artículo que se compra.

Lo primero que debemos hacer es crear en el objeto de la orden de compra (purchase.order) un campo binario donde se almacenará el archivo Excel.


po_file = fields.Binary('Archivo Orden de Compra')


Y luego agregamos un tab al formulario de la orden de compra donde cargaremos el archivo Excel de la orden de compra

<record id="cre_purchase_order_form" model="ir.ui.view">
<field name="name">cre.purchase.order.form</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/> <!--hereda de la vista de usuarios padre id externo-->
<field name="arch" type="xml">
<xpath expr="//header" position="inside">
<button name="process_file" states="draft"
type="object" string="Procesar archivo" />
</xpath>
<xpath expr="//notebook" position="inside">
<page string="Archivo Proveedor">
<group>
<field name="po_file" widget="binary"
attrs="{'readonly': [('state','!=','draft')]}"/>
<field name="errores_archivo_siderar" readonly="1" />
</group>
</page>
</xpath>
</field>
</record>

Con esta vista extendemos la vista del formulario de la orden de compra, y en esta vista hacemos dos cosas. Primero agregamos un botón para procesar el archivo (gracias a xpath localizamos el nodo header del formulario y le agregamos un botón que esta visible en el estado draft e invoca al método process_file). Después agregamos un tab donde tenemos el campo po_file, donde gracias al widget binary podemos cargar el archivo Excel. 

La lógica real transcurre en el método process_file, el cual tiene el siguiente código.

def process_file(self):
self.ensure_one()
if not self.po_file:
raise ValidationError('No hay archivo cargado')
wb = xlrd.open_workbook(file_contents = base64.decodestring(self.po_file))
for s in wb.sheets()[0]:
products = []
qtys = []
for row in range(s.nrows):
if row == 0:
continue
for col in range(s.ncols):
if col == 0:
products.append(s.cell(row,col).value)
if col == 1:
qtys.append(s.cell(row,col).value)
if len(qtys) != len(products):
raise ValidationError('Problemas en lectura del archivo')
uom_t = self.env.ref('uom.product_uom_ton')
if not uom_t:
raise ValidationError('No encuentra la unidad de medida tonelada\nContacte el administrador')
for i,product in enumerate(products):
product_id = self.env['product.product'].\
​search([('default_code','=',product)])
vals_line = {
'order_id': self.id,
'product_id': product_id.id,
'product_uom': uom_t.id,
'name': product_id.name,
'product_qty': int(qtys[i])
}
line_id = self.env['purchase.order.line'].create(vals_line)

Lo primero que hacemos en el método es chequear que el campo po_file no esté vacío. 


if not self.po_file:
raise ValidationError('No hay archivo cargado')

Depues hacemos que el módulo xlrd lea el contenido binario del campo po_file

wb = xlrd.open_workbook(file_contents = base64.decodestring(self.po_file))

Luego iteramos en la primer hoja de la planilla cada una de las filas y cada una de las columnas de la misma. Solo salteamos la primer fila debido a que contiene los nombres de las columnas (podríamos dar la opción en el módulo que la saltee o no). Si la celda que estamos iterando es la primer celda de la fila, asignamos su contenido a la lista products, caso contrario (segunda columna) va a la lista qtys

    for s in wb.sheets()[0]:
products = []
qtys = []
for row in range(s.nrows):
if row == 0:
continue
for col in range(s.ncols):
if col == 0:
products.append(s.cell(row,col).value)
if col == 1:
qtys.append(s.cell(row,col).value)

De esta manera vamos a tener dos listas: products y qtys donde tenemos en forma coordinada los contenidos de cada fila. Por cada fila crearemos una fila en la orden de compra. Iteramos los contenidos de ambas listas, y por cada fila que se procesa: se busca el producto correspondiente por medio del código (campo default_code), se busca la unidad de medida tonelada por medio de la referencia (asumimos que el proveedor nos envía toneladas, la verdad pueden buscar la unidad de medida que se les plazca o utilizar la unidad default), creamos un diccionario con los valores con los que se crearán la línea de la orden de compra, y paso siguiente se crea la orden de compra:

    for i,product in enumerate(products):
product_id = self.env['product.product'].search(\
​[('default_code','=',product)])
vals_line = {
'order_id': self.id,
'product_id': product_id.id,
'product_uom': uom_t.id,
'name': product_id.name,
'product_qty': int(qtys[i])
}
line_id = self.env['purchase.order.line'].create(vals_line)

Como ven no es dificil. Primero se debe entender la lógica de crear objetos purchase.order.line. Y luego comprender como con xlrd leer los contenidos de los archivos binarios. Por último, se debe entender como iterar una planilla de cálculo usando xlrd. Una vez que se entienden esas piezas, uno puede resolver el problema de crear objetos en base a archivos Excel.

Gustavo Orrillo 12 de julio de 2023
Compartir
Archivar
Identificarse dejar un comentario