Cargando saldos de cuenta corriente en Odoo con xmlrpc

Gustavo Orrillo
- 21/05/2022 - 5 min. de lectura

Como pudimos ver en el post "Como entender la cuenta corriente de Odoo - Behind the scenes", la cuenta corriente se mantiene en la contabilidad de Odoo. Por ende para cargar los saldos de cuenta corriente de los clientes o de los proveedores (por ejemplo cuando se migra de sistema), se deben crear los asientos contables en los cuales se crean los saldos deudores/acreedores de clientes/proveedores.

En este post vamos a ver un ejemplo en el cual vamos a leer un archivo de Excel con los saldos de las facturas impagas de clientes. El archivo tiene las siguientes columnas:


En este archivo tenemos las siguientes columnas: cliente (con el código de referencia del cliente), referencia (con el nro de factura del cliente), fecha de la factura y saldo sin cobrar. Habiendo dicho eso, el primer paso es leer el archivo en formato XLSX

#!/usr/bin/python3
from xmlrpc import client
import openpyxl
from datetime import datetime
url = 'http://localhost:8069'
common = client.ServerProxy('{}/xmlrpc/2/common'.format(url))
res = common.version()
dbname = 'mrputilsv1'
user = 'admin'
pwd = 'admin'
uid = common.authenticate(dbname, user, pwd, {})
# prints Odoo version and UID to make sure we are connected
print(res)
print(uid)
models = client.ServerProxy('{}/xmlrpc/2/object'.format(url))
# Define la variable para leer el workbook
workbook = openpyxl.load_workbook("saldos_cuenta_corriente.xlsx")
# Define variable para la planilla activa
worksheet = workbook.active
# Itera las filas para leer los contenidos de cada celda
rows = worksheet.rows
for x,row in enumerate(rows):
    # Saltea la primer fila porque tiene el nombre de las columnas
    if x == 0:
        continue
    # Lee cada una de las celdas en la fila
    vals = {}
    for i,cell in enumerate(row):
        print(i,cell.value)

Luego, por cada registro leido del archivo Excel, procedemos a crear un asiento contable. El diario a asignarse va a ser el de "Operaciones miscelaneas" que es del tipo varios y permite crear todo tipo de asientos contables. El código para generar los asientos es el siguiente:

#!/usr/bin/python3 
from xmlrpc import client
import openpyxl
from datetime import datetime

url = 'http://localhost:8069'
common = client.ServerProxy('{}/xmlrpc/2/common'.format(url))
res = common.version()
dbname = 'mrputilsv1'
user = 'admin'
pwd = 'admin'
uid = common.authenticate(dbname, user, pwd, {})

# prints Odoo version and UID to make sure we are connected
print(res)
print(uid)
models = client.ServerProxy('{}/xmlrpc/2/object'.format(url))

# Busca el ID del journal MISC
journal_id = models.execute_kw(dbname,uid,pwd,'account.journal','search',[[['code','=','MISC']]])

# Define la variable para leer el workbook
workbook = openpyxl.load_workbook("saldos_cuenta_corriente.xlsx")
# Define variable para la planilla activa
worksheet = workbook.active

# Itera las filas para leer los contenidos de cada celda
rows = worksheet.rows
for x,row in enumerate(rows):
    # Saltea la primer fila porque tiene el nombre de las columnas
    if x == 0:
        continue
    # Lee cada una de las celdas en la fila
    vals = {}
    for i,cell in enumerate(row):
        print(i,cell.value)
        ref = ''
        if i == 1:
            col = 'ref'
            vals[col] = cell.value
        if i == 2:
            col = 'date'
            vals[col] = cell.value
            if type(vals[col]) == datetime:
                vals[col] = str(vals[col])
        vals['journal_id'] = journal_id[0]
        vals['name'] = vals.get('ref')
    move_id = models.execute_kw(dbname,uid,pwd,'account.move','search',[[['ref','=',vals.get('ref')]]])
    if not move_id:
        move_id = models.execute_kw(dbname,uid,pwd,'account.move','create',[vals])
    else:
        # El asiento ya fue creado, se pasa a la siguiente fila
        continue
    print(move_id)

y genera los asientos de la siguiente manera:


Ahora procederemos a crear las líneas de los asientos contables (o apuntes contables como están traducidos en Odoo). El objeto a actualizar es account.move.line. Cada asiento creado tendrá dos líneas, una de débito y otro de crédito. Y el monto a debitar/acreditar será el de la deuda. Como en este caso estamos hablando de la deuda de clientes; se debe debitar la cuenta "Deudores por venta" (que es del tipo cobrable) por el monto de la deuda, y acreditar la cuenta Ajustes de Capital (la elegí arbitrariamente, puede utilizar la cuenta que le indique su contador). Tambien debemos indicar el cliente de la línea de la deuda. Por último, y no por eso menos importante, debemos indicar el contexto check_move_validity con el valor False. Sino cuando creemos el apunte contable tendremos un error de Odoo indicando que el asiento no se encuentra balanceado.

