Hire Purchase: Module Development example in openerp 5.0.0.1
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" : "http://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
else:
rec_of_invoice=self.pool.get('account.invoice').browse(cr,uid,invoice,context=context)
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
else:
n=int(current_form_obj.number_installment)
i=1
while i<=n:
self.pool.get('account.hirepurchaseline').create(cr,uid,{
'hirepurchaseid':current_form_obj.id,
'due_amount':current_form_obj.total_amount/n,
'installment_no':i,
'status':'due',
})
i=i+1
return True
account_hirepurchase()
class account_hirepurchaseline(osv.osv):
_name = 'account.hirepurchaseline'
_rec_name='installment_no'
_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'),
}
account_hirepurchaseline()
In this file we created two objects i.e. account.hirepurchase and account.hirepurchaseline.
Account.hirpurchase:
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.
Account.Purchaseline:
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"/>
</form>
</field>
</record>"
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"/>
</tree>
</field>
</record>
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>
</record>
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"/>
</form>
</field>
</record>
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"/>
</tree>
</field>
</record>
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>
</record>
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 .
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 = '''
<form>
'''
installment_fields = {
'Installment': {'string': 'Installment', 'type': 'many2one', 'relation':'account.hirepurchaseline', 'required':True},
}
pay_form = '''
<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)
hirepurchase_id=data['id']
l.notifyChannel("MyLog: = hirepurchase_id",netsvc.LOG_INFO,hirepurchase_id)
return {}
def _get_amount_of_installment(self, cr, uid, data, context={}):
form=data['form']
# 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 {
'period_id':period_id,
'amount': installment.due_amount
}
def _voucher_entry(self, cr, uid, data, context={}):
form=data['form']
move_obj=pooler.get_pool(cr.dbname).get('account.move')
move_line_obj=pooler.get_pool(cr.dbname).get('account.move.line')
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)
move={'name':"/",'ref':form.get('Installment',True),'period_id':form.get('period_id',True),'journal_id':form.get('journal_id',True),'type':form.get('voucher_type',True),'date':form.get('date',True),'to_check':False}
new_move_id=move_obj.create(cr,uid,move,context)
l.notifyChannel("MyLog: = partner = ",netsvc.LOG_INFO,partner.name.property_account_receivable.id)
move_line=[
{'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:
new_move_line_id=move_line_obj.create(cr,uid,r,context)
hire_purchase_line_obj=pooler.get_pool(cr.dbname).get('account.hirepurchaseline')
vals={'status':'paid','voucher':new_move_id}
hire_purchase_line_obj.write(cr,uid,form.get('Installment',True),vals,context=None)
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_pay_hirepurchaseinvoice('account.invoice.hirepurchasepay')
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.





Modules Development in OpenERP: View, Events, Menu and Actions
http://mohsinpage.wordpress.com/2010/10/06/modules-development-in-openerp-view-events-menu-and-actions/
this post also help for xml based form, events, menu and actions development in openerp
Great work! Thanks for your time and effort explaining the details. Look forward to seeing how you handle the interest.
Thanks timothy, will continue with more post on openerp as soon as i got time.
I got the Warning message “There is no reference available for account.hirepurchase”
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
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.
@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.
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!
@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
form_button()
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.
enjoy
@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
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.
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..
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
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”),
}
product_product()
and finally the xml file product_color_desc.xml
product.product.expense.form
product.product
form
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
i have also added a new field “color” in the product_product table
Alter product_product add column color varchar (30);
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.?????
@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
Greetings Mohsin,
Have been watching your blog waiting for the next installment for this example.. would love to see how you would implement interest