Creando órdenes de compra con un archivo Excel
Muchas veces nos encontramos que debemos crear documentos o datos maestros a partir de un archivo Excel. Pueden ser facturas, órdenes de venta, actualizar precios de productos, etc. Como podemos hacerlo en Odoo? Son escenarios en los que debemos desarrollar módulos para que permitan hacerlo, los cuales 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.
Donde tenemos una línea por cada artículo que se compra. Y el proveedor indica el código de proveedor del artículo que se compra.
Lo primero que hay que 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 lo que hacemos es agregar 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>
Aca extendemos el formulario de la orden de compra, y hacemos dos cosas. Primero agregamos un botón para procesar el archivo (con 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. Luego agregamos un tab donde tenemos el campo po_file, donde gracias al widget binary podemos cargar el archivo Excel.
Como se imaginarán, la lógica transcurre en el método process_file, el cual tiene el siguiente código.
def process_siderar_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 es chequear que el campo po_file no esté vacío.
if not self.po_file:
raise ValidationError('No hay archivo cargado')
Seguidamente, 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))
Seguidamente, iteramos en la primer hoja de la planilla cada una de las filas y cada una de las columnas. Salteamos la primer fila ya que contiene los nombres de las columnas. 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.
Luego iteramos los contenidos de ambas listas, y por cada fila que se itera: 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), 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 pueden ver no es complicado. 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.
Acerca de:
Gustavo Orrillo
Apasionado de la programación, implementa Odoo para distintos tipos de negocios desde el año 2010. En Moldeo Interactive es Socio fundador y Programador; además de escribir en el Blog sobre distintos temas relacionados a los desarrollos que realiza.