Home > Eclipse, Financial Accounting, OpenERP, Postgresql, Python, XML > Hire Purchase: Module Development example in openerp

Hire Purchase: Module Development example in openerp

In this tutorial, I will share how module develops in openerp. First I will explain the functionality of hire purchase (what we are going to develop), second I will develop hire purchase module in openerp. A simple explanation of hire purchase module is sale of goods in installment. In my example, I will ignore the interest portion of hire purchase. I just spread the price of good in number of monthly installment. I can say that, this module is a demo version of hire purchased without installment. In future I will extend this module with interest. In this tutorial I will focus on step by step development of module in openerp.
What hire purchase is read out the wiki about hire purchase, for detail of hire purchase also read wikieducator.

In this tutorial I will cover; how to create openerp objects (in .py file), interaction with object using openerp views (tree and form), openerp wizards and menuitem.

Step 1:

Create the following files and folders.

a. Folder having named ‘hirepurchase’ and 2nd folder having name ‘wizard’ in ‘hirepurchase’ folder.

b. Create the files in hirepurchase folder, i.e. __init__.py, __terp__.py, hirepurchase_view.xml and hirepurchase.py.

c. Create the files in wizard folder, i.e. __init__.py and wizard_pay_hirepurchaseinvoice.py

File structure: hirepurchase folder contains one folder (wizard) and four files (__init__.py, __terp__.py, hirepurchase_view.xml and hirepurchase.py). Wizard folder contains two files (__init__.py and wizard_pay_hirepurchaseinvoice.py)

Step 2:

Open the __init__.py in gedit (linux) or notepad (windows). Write the following code.

import hirepurchase

Step 3:

Open the __terp__.py in gedit (linux) or notepad (windows). Write the following code.

        "name" : "hirepurchase",
        "version" : "0.1",
        "author" : "Mohsin",
        "website" : "https://mohsinpage.wordpress.com",
        "category" : "Accounting",
        "description": """  """,
        "depends" : ['base','account','base_report_designer'],
        "init_xml" : [ ],
        "demo_xml" : [ ],
        "update_xml" : ['hirepurchase_view.xml'],
        "installable": True

Name of module will be ‘hireurchase’. Hirepuchase depends on the on other two modules i.e. base and account. Hirepurchase module use an xml file having name hirepurchase.xml, in this xml file we will declare different type of views available in openerp like tree, form, button and menuitem etc other then wizards.

Step 3:
Open the hirepurchase.py in gedit (linux) or notepad (windows). Write the following code.

from osv import osv, fields
import netsvc
import wizard

l = netsvc.Logger()

class account_hirepurchase(osv.osv):
    def onchange_invoice_id(self, cr, uid, ids,invoice, context={}):