Para hacer todo esto, tendremos el siguiente código en el cual se crea el asiento y los dos apuntes contables. Por último, se confirma el asiento contable pare registrar la deuda en la cuenta corriente del cliente.

#!/usr/bin/python3
from xmlrpc import client
import openpyxl
from datetime import datetime
url = 'http://localhost:8069'
common = client.ServerProxy('{}/xmlrpc/2/common'.format(url))
res = common.version()
dbname = 'demo_contract_ar'
user = 'admin'
pwd = 'admin'
uid = common.authenticate(dbname, user, pwd, {})
# prints Odoo version and UID to make sure we are connected
print(res)
print(uid)
models = client.ServerProxy('{}/xmlrpc/2/object'.format(url))
# Busca el ID del journal MISC
journal_id = models.execute_kw(dbname,uid,pwd,'account.journal','search',[[['code','=','MISC']]])
# Busca cuenta ajuste de capital (codigo 3.1.1.01.020)
credit_account_id = models.execute_kw(dbname,uid,pwd,'account.account','search',[[['code','=','3.1.1.01.020'],['company_id','=',1]]])
# Busca cuenta deudores por venta (codigo 3.1.1.01.020)
debit_account_id = models.execute_kw(dbname,uid,pwd,'account.account','search',[[['code','=','1.1.3.01.010'],['company_id','=',1]]])
# Define la variable para leer el workbook
workbook = openpyxl.load_workbook("saldos_cuenta_corriente.xlsx")
# Define variable para la planilla activa
worksheet = workbook.active
# Itera las filas para leer los contenidos de cada celda
rows = worksheet.rows
for x,row in enumerate(rows):
    # Saltea la primer fila porque tiene el nombre de las columnas
    if x == 0:
        continue
    # Lee cada una de las celdas en la fila
    vals = {}
    partner_id = None
    amount = 0
    for i,cell in enumerate(row):
        print(i,cell.value)
        ref = ''
        if i == 0:
            # Busca el cliente por el codigo de referencia
            partner_id = models.execute_kw(dbname,uid,pwd,'res.partner','search',[[['ref','=',cell.value]]])
            if not partner_id:
                continue
        if i == 1:
            col = 'ref' 
            vals[col] = cell.value
        if i == 2:
            col = 'date'
            vals[col] = cell.value
            if type(vals[col]) == datetime:
                vals[col] = str(vals[col])
        if i == 3:
            amount = float(cell.value)
        vals['journal_id'] = journal_id[0]
        vals['name'] = vals.get('ref')
        vals['company_id'] = 1
        move_id = models.execute_kw(dbname,uid,pwd,'account.move','search',[[['ref','=',vals.get('ref')]]])
        if not move_id:
            move_id = models.execute_kw(dbname,uid,pwd,'account.move','create',[vals])
        else:
            # El asiento ya fue creado, se pasa a la siguiente fila
            continue
        print(move_id)
        vals_debit = {
            'company_id': 1,
            'move_id': move_id,
            'date': vals.get('date'),
            'journal_id': vals.get('journal_id'),
            'account_id': debit_account_id[0],
            'partner_id': partner_id[0],
            'name': vals.get('ref'),
            'debit': amount,
            'credit': 0,
            }
    debit_id = models.execute_kw(dbname,uid,pwd,'account.move.line','create',[vals_debit],{'context' :{'check_move_validity': False}})
    vals_credit = {
        'company_id': 1,
        'move_id': move_id,
        'date': vals.get('date'),
        'journal_id': vals.get('journal_id'),
        'account_id': credit_account_id[0],
        'partner_id': partner_id[0],
        'name': vals.get('ref'),
        'debit': 0,
        'credit': amount,
        }
    credit_id = models.execute_kw(dbname,uid,pwd,'account.move.line','create',[vals_credit],{'context' :{'check_move_validity': False}})
    post_id = models.execute_kw(dbname,uid,pwd,'account.move','action_post',[move_id])
    print(post_id)

Lo que crea asientos de la siguiente manera


Ahora, si queremos comprobar que la cuenta corriente este actualizada, creemos una nota de crédito para de $1,800 para el cliente Azure Interior. Al validarla, veremos que tenemos como saldo pendiente los asientos que acabamos de crear:


Líneas más, líneas menos... este es el proceso para cargar saldos de cuenta corriente. En este caso se ejemplificó clientes, pero cambiando las cuentas y los saldos se pueden cargar los saldos de proveedores. Lo mismo cualquier deuda. Y para cargar deuda en otra moneda (por ejemplo USD) solo necesita un par de modificaciones. Recomendamos si usted no tiene mucha experiencia, probar este script de migración con un contador al lado. Para asegurarse que obtiene los resultados esperados. 

Por último, el código se encuentra disponible para su descarga en github.



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.