#        l.notifyChannel("MyLog: in method= ",netsvc.LOG_INFO,'')
        res = {'value': {}}
        if not invoice:
            return res
            res['value']['total_amount'] = rec_of_invoice.residual
            return res

    _name = 'account.hirepurchase'
    _columns = {
        'name': fields.many2one('res.partner','Partner',domain=[('active','=','TRUE'),('customer','=','TRUE')]),
        'invoice': fields.many2one('account.invoice','Invoice',domain="[('state','=','open'),('partner_id','=',name)]"),
        'number_installment': fields.integer('Installments'),
        'total_amount': fields.float('Residual Amount'),
        'hirepurchase_lines': fields.one2many('account.hirepurchaseline','hirepurchaseid'),
        'status': fields.selection([('unpaid', 'Un Paid'),
                                   ('partial_paid', 'Partial Paid'),('fully_paid', 'Fully Paid')], 'Status',
                                   required=True, help="""Help Text"""),

    def create_installment(self,cr, uid, ids, context={}):
        current_form_obj=self.browse(cr, uid, ids, context)[0]
        if not current_form_obj:
            return False
            while i<=n:
        return True

class account_hirepurchaseline(osv.osv):
    _name = 'account.hirepurchaseline'
    _columns = {
        'hirepurchaseid': fields.many2one('account.hirepurchase','Hire Purchase ID',ondelete='cascade'),
        'due_amount': fields.float('Amount Due'),
        'installment_no': fields.integer('Installment No'),
        'status': fields.selection([('due', 'Due'),
                                   ('paid', 'Paid')], 'Status',
                                   required=True, help="""Help Text"""),
        'voucher': fields.many2one('account.move','Payment Voucher',ondelete='restrict'),

In this file we created two objects i.e. account.hirepurchase and account.hirepurchaseline.

This object has following attributes and functionalities.
Name: its store the id of res.partner object means it’s the foreign key of res.partner object. One res.partner can occur many time in account.hirepurchase.
Invoice: All the account.invoice related to res.partner will be available to store its id in invoice column, means invoice is the foreign key of account.invoice in account.hirpurchase. Only the open invoice will be entertain in hire purchases.
Number_installment: according to rule of hire purchase, each res.partner is allowed to pay in installments for his purchases of above mention price. Company will decide the number of installment for above res.partner on his purchase. It’s an integer type of column
Total_amount: its store the total residual value receivable from res.partner for his purchase in above accout.invoice. It’s a float type of column
Status: status show the three values un paid, partially paid and fully paid. It’s a selection type of column.
Onchnage_invoice_id(): In the form of account.hirepurchase, when invoice will selected or change from available list of invoice then the residual value of selected invoice will be assign to the text box of total_amount of account.hirepurchase .
Create_installment(): In the form of account.hirepurchase, there is a button having name ‘create the installments’. When this button will click, this functionality will be call. This function will create a number of installments, amount for each installment and status of each installment.


This object has following attributes.
Due_amount: each installment amount receivable for monthly basis from res.partner.
Installment_no: it will store the no of installment.
Status: its store the status of each installment either paid or due.
Voucher: when res.partner pays his installment, the id of that receipt voucher will be store in this column, with installment status paid.

Below is the example of account.hirepurchase and account.hirepurchaseline:

Object = Account.hirepurchase Name: Ali Yaseen Invoice: Purchase Computer
Number_installment: 4 Total_amount: 25000
Status: Partially Paid
Functionality One: onchange_invoice_id()
Functionality Two: create_installment()
Object = Account.hirepurchaseline Due_amount Installment_no Status voucher
6250 1 Paid Payment receipt
6250 2 Due
6250 3 Due
6250 4 Due

Step 4:
Open the hirepurchase_view.xml in gedit (linux) or notepad (windows). Write the code in xml like below for tree, form, action & menuitem for account.hirepurchase object and similarly xml code for tree, form and action for account.hirepurchaseline.

Form of hire purchase

"    <record model="ir.ui.view" id="view_account_hirepurchase_form">
        <field name="name">Hire Purchase Form</field>
        <field name="model">account.hirepurchase</field>
        <field name="type">form</field>
        <field name="arch" type="xml">
            <form string="account.hirepurchase">
            	<separator colspan="4" string="Invoice"/>
                <field name="name" select="1"/>
                <field name="invoice" select="2"  on_change="onchange_invoice_id(invoice)"/>
                <field name="total_amount" select="0"/>
                <separator colspan="4" string="Create Installment"/>
                <field name="number_installment" select="0"/>
                <button name="create_installment" string="Create Installment" type="object"/>
                <field name="hirepurchase_lines" colspan="4" select="0" nolabel="1"/>
                <field name="status" colspan="4"/>

Tree of hire purchase

    <record model="ir.ui.view" id="view_account_hirepurchase_tree">
        <field name="name">Hire Purchase Tree</field>
        <field name="model">account.hirepurchase</field>
        <field name="type">tree</field>
        <field name="arch" type="xml">
            <tree string="account.hirepurchase">
                <field name="name"/>
                <field name="invoice"/>
                <field name="number_installment"/>
                <field name="total_amount"/>
				<field name="status"/>

Action of hire purchase

    <record model="ir.actions.act_window" id="action_account_hirepurchase">
        <field name="name">Hire Purchases</field>
        <field name="res_model">account.hirepurchase</field>
        <field name="view_type">form</field>
        <field name="view_mode">tree,form</field>

Menu of hire purchase

    <menuitem name="Financial Management/Invoices/Hire Purchases" id="menu_account_hirepurchase" action="action_account_hirepurchase"/>

Form of hire purchase lines

    <record model="ir.ui.view" id="view_account_hirepurchaseline_form">
        <field name="name">Hire Purchas Lines</field>
        <field name="model">account.hirepurchaseline</field>
        <field name="type">form</field>
        <field name="arch" type="xml">
            <form string="Hire Purchase Lines List">
                <field name="due_amount" select="2"/>
                <field name="installment_no" select="0"/>
				<field name="status" select="2"/>
				<field name="voucher"/>

Tree of hire purchase lines

    <record model="ir.ui.view" id="view_account_hirepurchaseline_tree">
        <field name="name">Hire Purchase Lines</field>
        <field name="model">account.hirepurchaseline</field>
        <field name="type">tree</field>
        <field name="arch" type="xml">
            <tree string="Hire Purchase Lines List">
                <field name="due_amount"/>
                <field name="installment_no"/>
				<field name="status"/>
				<field name="voucher"/>

Action of hire purchase lines

    <record model="ir.actions.act_window" id="action_account_hirepurchaseline">
        <field name="name">Hire Purchase Lines</field>
        <field name="res_model">account.hirepurchaseline</field>
        <field name="view_type">form</field>
        <field name="view_mode">tree,form</field>

So far, we can install this hire purchase module. But let me extent the functionality of this module, by introducing a wizards. Using this wizard, we don’t need to enter a receipt voucher. Just select the installment & bank book, this wizard will generate the receipt voucher in account.move.line .

Hire Purchase form, Hire Purchase Lines, Wizard

Hire Purchase Lines Form

Step 5:
Now in this step, we will create wizard, which will enter receipt voucher in account.move.line. First enter the accessing action in account.hirepurchase object of this wizard like below. This below line will be adding in same file hirepurchase_view.xml of hirepurchase folder.

    <wizard id="wizard_hirepurchaseinvoice_pay" model="account.hirepurchase" name="account.invoice.hirepurchasepay" string="Receipt Installment" groups="base.group_user"/>

Step 6:
Open the __init__.py of wizard folder not hirepurchase folder in gedit (for ubuntu) or noteoad (for windows). Write the below code.

import wizard_pay_hirepurchaseinvoice

Step 7:
Open the wizard_pay_hirepurchaseinvoice.pyof wizard folder in gedit (for ubuntu) or noteoad (for windows). Write the below code.

import wizard
import netsvc
import time
import pooler

l = netsvc.Logger()

installment_form = '''


installment_fields = {
    'Installment': {'string': 'Installment', 'type': 'many2one', 'relation':'account.hirepurchaseline', 'required':True},


pay_form = '''


pay_fields = {
    'amount': {'string': 'Amount paid', 'type':'float', 'required':True},
    'name': {'string': 'Entry Name', 'type':'char', 'size': 64, 'required':True},
    'date': {'string': 'Payment date', 'type':'date', 'required':True, 'default':lambda *args: time.strftime('%Y-%m-%d')},
    'journal_id': {'string': 'Journal/Payment Mode', 'type': 'many2one', 'relation':'account.journal', 'required':True, 'domain':[('type','=','cash')]},
    'voucher_type':{'string':"Voucher Type", 'type':'selection', 'selection':[('rec_voucher','Cash Receipt'),('bank_rec_voucher','Bank Receipt')],'required':True},
    'period_id': {'string': 'Period', 'type': 'many2one', 'relation':'account.period', 'required':True},
def _get_hirepurchase_id(self, cr, uid, data, context={}):
     l.notifyChannel("MyLog: = _get_hirepurchase_id",netsvc.LOG_INFO,data['id'])
     global hirepurchase_id
     l.notifyChannel("MyLog: = hirepurchase_id",netsvc.LOG_INFO,hirepurchase_id)
     l.notifyChannel("MyLog: = hirepurchase_id",netsvc.LOG_INFO,hirepurchase_id)
     return {}

def _get_amount_of_installment(self, cr, uid, data, context={}):
#     installment_id=form.get('Installment',False)
     installment=pooler.get_pool(cr.dbname).get('account.hirepurchaseline').browse(cr, uid, form.get('Installment',False), context)
     ids = pooler.get_pool(cr.dbname).get('account.period').find(cr, uid, context=context)
     period_id = False
     if len(ids):
         period_id = ids[0]
#     l.notifyChannel("MyLog: = installment_id",netsvc.LOG_INFO,installment_id)
     return {
        'amount': installment.due_amount

def _voucher_entry(self, cr, uid, data, context={}):
    journal=pooler.get_pool(cr.dbname).get('account.journal').browse(cr, uid, form.get('journal_id',True), context)
    l.notifyChannel("MyLog: = form.get('name',True) = ",netsvc.LOG_INFO,data['id'])
    partner =pooler.get_pool(cr.dbname).get('account.hirepurchase').browse(cr, uid, data['id'], context)
    l.notifyChannel("MyLog: = partner = ",netsvc.LOG_INFO,partner.name.property_account_receivable.id)
        {'account_id':journal.default_debit_account_id.id,'partner_id':partner.name.id,'period_id':form.get('period_id',True),'date':form.get('date',True),'centralization':'normal','journal_id':form.get('journal_id',True),'name':'Cash Receipt','credit':'0.00','state':'draft','move_id':new_move_id,'debit':form.get('amount',True),'ref':form.get('Installment',True)},
        {'account_id':partner.name.property_account_receivable.id,'partner_id':partner.name.id,'period_id':form.get('period_id',True),'date':form.get('date',True),'centralization':'normal','journal_id':form.get('journal_id',True),'name':'A/C Receivable','credit':form.get('amount',True),'state':'draft','move_id':new_move_id,'debit':'0.00','ref':form.get('Installment',True)},
    l.notifyChannel("MyLog: move_line = ",netsvc.LOG_INFO,move_line)
    for r in move_line:
    return {}

class wizard_pay_hirepurchaseinvoice(wizard.interface):
    states = {
        'init': {
            'actions': [],
            'result': {'type':'state', 'state':'show_form'}
        'show_form': {
            'actions': [],
            'result': {'type':'form', 'arch':installment_form, 'fields':installment_fields, 'state':[('end','Cancel'),('next','Next')]}
        'next': {
            'actions': [_get_amount_of_installment],
            'result': {'type':'form', 'arch':pay_form, 'fields':pay_fields, 'state':[('end','Cancel'),('pay','Payment')]}
        'pay': {
            'actions': [_voucher_entry],
            'result': {'type':'state', 'state':'end'}


Wizard First Form: Select the installment for receipt voucher

Wizard from 2: Receipt Voucher.

Step 8:
Copy the folder in addons of openerp, update module list in openerp. Install this module. This module is a beta version, its more functionality will be share soon.

Receipt voucher entered after wizard step

  1. December 15, 2010 at 5:41 am

    Modules Development in OpenERP: View, Events, Menu and Actions
    this post also help for xml based form, events, menu and actions development in openerp

  2. December 15, 2010 at 6:31 am

    Great work! Thanks for your time and effort explaining the details. Look forward to seeing how you handle the interest.

    • December 15, 2010 at 7:13 am

      Thanks timothy, will continue with more post on openerp as soon as i got time.

  3. Monther
    December 20, 2010 at 7:05 pm

    I got the Warning message “There is no reference available for account.hirepurchase”

    • December 21, 2010 at 8:19 am

      i did not get such error, can you plz tell me the line number with file name.so that i can suggest you the reason.thanks

  4. Monther
    December 21, 2010 at 5:29 pm

    the Warning message “There is no reference available for account.hirepurchase” is displayed when i am start update the module hirepurchase ( after fixing all errors ). i am using openerp ver 6.

    • December 23, 2010 at 5:20 am

      @monther brother just for you i created fresh database and install hirepurchase module from scratch. i haven’t got any ‘no reference available’ warning at all.
      neither i need to debug any thing. every thing going fine.
      note: but i am not using version 6 bcoz of clients aspects reasons.

  5. December 22, 2010 at 4:54 am

    Great, this is very helpful! Could you also show how to work with buttons?
    I need a button type object which will submit form into a table (but it isn’t like the original ‘save’ button from openerp).
    I kept on getting error message like “….. object has no attribute …..”
    thank you!

    • December 23, 2010 at 5:12 am

      @cillardnaid thanks or question:
      let suppose you have class having attributes name like below in openerp:
      class form_button(osv.osv):
      _name = “form.button”
      _description = “Button Testing”
      _columns = {
      ‘name’: fields.char(‘Enter Name’, size=64, required=True),

      def print_name(self,cr, uid, ids, context={}):
      current_obj=self.browse(cr, uid, ids, context)[0]
      print current_obj.name
      return True
      and now you want a button on form, after clicking on button a function will call that is define in above class i.e. print_name;
      now the form & button declaration and how this button will call name_print method of class form_button from xml. see below;

      < button name=”print_name” type=”object” string=”Print Name” />

      that is a simple function call from button click. you can use these button in advance like work flow enforcement etc.

      • December 23, 2010 at 5:27 am

        @cillardnaid why not you override the save method rather then using user define button like save.
        override save method, do your user define task in overrided save method. at last call the super save method.
        two reason for override save method
        1. its more simple rather than using button.
        2. your entry operator don’t need to click two time, first user define button, then actual save button.
        its just suggestion

  6. January 12, 2011 at 10:09 am

    Hi! works nicely. I’m trying to make the process a bit simpler. I’m thinking of extending the payment_terms to add a “hire purchase” checkbox so you can create a Hire Purchase payment term. Then any invoices with this payment term I will use a scheduled task to pass a journal to add the interest and insurance every month.

    Do you think that could work?

    I’m not so hot on the accounting though.. How do you pass a journal in the module?
    Also – can you link this debt to a specific invoice?

    Thanks for your insight.

  7. Shabneez Emamdhully
    March 7, 2011 at 9:14 am

    Hii mohsen thanks for the great piece of codes you are sharing..

    i am currently using OpenERP version 5.. And i’m working with Purchase mgt, stock mgt, manufacturing mgt and sales mgt.. In short the supply chain management of a company.. i am a student and doing my final yr project..

    I need to customize the product form like adding new fields like product color, discount and date of expiry..

    Can you plzzz help me with thisss!!! its really urgent…

    i thank you in advance..

    • March 8, 2011 at 3:18 pm

      its simple; add these line in *.py (where u want to add these fields )
      ‘product_color’ :fields.selection([(‘red’,’Red’),(‘green’,’Green’)],’Product Color’),
      ‘discount_amt’ : fields.integer(‘Discount Amount’),
      ‘date_expiry’ : fields.date(‘Discount Amount’),

      after this add these line ur xml document too

  8. shabneez
    March 9, 2011 at 7:51 pm

    hii mohsen i’ve try adding new fields by inheritance method… In the Addons folder, i have created a new folder called product_color

    i have created the _init.py file in which the piece of code is:

    import product_color

    in the _terp.py file:

    “name” : “Product color description”,
    “version” : “1.1”,
    “author” : “Tiny”,
    “category” : “Generic Modules/Inventory Control”,
    “depends” : [“base”, “process”, “product”],
    “demo_xml” : [],
    “update_xml” : [“product_color_desc.xml”],
    “description”: “””
    “active”: False,
    “installable”: True


    in the product_color.py:

    from osv import osv, fields
    import pooler

    class product.product(osv.osv):

    _table = “product_product”
    _name = ‘product.product’
    _description = ‘Product’
    _inherit = ‘product’
    _columns = {
    ‘color’: fields.char(‘color’, size=34, help=”Inherited field”),

    and finally the xml file product_color_desc.xml



    but when i have imported the module in openerp and scheduled for installation i’m getting an error msg

    Integrity Error

    null value in column “name” violates not-null constraint

    Can you please help me out

    kind regards

    • shabneez
      March 9, 2011 at 7:54 pm

      i have also added a new field “color” in the product_product table

      Alter product_product add column color varchar (30);

  9. Shab
    March 14, 2011 at 7:14 pm

    hii mohsin.. Plz help me out to clear this problem

    when i have created the 4 files init.py, terp.py, product_color.py, product_view.xml.. these were written in notepad and save as extension .py

    Should i compile these python files or just zip the folder with these 4 files and import it as a new module in openerp.?????

    • March 18, 2011 at 5:27 pm

      @shabneez i am writing a small tutorial on openerp inheritance. Soon i will complete as per time constraint. I will upload soon. That post will solve ur problem ok

  10. February 21, 2012 at 2:25 pm

    Greetings Mohsin,

    Have been watching your blog waiting for the next installment for this example.. would love to see how you would implement interest

  11. Harris
    October 31, 2014 at 10:20 am

    hi,great piece of work, looking forward to see the interest side.
    please this is in version 5 right ?…will it be possible that it can be done in other open erp versions like 7 or 8

    • February 11, 2015 at 6:09 pm

      We have built a full system on this for v8, but it is highly customised for the client. If you need a similar system let me know, perhaps I can help.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: