1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
25 from osv import fields, osv
26 import decimal_precision as dp
28 from tools.misc import currency
29 from tools.translate import _
31 from datetime import datetime
32 from dateutil.relativedelta import relativedelta
34 from tools import config
36 class account_payment_term(osv.osv):
37 _name = "account.payment.term"
38 _description = "Payment Term"
40 'name': fields.char('Payment Term', size=64, translate=True, required=True),
41 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the payment term without removing it."),
42 'note': fields.text('Description', translate=True),
43 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
46 'active': lambda *a: 1,
50 def compute(self, cr, uid, id, value, date_ref=False, context={}):
52 date_ref = datetime.now().strftime('%Y-%m-%d')
53 pt = self.browse(cr, uid, id, context)
56 for line in pt.line_ids:
57 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
58 if line.value == 'fixed':
59 amt = round(line.value_amount, prec)
60 elif line.value == 'procent':
61 amt = round(value * line.value_amount, prec)
62 elif line.value == 'balance':
63 amt = round(amount, prec)
65 next_date = datetime.strptime(date_ref, '%y-%m-%d') + relativedelta(days=line.days)
67 next_date += relativedelta(day=line.days2)
69 next_date += relativedelta(day=line.days2, months=1)
70 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
74 account_payment_term()
76 class account_payment_term_line(osv.osv):
77 _name = "account.payment.term.line"
78 _description = "Payment Term Line"
80 'name': fields.char('Line Name', size=32, required=True),
81 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the payment term lines from the lowest sequences to the higher ones"),
82 'value': fields.selection([('procent', 'Percent'),
83 ('balance', 'Balance'),
84 ('fixed', 'Fixed Amount')], 'Value',
85 required=True, help="""Example: 14 days 2%, 30 days net
86 1. Line 1: percent 0.02 14 days
87 2. Line 2: balance 30 days"""),
89 'value_amount': fields.float('Value Amount', help="For Value percent enter % ratio between 0-1."),
90 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
91 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
92 'days2': fields.integer('Day of the Month', required=True, help="Day of the month, set -1 for the last day of the current month. If it's positive, it gives the day of the next month. Set 0 for net days (otherwise it's based on the beginning of the month)."),
93 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
96 'value': lambda *a: 'balance',
97 'sequence': lambda *a: 5,
98 'days2': lambda *a: 0,
102 def _check_percent(self, cr, uid, ids, context={}):
103 obj = self.browse(cr, uid, ids[0])
104 if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
109 (_check_percent, _('Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% '), ['value_amount']),
112 account_payment_term_line()
115 class account_account_type(osv.osv):
116 _name = "account.account.type"
117 _description = "Account Type"
119 'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
120 'code': fields.char('Code', size=32, required=True),
121 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of account types."),
122 'partner_account': fields.boolean('Partner account'),
123 'close_method': fields.selection([('none', 'None'), ('balance', 'Balance'), ('detail', 'Detail'), ('unreconciled', 'Unreconciled')], 'Deferral Method', required=True),
124 'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign on Reports', required=True, help='Allows you to change the sign of the balance amount displayed in the reports, so that you can see positive figures instead of negative ones in expenses accounts.'),
127 'close_method': lambda *a: 'none',
128 'sequence': lambda *a: 5,
129 'sign': lambda *a: 1,
132 account_account_type()
134 def _code_get(self, cr, uid, context={}):
135 acc_type_obj = self.pool.get('account.account.type')
136 ids = acc_type_obj.search(cr, uid, [])
137 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context)
138 return [(r['code'], r['name']) for r in res]
140 #----------------------------------------------------------
142 #----------------------------------------------------------
144 class account_tax(osv.osv):
145 _name = 'account.tax'
148 class account_account(osv.osv):
149 _order = "parent_left"
150 _parent_order = "code"
151 _name = "account.account"
152 _description = "Account"
155 def search(self, cr, uid, args, offset=0, limit=None, order=None,
156 context=None, count=False):
161 while pos < len(args):
163 if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
164 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
165 if args[pos][0] == 'journal_id':
169 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
170 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
171 args[pos] = ('type','not in',('consolidation','view'))
173 ids3 = map(lambda x: x.id, jour.type_control_ids)
174 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
175 ids1 += map(lambda x: x.id, jour.account_control_ids)
176 args[pos] = ('id', 'in', ids1)
179 if context and context.has_key('consolidate_childs'): #add consolidated childs of accounts
180 ids = super(account_account, self).search(cr, uid, args, offset, limit,
181 order, context=context, count=count)
182 for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
183 ids.append(consolidate_child.id)
186 return super(account_account, self).search(cr, uid, args, offset, limit,
187 order, context=context, count=count)
189 def _get_children_and_consol(self, cr, uid, ids, context={}):
190 #this function search for all the children and all consolidated children (recursively) of the given account ids
191 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
193 for rec in self.browse(cr, uid, ids2, context=context):
194 for child in rec.child_consol_ids:
195 ids3.append(child.id)
197 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
200 def __compute(self, cr, uid, ids, field_names, arg, context={}, query=''):
201 #compute the balance/debit/credit accordingly to the value of field_name for the given account ids
203 'balance': "COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance ",
204 'debit': "COALESCE(SUM(l.debit), 0) as debit ",
205 'credit': "COALESCE(SUM(l.credit), 0) as credit "
207 #get all the necessary accounts
208 ids2 = self._get_children_and_consol(cr, uid, ids, context)
209 #compute for each account the balance/debit/credit from the move lines
212 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
216 wheres.append(query.strip())
218 wheres.append(aml_query.strip())
219 query = " AND ".join(wheres)
221 cr.execute("SELECT l.account_id as id, " +\
222 ' , '.join(map(lambda x: mapping[x], field_names)) +
224 "account_move_line l " \
226 "l.account_id =ANY(%s) " \
228 " GROUP BY l.account_id",(ids2,))
230 for res in cr.dictfetchall():
231 accounts[res['id']] = res
234 # consolidate accounts with direct children
235 brs = list(self.browse(cr, uid, ids2, context=context))
240 for child in current.child_id:
241 if child.id not in sums:
244 brs.insert(0, brs.pop(brs.index(child)))
249 for fn in field_names:
250 sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
252 sums[current.id][fn] += sum(sums[child.id][fn] for child in current.child_id)
258 def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
260 for rec in self.browse(cr, uid, ids, context):
261 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
264 def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
266 for record in self.browse(cr, uid, ids, context):
267 if record.child_parent_ids:
268 result[record.id] = [x.id for x in record.child_parent_ids]
270 result[record.id] = []
272 if record.child_consol_ids:
273 for acc in record.child_consol_ids:
274 if acc.id not in result[record.id]:
275 result[record.id].append(acc.id)
280 'name': fields.char('Name', size=128, required=True, select=True),
281 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
282 'code': fields.char('Code', size=64, required=True),
283 'type': fields.selection([
284 ('receivable', 'Receivable'),
285 ('payable', 'Payable'),
287 ('consolidation', 'Consolidation'),
289 ('closed', 'Closed'),
290 ], 'Internal Type', required=True, help="This type is used to differentiate types with "\
291 "special effects in Open ERP: view can not have entries, consolidation are accounts that "\
292 "can have children accounts for multi-company consolidations, payable/receivable are for "\
293 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
294 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
295 help="These types are defined according to your country. The type contains more information "\
296 "about the account and its specificities."),
297 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade'),
298 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
299 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
300 'child_id': fields.function(_get_child_ids, method=True, type='many2many', relation="account.account", string="Child Accounts"),
301 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Balance', multi='balance'),
302 'credit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Credit', multi='balance'),
303 'debit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Debit', multi='balance'),
304 'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."),
305 'shortcut': fields.char('Shortcut', size=12),
306 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
307 'account_id', 'tax_id', 'Default Taxes'),
308 'note': fields.text('Note'),
309 'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
310 'company_id': fields.many2one('res.company', 'Company', required=True),
311 'active': fields.boolean('Active', select=2, help="If the active field is set to true, it will allow you to hide the account without removing it."),
313 'parent_left': fields.integer('Parent Left', select=1),
314 'parent_right': fields.integer('Parent Right', select=1),
315 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
317 'This will select how the current currency rate for outgoing transactions is computed. '\
318 'In most countries the legal method is "average" but only a few software systems are able to '\
319 'manage this. So if you import from another software system you may have to use the rate at date. ' \
320 'Incoming transactions always use the rate at date.', \
322 'check_history': fields.boolean('Display History',
323 help="Check this box if you want to print all entries when printing the General Ledger, "\
324 "otherwise it will only print its balance."),
327 def _default_company(self, cr, uid, context={}):
328 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
330 return user.company_id.id
331 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
334 'type': lambda *a : 'view',
335 'reconcile': lambda *a: False,
336 'company_id': _default_company,
337 'active': lambda *a: True,
338 'check_history': lambda *a: True,
339 'currency_mode': lambda *a: 'current',
340 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
343 def _check_recursion(self, cr, uid, ids):
344 obj_self = self.browse(cr, uid, ids[0])
345 p_id = obj_self.parent_id and obj_self.parent_id.id
346 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
349 cr.execute('select distinct child_id from account_account_consol_rel where parent_id =ANY(%s)',(ids,))
350 child_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
352 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
355 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
356 if p_id and (p_id in s_ids):
363 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
366 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
368 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
376 if name and str(name).startswith('partner:'):
377 part_id = int(name.split(':')[1])
378 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
379 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
381 if name and str(name).startswith('type:'):
382 type = name.split(':')[1]
383 args += [('type', '=', type)]
388 ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
390 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
392 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
394 ids = self.search(cr, user, args, context=context, limit=limit)
395 return self.name_get(cr, user, ids, context=context)
397 def name_get(self, cr, uid, ids, context={}):
400 reads = self.read(cr, uid, ids, ['name', 'code'], context)
403 name = record['name']
405 name = record['code'] + ' '+name
406 res.append((record['id'], name))
409 def copy(self, cr, uid, id, default={}, context={}, done_list=[], local=False):
410 account = self.browse(cr, uid, id, context=context)
414 default = default.copy()
415 default['code'] = (account['code'] or '') + '(copy)'
418 if account.id in done_list:
420 done_list.append(account.id)
422 for child in account.child_id:
423 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
425 new_child_ids.append(child_ids)
426 default['child_parent_ids'] = [(6, 0, new_child_ids)]
428 default['child_parent_ids'] = False
429 return super(account_account, self).copy(cr, uid, id, default, context=context)
431 def _check_moves(self, cr, uid, ids, method, context):
432 line_obj = self.pool.get('account.move.line')
433 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
434 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
435 if method == 'write':
436 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
437 elif method == 'unlink':
438 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
441 def write(self, cr, uid, ids, vals, context=None):
444 if 'active' in vals and not vals['active']:
445 self._check_moves(cr, uid, ids, "write", context)
446 return super(account_account, self).write(cr, uid, ids, vals, context=context)
448 def unlink(self, cr, uid, ids, context={}):
449 self._check_moves(cr, uid, ids, "unlink", context)
450 return super(account_account, self).unlink(cr, uid, ids, context)
454 class account_journal_view(osv.osv):
455 _name = "account.journal.view"
456 _description = "Journal View"
458 'name': fields.char('Journal View', size=64, required=True),
459 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
462 account_journal_view()
465 class account_journal_column(osv.osv):
466 def _col_get(self, cr, user, context={}):
468 cols = self.pool.get('account.move.line')._columns
470 result.append( (col, cols[col].string) )
473 _name = "account.journal.column"
474 _description = "Journal Column"
476 'name': fields.char('Column Name', size=64, required=True),
477 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
478 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
479 'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column."),
480 'required': fields.boolean('Required'),
481 'readonly': fields.boolean('Readonly'),
484 account_journal_column()
486 class account_journal(osv.osv):
487 _name = "account.journal"
488 _description = "Journal"
490 'name': fields.char('Journal Name', size=64, required=True, translate=True),
491 'code': fields.char('Code', size=16),
492 'type': fields.selection([('sale', 'Sale'), ('purchase', 'Purchase'), ('cash', 'Cash'), ('general', 'General'), ('situation', 'Situation')], 'Type', size=32, required=True,
493 help="Select 'Sale' for Sale journal to be used at the time of making invoice."\
494 " Select 'Purchase' for Purchase Journal to be used at the time of approving purchase order."\
495 " Select 'Cash' to be used at the time of making payment."\
496 " Select 'General' to be used at the time of stock input/output."\
497 " Select 'Situation' to be used at the time of making vouchers."),
498 'refund_journal': fields.boolean('Refund Journal', help='Fill this if the journal is to be used for refunds of invoices.'),
500 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
501 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
503 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the journal without removing it."),
504 'view_id': fields.many2one('account.journal.view', 'View', required=True, help="Gives the view used when writing or browsing entries in this journal. The view tells Open ERP which fields should be visible, required or readonly and in which order. You can create your own view for a faster encoding in each journal."),
505 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]"),
506 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]"),
507 'centralisation': fields.boolean('Centralised counterpart', help="Check this box to determine that each entry of this journal won't create a new counterpart but will share the same counterpart. This is used in fiscal year closing."),
508 'update_posted': fields.boolean('Allow Cancelling Entries'),
509 'group_invoice_lines': fields.boolean('Group invoice lines', help="If this box is checked, the system will try to group the accounting lines when generating them from invoices."),
510 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals", required=True),
511 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
512 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
513 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
514 'entry_posted': fields.boolean('Skip \'Draft\' State for Created Entries', help='Check this box if you don\'t want new account moves to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation.'),
515 'company_id': fields.many2one('res.company', 'Company', required=True,select=1),
516 'invoice_sequence_id': fields.many2one('ir.sequence', 'Invoice Sequence', \
517 help="The sequence used for invoice numbers in this journal."),
521 'active': lambda *a: 1,
522 'user_id': lambda self,cr,uid,context: uid,
523 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
525 def create(self, cr, uid, vals, context={}):
526 journal_id = super(account_journal, self).create(cr, uid, vals, context)
527 # journal_name = self.browse(cr, uid, [journal_id])[0].code
528 # periods = self.pool.get('account.period')
529 # ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
530 # for period in periods.browse(cr, uid, ids):
531 # self.pool.get('account.journal.period').create(cr, uid, {
532 # 'name': (journal_name or '')+':'+(period.code or ''),
533 # 'journal_id': journal_id,
534 # 'period_id': period.id
538 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
545 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
547 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
548 return self.name_get(cr, user, ids, context=context)
551 class account_fiscalyear(osv.osv):
552 _name = "account.fiscalyear"
553 _description = "Fiscal Year"
555 'name': fields.char('Fiscal Year', size=64, required=True),
556 'code': fields.char('Code', size=6, required=True),
557 'company_id': fields.many2one('res.company', 'Company',
558 help="Keep empty if the fiscal year belongs to several companies.", required=True),
559 'date_start': fields.date('Start Date', required=True),
560 'date_stop': fields.date('End Date', required=True),
561 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
562 'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', readonly=True,
563 help='When fiscal year is created. The state is \'Draft\'. At the end of the year it is in \'Done\' state.'),
567 'state': lambda *a: 'draft',
568 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
570 _order = "date_start"
572 def _check_duration(self,cr,uid,ids):
573 obj_fy=self.browse(cr,uid,ids[0])
574 if obj_fy.date_stop < obj_fy.date_start:
579 (_check_duration, 'Error ! The duration of the Fiscal Year is invalid. ', ['date_stop'])
582 def create_period3(self,cr, uid, ids, context={}):
583 return self.create_period(cr, uid, ids, context, 3)
585 def create_period(self,cr, uid, ids, context={}, interval=1):
586 for fy in self.browse(cr, uid, ids, context):
587 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
588 while ds.strftime('%Y-%m-%d')<fy.date_stop:
589 de = ds + relativedelta(months=interval, days=-1)
591 if de.strftime('%Y-%m-%d')>fy.date_stop:
592 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
594 self.pool.get('account.period').create(cr, uid, {
595 'name': ds.strftime('%m/%Y'),
596 'code': ds.strftime('%m/%Y'),
597 'date_start': ds.strftime('%Y-%m-%d'),
598 'date_stop': de.strftime('%Y-%m-%d'),
599 'fiscalyear_id': fy.id,
601 ds = ds + relativedelta(months=interval)
604 def find(self, cr, uid, dt=None, exception=True, context={}):
606 dt = time.strftime('%Y-%m-%d')
607 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
610 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
616 class account_period(osv.osv):
617 _name = "account.period"
618 _description = "Account period"
620 'name': fields.char('Period Name', size=64, required=True),
621 'code': fields.char('Code', size=12),
622 'special': fields.boolean('Opening/Closing Period', size=12,
623 help="These periods can overlap."),
624 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
625 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
626 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
627 'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', readonly=True,
628 help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
629 'company_id': fields.many2one('res.company', 'Company', required=True)
632 'state': lambda *a: 'draft',
633 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
635 _order = "date_start"
637 def _check_duration(self,cr,uid,ids,context={}):
638 obj_period=self.browse(cr,uid,ids[0])
639 if obj_period.date_stop < obj_period.date_start:
643 def _check_year_limit(self,cr,uid,ids,context={}):
644 for obj_period in self.browse(cr,uid,ids):
645 if obj_period.special:
648 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
649 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
650 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
651 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
654 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
655 for period in self.browse(cr, uid, pids):
656 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
661 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
662 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
665 def next(self, cr, uid, period, step, context={}):
666 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
671 def find(self, cr, uid, dt=None, context={}):
673 dt = time.strftime('%Y-%m-%d')
674 #CHECKME: shouldn't we check the state of the period?
675 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
677 raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create a fiscal year.')%dt)
680 def action_draft(self, cr, uid, ids, *args):
681 users_roles = self.pool.get('res.users').browse(cr, uid, uid).roles_id
682 for role in users_roles:
683 if role.name=='Period':
686 cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
687 cr.execute('update account_period set state=%s where id=%s', (mode, id))
692 class account_journal_period(osv.osv):
693 _name = "account.journal.period"
694 _description = "Journal - Period"
696 def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
697 result = {}.fromkeys(ids, 'STOCK_NEW')
698 for r in self.read(cr, uid, ids, ['state']):
700 'draft': 'STOCK_NEW',
701 'printed': 'STOCK_PRINT_PREVIEW',
702 'done': 'STOCK_DIALOG_AUTHENTICATION',
703 }.get(r['state'], 'STOCK_NEW')
707 'name': fields.char('Journal-Period Name', size=64, required=True),
708 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
709 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
710 'icon': fields.function(_icon_get, method=True, string='Icon', type='char', size=32),
711 'active': fields.boolean('Active', required=True, help="If the active field is set to true, it will allow you to hide the journal period without removing it."),
712 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
713 help='When journal period is created. The state is \'Draft\'. If a report is printed it comes to \'Printed\' state. When all transactions are done, it comes in \'Done\' state.'),
714 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
715 'company_id' : fields.many2one('res.company', 'Company')
718 def _check(self, cr, uid, ids, context={}):
719 for obj in self.browse(cr, uid, ids, context):
720 cr.execute('select * from account_move_line where journal_id=%s and period_id=%s limit 1', (obj.journal_id.id, obj.period_id.id))
723 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
726 def write(self, cr, uid, ids, vals, context={}):
727 self._check(cr, uid, ids, context)
728 return super(account_journal_period, self).write(cr, uid, ids, vals, context)
730 def create(self, cr, uid, vals, context={}):
731 period_id=vals.get('period_id',False)
733 period = self.pool.get('account.period').browse(cr, uid,period_id)
734 vals['state']=period.state
735 return super(account_journal_period, self).create(cr, uid, vals, context)
737 def unlink(self, cr, uid, ids, context={}):
738 self._check(cr, uid, ids, context)
739 return super(account_journal_period, self).unlink(cr, uid, ids, context)
742 'state': lambda *a: 'draft',
743 'active': lambda *a: True,
744 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
748 account_journal_period()
750 class account_fiscalyear(osv.osv):
751 _inherit = "account.fiscalyear"
752 _description = "Fiscal Year"
754 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
758 #----------------------------------------------------------
760 #----------------------------------------------------------
761 class account_move(osv.osv):
762 _name = "account.move"
763 _description = "Account Entry"
766 def name_get(self, cursor, user, ids, context=None):
770 data_move = self.pool.get('account.move').browse(cursor,user,ids)
771 for move in data_move:
772 if move.state=='draft':
773 name = '*' + str(move.id)
776 res.append((move.id, name))
780 def _get_period(self, cr, uid, context):
781 periods = self.pool.get('account.period').find(cr, uid)
787 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
788 if not ids: return {}
789 cr.execute('select move_id,sum(debit) from account_move_line where move_id =ANY(%s) group by move_id',(ids,))
790 result = dict(cr.fetchall())
792 result.setdefault(id, 0.0)
795 def _search_amount(self, cr, uid, obj, name, args, context):
797 cr.execute('select move_id,sum(debit) from account_move_line group by move_id')
798 result = dict(cr.fetchall())
802 res = [('id', 'in', [k for k,v in result.iteritems() if v >= item[2]])]
804 res = [('id', 'in', [k for k,v in result.iteritems() if v <= item[2]])]
808 return [('id', '>', '0')]
813 'name': fields.char('Number', size=64, required=True),
814 'ref': fields.char('Reference', size=64),
815 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
816 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
817 'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'State', required=True, readonly=True,
818 help='When new account move is created the state will be \'Draft\'. When all the payments are done it will be in \'Posted\' state.'),
819 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
820 'to_check': fields.boolean('To Be Verified'),
821 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner"),
822 'amount': fields.function(_amount_compute, method=True, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
823 'date': fields.date('Date', required=True),
824 'type': fields.selection([
825 ('pay_voucher','Cash Payment'),
826 ('bank_pay_voucher','Bank Payment'),
827 ('rec_voucher','Cash Receipt'),
828 ('bank_rec_voucher','Bank Receipt'),
829 ('cont_voucher','Contra'),
830 ('journal_sale_vou','Journal Sale'),
831 ('journal_pur_voucher','Journal Purchase'),
832 ('journal_voucher','Journal Voucher'),
833 ],'Type', readonly=True, select=True, states={'draft':[('readonly',False)]}),
834 'narration':fields.text('Narration', readonly=True, select=True, states={'draft':[('readonly',False)]}),
835 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
838 'name': lambda *a: '/',
839 'state': lambda *a: 'draft',
840 'period_id': _get_period,
841 'type' : lambda *a : 'journal_voucher',
842 'date': lambda *a:time.strftime('%Y-%m-%d'),
843 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
846 def _check_centralisation(self, cursor, user, ids):
847 for move in self.browse(cursor, user, ids):
848 if move.journal_id.centralisation:
849 move_ids = self.search(cursor, user, [
850 ('period_id', '=', move.period_id.id),
851 ('journal_id', '=', move.journal_id.id),
853 if len(move_ids) > 1:
857 def _check_period_journal(self, cursor, user, ids):
858 for move in self.browse(cursor, user, ids):
859 for line in move.line_id:
860 if line.period_id.id != move.period_id.id:
862 if line.journal_id.id != move.journal_id.id:
867 (_check_centralisation,
868 'You cannot create more than one move per period on centralized journal',
870 (_check_period_journal,
871 'You cannot create entries on different periods/journals in the same move',
874 def post(self, cr, uid, ids, context=None):
875 if self.validate(cr, uid, ids, context) and len(ids):
876 for move in self.browse(cr, uid, ids):
879 journal = move.journal_id
880 if journal.sequence_id:
881 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
882 new_name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id, context=c)
884 raise osv.except_osv(_('Error'), _('No sequence defined in the journal !'))
886 self.write(cr, uid, [move.id], {'name':new_name})
888 cr.execute('update account_move set state=%s where id =ANY(%s) ',('posted',ids,))
890 raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non-balanced entry !'))
893 def button_validate(self, cursor, user, ids, context=None):
894 return self.post(cursor, user, ids, context=context)
896 def button_cancel(self, cr, uid, ids, context={}):
897 for line in self.browse(cr, uid, ids, context):
898 if not line.journal_id.update_posted:
899 raise osv.except_osv(_('Error !'), _('You can not modify a posted entry of this journal !\nYou should set the journal to allow cancelling entries if you want to do that.'))
901 cr.execute('update account_move set state=%s where id =ANY(%s)',('draft',ids,))
904 def write(self, cr, uid, ids, vals, context={}):
906 c['novalidate'] = True
907 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
908 self.validate(cr, uid, ids, context)
912 # TODO: Check if period is closed !
914 def create(self, cr, uid, vals, context={}):
915 if 'line_id' in vals:
916 if 'journal_id' in vals:
917 for l in vals['line_id']:
919 l[2]['journal_id'] = vals['journal_id']
920 context['journal_id'] = vals['journal_id']
921 if 'period_id' in vals:
922 for l in vals['line_id']:
924 l[2]['period_id'] = vals['period_id']
925 context['period_id'] = vals['period_id']
927 default_period = self._get_period(cr, uid, context)
928 for l in vals['line_id']:
930 l[2]['period_id'] = default_period
931 context['period_id'] = default_period
933 accnt_journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'])
934 if 'line_id' in vals:
936 c['novalidate'] = True
937 result = super(account_move, self).create(cr, uid, vals, c)
938 self.validate(cr, uid, [result], context)
940 result = super(account_move, self).create(cr, uid, vals, context)
943 def copy(self, cr, uid, id, default=None, context=None):
946 default = default.copy()
947 default.update({'state':'draft', 'name':'/',})
948 return super(account_move, self).copy(cr, uid, id, default, context)
950 def unlink(self, cr, uid, ids, context={}, check=True):
952 for move in self.browse(cr, uid, ids, context):
953 if move['state'] != 'draft':
954 raise osv.except_osv(_('UserError'),
955 _('You can not delete posted movement: "%s"!') % \
957 line_ids = map(lambda x: x.id, move.line_id)
958 context['journal_id'] = move.journal_id.id
959 context['period_id'] = move.period_id.id
960 self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
961 self.pool.get('account.move.line').unlink(cr, uid, line_ids, context=context)
962 toremove.append(move.id)
963 result = super(account_move, self).unlink(cr, uid, toremove, context)
966 def _compute_balance(self, cr, uid, id, context={}):
967 move = self.browse(cr, uid, [id])[0]
969 for line in move.line_id:
970 amount+= (line.debit - line.credit)
973 def _centralise(self, cr, uid, move, mode, context=None):
978 account_id = move.journal_id.default_debit_account_id.id
981 raise osv.except_osv(_('UserError'),
982 _('There is no default default debit account defined \n' \
983 'on journal "%s"') % move.journal_id.name)
985 account_id = move.journal_id.default_credit_account_id.id
988 raise osv.except_osv(_('UserError'),
989 _('There is no default default credit account defined \n' \
990 'on journal "%s"') % move.journal_id.name)
992 # find the first line of this move with the current mode
993 # or create it if it doesn't exist
994 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
999 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1000 line_id = self.pool.get('account.move.line').create(cr, uid, {
1001 'name': _(mode.capitalize()+' Centralisation'),
1002 'centralisation': mode,
1003 'account_id': account_id,
1005 'journal_id': move.journal_id.id,
1006 'period_id': move.period_id.id,
1007 'date': move.period_id.date_stop,
1012 # find the first line of this move with the other mode
1013 # so that we can exclude it from our calculation
1014 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1021 cr.execute('select sum('+mode+') from account_move_line where move_id=%s and id<>%s', (move.id, line_id2))
1022 result = cr.fetchone()[0] or 0.0
1023 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1027 # Validate a balanced move. If it is a centralised journal, create a move.
1029 def validate(self, cr, uid, ids, context={}):
1030 if context and ('__last_update' in context):
1031 del context['__last_update']
1033 for move in self.browse(cr, uid, ids, context):
1034 #unlink analytic lines on move_lines
1035 for obj_line in move.line_id:
1036 for obj in obj_line.analytic_lines:
1037 self.pool.get('account.analytic.line').unlink(cr,uid,obj.id)
1039 journal = move.journal_id
1044 for line in move.line_id:
1045 amount += line.debit - line.credit
1046 line_ids.append(line.id)
1047 if line.state=='draft':
1048 line_draft_ids.append(line.id)
1051 company_id = line.account_id.company_id.id
1052 if not company_id == line.account_id.company_id.id:
1053 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1055 if line.account_id.currency_id:
1056 if line.account_id.currency_id.id != line.currency_id.id and (line.account_id.currency_id.id != line.account_id.company_id.currency_id.id or line.currency_id):
1057 raise osv.except_osv(_('Error'), _("""Couldn't create move with currency different from the secondary currency of the account "%s - %s". Clear the secondary currency field of the account definition if you want to accept all currencies.""" % (line.account_id.code, line.account_id.name)))
1059 if abs(amount) < 0.0001:
1060 if not len(line_draft_ids):
1062 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1063 'journal_id': move.journal_id.id,
1064 'period_id': move.period_id.id,
1066 }, context, check=False)
1070 if journal.type not in ('purchase','sale'):
1073 for line in move.line_id:
1075 key = (line.account_id.id, line.tax_code_id.id)
1077 code = account2[key][0]
1078 amount = account2[key][1] * (line.debit + line.credit)
1079 elif line.account_id.id in account:
1080 code = account[line.account_id.id][0]
1081 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1082 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1083 self.pool.get('account.move.line').write(cr, uid, [line.id], {
1084 'tax_code_id': code,
1085 'tax_amount': amount
1086 }, context, check=False)
1091 if journal.centralisation:
1092 self._centralise(cr, uid, move, 'debit', context=context)
1093 self._centralise(cr, uid, move, 'credit', context=context)
1094 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1096 }, context, check=False)
1099 self.pool.get('account.move.line').write(cr, uid, line_ids, {
1100 'journal_id': move.journal_id.id,
1101 'period_id': move.period_id.id,
1102 #'tax_code_id': False,
1103 #'tax_amount': False,
1105 }, context, check=False)
1109 for tmp in move.line_id:
1110 list_ids.append(tmp.id)
1111 self.pool.get('account.move.line').create_analytic_lines(cr, uid, list_ids, context)
1115 class account_move_reconcile(osv.osv):
1116 _name = "account.move.reconcile"
1117 _description = "Account Reconciliation"
1119 'name': fields.char('Name', size=64, required=True),
1120 'type': fields.char('Type', size=16, required=True),
1121 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1122 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1123 'create_date': fields.date('Creation date', readonly=True),
1126 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1128 def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1130 for rec in self.browse(cr, uid, ids, context):
1131 for line in rec.line_partial_ids:
1132 total += (line.debit or 0.0) - (line.credit or 0.0)
1134 self.pool.get('account.move.line').write(cr, uid,
1135 map(lambda x: x.id, rec.line_partial_ids),
1136 {'reconcile_id': rec.id }
1140 def name_get(self, cr, uid, ids, context=None):
1144 for r in self.browse(cr, uid, ids, context):
1145 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1147 name = '%s (%.2f)' % (r.name, total)
1148 result.append((r.id,name))
1150 result.append((r.id,r.name))
1154 account_move_reconcile()
1156 #----------------------------------------------------------
1158 #----------------------------------------------------------
1161 child_depend: la taxe depend des taxes filles
1163 class account_tax_code(osv.osv):
1165 A code for the tax object.
1167 This code is used for some tax declarations.
1169 def _sum(self, cr, uid, ids, name, args, context, where =''):
1170 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
1171 if context.get('based_on', 'invoices') == 'payments':
1172 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1173 FROM account_move_line AS line, \
1174 account_move AS move \
1175 LEFT JOIN account_invoice invoice ON \
1176 (invoice.move_id = move.id) \
1177 WHERE line.tax_code_id =ANY(%s) '+where+' \
1178 AND move.id = line.move_id \
1179 AND ((invoice.state = \'paid\') \
1180 OR (invoice.id IS NULL)) \
1181 GROUP BY line.tax_code_id',(ids2,))
1183 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1184 FROM account_move_line AS line \
1185 WHERE line.tax_code_id =ANY(%s) '+where+' \
1186 GROUP BY line.tax_code_id',(ids2,))
1187 res=dict(cr.fetchall())
1188 for record in self.browse(cr, uid, ids, context):
1189 def _rec_get(record):
1190 amount = res.get(record.id, 0.0)
1191 for rec in record.child_ids:
1192 amount += _rec_get(rec) * rec.sign
1194 res[record.id] = round(_rec_get(record), self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
1197 def _sum_year(self, cr, uid, ids, name, args, context):
1198 if 'fiscalyear_id' in context and context['fiscalyear_id']:
1199 fiscalyear_id = context['fiscalyear_id']
1201 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1204 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1206 where = ' and period_id in (' + (','.join(pids))+')'
1207 return self._sum(cr, uid, ids, name, args, context,
1210 def _sum_period(self, cr, uid, ids, name, args, context):
1211 if 'period_id' in context and context['period_id']:
1212 period_id = context['period_id']
1214 period_id = self.pool.get('account.period').find(cr, uid)
1215 if not len(period_id):
1216 return dict.fromkeys(ids, 0.0)
1217 period_id = period_id[0]
1218 return self._sum(cr, uid, ids, name, args, context,
1219 where=' and line.period_id='+str(period_id))
1221 _name = 'account.tax.code'
1222 _description = 'Tax Code'
1225 'name': fields.char('Tax Case Name', size=64, required=True),
1226 'code': fields.char('Case Code', size=64),
1227 'info': fields.text('Description'),
1228 'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1229 'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1230 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1231 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1232 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1233 'company_id': fields.many2one('res.company', 'Company', required=True),
1234 'sign': fields.float('Sign for parent', required=True),
1235 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"),
1239 def name_get(self, cr, uid, ids, context=None):
1242 if isinstance(ids, (int, long)):
1244 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1245 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1248 def _default_company(self, cr, uid, context={}):
1249 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1251 return user.company_id.id
1252 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1254 'company_id': _default_company,
1255 'sign': lambda *args: 1.0,
1256 'notprintable': lambda *a: False,
1258 def _check_recursion(self, cr, uid, ids):
1261 cr.execute('select distinct parent_id from account_tax_code where id =ANY(%s)',(ids,))
1262 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1269 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1271 _order = 'code,name'
1274 class account_tax(osv.osv):
1278 Type: percent, fixed, none, code
1279 PERCENT: tax = price * amount
1280 FIXED: tax = price + amount
1282 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1283 return result in the context
1284 Ex: result=round(price_unit*0.21,4)
1286 _name = 'account.tax'
1287 _description = 'Tax'
1289 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1290 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the tax lines from the lowest sequences to the higher ones. The order is important if you have a tax with several tax children. In this case, the evaluation order is important."),
1291 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
1292 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the tax without removing it."),
1293 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'),('balance','Balance')], 'Tax Type', required=True,
1294 help="The computation method for the tax amount."),
1295 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True,
1296 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1297 'domain':fields.char('Domain', size=32, help="This field is only used if you develop your own module allowing developers to create specific taxes in a custom domain."),
1298 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1299 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1300 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1301 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1302 'child_depend':fields.boolean('Tax on Children', help="Set if the tax computation is based on the computation of child taxes rather than on the total amount."),
1303 'python_compute':fields.text('Python Code'),
1304 'python_compute_inv':fields.text('Python Code (reverse)'),
1305 'python_applicable':fields.text('Python Code'),
1306 'tax_group': fields.selection([('vat','VAT'),('other','Other')], 'Tax Group', help="If a default tax is given in the partner it only overrides taxes from accounts (or products) in the same group."),
1309 # Fields used for the VAT declaration
1311 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1312 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1313 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1314 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1316 # Same fields for refund invoices
1318 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1319 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1320 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1321 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1322 'include_base_amount': fields.boolean('Included in base amount', help="Indicates if the amount of tax must be included in the base amount for the computation of the next taxes"),
1323 'company_id': fields.many2one('res.company', 'Company', required=True),
1324 'description': fields.char('Tax Code',size=32),
1325 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1326 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1329 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1330 context=None, count=False):
1331 if context and context.has_key('type'):
1332 if context['type'] in ('out_invoice','out_refund'):
1333 args.append(('type_tax_use','in',['sale','all']))
1334 elif context['type'] in ('in_invoice','in_refund'):
1335 args.append(('type_tax_use','in',['purchase','all']))
1336 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1338 def name_get(self, cr, uid, ids, context={}):
1342 for record in self.read(cr, uid, ids, ['description','name'], context):
1343 name = record['description'] and record['description'] or record['name']
1344 res.append((record['id'],name ))
1347 def _default_company(self, cr, uid, context={}):
1348 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1350 return user.company_id.id
1351 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1353 'python_compute': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n# product : product.product object or None\n# partner : res.partner object or None\n\nresult = price_unit * 0.10''',
1354 'python_compute_inv': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n# product : product.product object or False\n\nresult = price_unit * 0.10''',
1355 'applicable_type': lambda *a: 'true',
1356 'type': lambda *a: 'percent',
1357 'amount': lambda *a: 0,
1358 'price_include': lambda *a: 0,
1359 'active': lambda *a: 1,
1360 'type_tax_use': lambda *a: 'all',
1361 'sequence': lambda *a: 1,
1362 'tax_group': lambda *a: 'vat',
1363 'ref_tax_sign': lambda *a: 1,
1364 'ref_base_sign': lambda *a: 1,
1365 'tax_sign': lambda *a: 1,
1366 'base_sign': lambda *a: 1,
1367 'include_base_amount': lambda *a: False,
1368 'company_id': _default_company,
1372 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1375 if tax.applicable_type=='code':
1376 localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id), 'product':product, 'partner':partner}
1377 exec tax.python_applicable in localdict
1378 if localdict.get('result', False):
1384 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1385 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1388 cur_price_unit=price_unit
1390 # we compute the amount for the current tax object and append it to the result
1392 data = {'id':tax.id,
1393 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1394 'account_collected_id':tax.account_collected_id.id,
1395 'account_paid_id':tax.account_paid_id.id,
1396 'base_code_id': tax.base_code_id.id,
1397 'ref_base_code_id': tax.ref_base_code_id.id,
1398 'sequence': tax.sequence,
1399 'base_sign': tax.base_sign,
1400 'tax_sign': tax.tax_sign,
1401 'ref_base_sign': tax.ref_base_sign,
1402 'ref_tax_sign': tax.ref_tax_sign,
1403 'price_unit': cur_price_unit,
1404 'tax_code_id': tax.tax_code_id.id,
1405 'ref_tax_code_id': tax.ref_tax_code_id.id,
1408 if tax.type=='percent':
1409 amount = cur_price_unit * tax.amount
1410 data['amount'] = amount
1412 elif tax.type=='fixed':
1413 data['amount'] = tax.amount
1414 data['tax_amount']=quantity
1415 # data['amount'] = quantity
1416 elif tax.type=='code':
1417 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1418 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1419 exec tax.python_compute in localdict
1420 amount = localdict['result']
1421 data['amount'] = amount
1422 elif tax.type=='balance':
1423 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1424 data['balance'] = cur_price_unit
1426 amount2 = data['amount']
1427 if len(tax.child_ids):
1428 if tax.child_depend:
1431 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1432 res.extend(child_tax)
1433 if tax.child_depend:
1435 for name in ('base','ref_base'):
1436 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1437 r[name+'_code_id'] = latest[name+'_code_id']
1438 r[name+'_sign'] = latest[name+'_sign']
1439 r['price_unit'] = latest['price_unit']
1440 latest[name+'_code_id'] = False
1441 for name in ('tax','ref_tax'):
1442 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1443 r[name+'_code_id'] = latest[name+'_code_id']
1444 r[name+'_sign'] = latest[name+'_sign']
1445 r['amount'] = data['amount']
1446 latest[name+'_code_id'] = False
1447 if tax.include_base_amount:
1448 cur_price_unit+=amount2
1451 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1454 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1458 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1459 one tax for each tax id in IDS and their childs
1461 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1464 if r.get('balance',False):
1465 r['amount'] = round(r['balance'] * quantity, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')) - total
1467 r['amount'] = round(r['amount'] * quantity, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
1468 total += r['amount']
1472 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1473 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1477 cur_price_unit = price_unit
1479 tax_parent_tot = 0.0
1481 if (tax.type=='percent') and not tax.include_base_amount:
1482 tax_parent_tot += tax.amount
1485 if (tax.type=='fixed') and not tax.include_base_amount:
1486 cur_price_unit -= tax.amount
1489 if tax.type=='percent':
1490 if tax.include_base_amount:
1491 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1493 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1495 elif tax.type=='fixed':
1498 elif tax.type=='code':
1499 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1500 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1501 exec tax.python_compute_inv in localdict
1502 amount = localdict['result']
1503 elif tax.type=='balance':
1504 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1505 # data['balance'] = cur_price_unit
1508 if tax.include_base_amount:
1509 cur_price_unit -= amount
1518 'account_collected_id': tax.account_collected_id.id,
1519 'account_paid_id': tax.account_paid_id.id,
1520 'base_code_id': tax.base_code_id.id,
1521 'ref_base_code_id': tax.ref_base_code_id.id,
1522 'sequence': tax.sequence,
1523 'base_sign': tax.base_sign,
1524 'tax_sign': tax.tax_sign,
1525 'ref_base_sign': tax.ref_base_sign,
1526 'ref_tax_sign': tax.ref_tax_sign,
1527 'price_unit': cur_price_unit,
1528 'tax_code_id': tax.tax_code_id.id,
1529 'ref_tax_code_id': tax.ref_tax_code_id.id,
1531 if len(tax.child_ids):
1532 if tax.child_depend:
1536 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1537 res.extend(parent_tax)
1542 total += r['amount']
1544 r['price_unit'] -= total
1548 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1550 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1551 Price Unit is a VAT included price
1555 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1556 one tax for each tax id in IDS and their childs
1558 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1561 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1562 if r.get('balance',False):
1563 r['amount'] = round(r['balance'] * quantity, prec) - total
1565 r['amount'] = round(r['amount'] * quantity, prec)
1566 total += r['amount']
1570 # ---------------------------------------------------------
1571 # Account Entries Models
1572 # ---------------------------------------------------------
1574 class account_model(osv.osv):
1575 _name = "account.model"
1576 _description = "Account Model"
1578 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
1579 'ref': fields.char('Reference', size=64),
1580 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
1581 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
1582 'legend' :fields.text('Legend',readonly=True,size=100),
1586 'legend': lambda self, cr, uid, context:_('You can specify year, month and date in the name of the model using the following labels:\n\n%(year)s : To Specify Year \n%(month)s : To Specify Month \n%(date)s : Current Date\n\ne.g. My model on %(date)s'),
1588 def generate(self, cr, uid, ids, datas={}, context={}):
1590 for model in self.browse(cr, uid, ids, context):
1591 context.update({'date':datas['date']})
1592 period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date',False))
1594 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
1595 period_id = period_id[0]
1596 move_id = self.pool.get('account.move').create(cr, uid, {
1598 'period_id': period_id,
1599 'journal_id': model.journal_id.id,
1600 'date': context.get('date',time.strftime('%Y-%m-%d'))
1602 move_ids.append(move_id)
1603 for line in model.lines_id:
1606 'journal_id': model.journal_id.id,
1607 'period_id': period_id
1611 'quantity': line.quantity,
1612 'debit': line.debit,
1613 'credit': line.credit,
1614 'account_id': line.account_id.id,
1617 'partner_id': line.partner_id.id,
1618 'date': context.get('date',time.strftime('%Y-%m-%d')),
1619 'date_maturity': time.strftime('%Y-%m-%d')
1622 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
1623 self.pool.get('account.move.line').create(cr, uid, val, context=c)
1627 class account_model_line(osv.osv):
1628 _name = "account.model.line"
1629 _description = "Account Model Entries"
1631 'name': fields.char('Name', size=64, required=True),
1632 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
1633 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries"),
1634 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
1635 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
1637 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
1639 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
1641 'ref': fields.char('Reference', size=16),
1643 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
1644 'currency_id': fields.many2one('res.currency', 'Currency'),
1646 'partner_id': fields.many2one('res.partner', 'Partner'),
1647 'date_maturity': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Maturity date', help="The maturity date of the generated entries for this model. You can choose between the creation date or the creation date of the entries plus the partner payment terms."),
1648 'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
1651 'date': lambda *a: 'today'
1654 _sql_constraints = [
1655 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
1656 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
1658 account_model_line()
1660 # ---------------------------------------------------------
1661 # Account Subscription
1662 # ---------------------------------------------------------
1665 class account_subscription(osv.osv):
1666 _name = "account.subscription"
1667 _description = "Account Subscription"
1669 'name': fields.char('Name', size=64, required=True),
1670 'ref': fields.char('Reference', size=16),
1671 'model_id': fields.many2one('account.model', 'Model', required=True),
1673 'date_start': fields.date('Start Date', required=True),
1674 'period_total': fields.integer('Number of Periods', required=True),
1675 'period_nbr': fields.integer('Period', required=True),
1676 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
1677 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
1679 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
1682 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
1683 'period_type': lambda *a: 'month',
1684 'period_total': lambda *a: 12,
1685 'period_nbr': lambda *a: 1,
1686 'state': lambda *a: 'draft',
1688 def state_draft(self, cr, uid, ids, context={}):
1689 self.write(cr, uid, ids, {'state':'draft'})
1692 def check(self, cr, uid, ids, context={}):
1694 for sub in self.browse(cr, uid, ids, context):
1696 for line in sub.lines_id:
1697 if not line.move_id.id:
1701 todone.append(sub.id)
1703 self.write(cr, uid, todone, {'state':'done'})
1706 def remove_line(self, cr, uid, ids, context={}):
1708 for sub in self.browse(cr, uid, ids, context):
1709 for line in sub.lines_id:
1710 if not line.move_id.id:
1711 toremove.append(line.id)
1713 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
1714 self.write(cr, uid, ids, {'state':'draft'})
1717 def compute(self, cr, uid, ids, context={}):
1718 for sub in self.browse(cr, uid, ids, context):
1720 for i in range(sub.period_total):
1721 self.pool.get('account.subscription.line').create(cr, uid, {
1723 'subscription_id': sub.id,
1725 if sub.period_type=='day':
1726 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
1727 if sub.period_type=='month':
1728 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
1729 if sub.period_type=='year':
1730 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
1731 self.write(cr, uid, ids, {'state':'running'})
1733 account_subscription()
1735 class account_subscription_line(osv.osv):
1736 _name = "account.subscription.line"
1737 _description = "Account Subscription Line"
1739 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
1740 'date': fields.date('Date', required=True),
1741 'move_id': fields.many2one('account.move', 'Entry'),
1745 def move_create(self, cr, uid, ids, context={}):
1747 for line in self.browse(cr, uid, ids, context):
1751 ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
1752 tocheck[line.subscription_id.id] = True
1753 self.write(cr, uid, [line.id], {'move_id':ids[0]})
1755 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
1758 account_subscription_line()
1761 class account_config_wizard(osv.osv_memory):
1762 _name = 'account.config.wizard'
1763 _inherit = 'res.config'
1767 'Name', required=True, size=64,
1768 help="Name of the fiscal year as displayed on screens."),
1770 'Code', required=True, size=64,
1771 help="Name of the fiscal year as displayed in reports."),
1772 'date1': fields.date('Start Date', required=True),
1773 'date2': fields.date('End Date', required=True),
1774 'period':fields.selection([('month','Month'), ('3months','3 Months')],
1775 'Periods', required=True),
1778 'code': lambda *a: time.strftime('%Y'),
1779 'name': lambda *a: time.strftime('%Y'),
1780 'date1': lambda *a: time.strftime('%Y-01-01'),
1781 'date2': lambda *a: time.strftime('%Y-12-31'),
1782 'period':lambda *a:'month',
1785 def execute(self, cr, uid, ids, context=None):
1786 for res in self.read(cr,uid,ids):
1787 if 'date1' in res and 'date2' in res:
1788 res_obj = self.pool.get('account.fiscalyear')
1789 start_date=res['date1']
1790 end_date=res['date2']
1791 name=res['name']#DateTime.strptime(start_date, '%Y-%m-%d').strftime('%m.%Y') + '-' + DateTime.strptime(end_date, '%Y-%m-%d').strftime('%m.%Y')
1795 'date_start':start_date,
1796 'date_stop':end_date,
1798 new_id=res_obj.create(cr, uid, vals, context=context)
1799 if res['period']=='month':
1800 res_obj.create_period(cr,uid,[new_id])
1801 elif res['period']=='3months':
1802 res_obj.create_period3(cr,uid,[new_id])
1803 account_config_wizard()
1806 # ---------------------------------------------------------------
1807 # Account Templates : Account, Tax, Tax Code and chart. + Wizard
1808 # ---------------------------------------------------------------
1810 class account_tax_template(osv.osv):
1811 _name = 'account.tax.template'
1812 account_tax_template()
1814 class account_account_template(osv.osv):
1816 _name = "account.account.template"
1817 _description ='Templates for Accounts'
1820 'name': fields.char('Name', size=128, required=True, select=True),
1821 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
1822 'code': fields.char('Code', size=64),
1823 'type': fields.selection([
1824 ('receivable','Receivable'),
1825 ('payable','Payable'),
1827 ('consolidation','Consolidation'),
1829 ('closed','Closed'),
1830 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
1831 "special effects in Open ERP: view can not have entries, consolidation are accounts that "\
1832 "can have children accounts for multi-company consolidations, payable/receivable are for "\
1833 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
1834 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
1835 help="These types are defined according to your country. The type contains more information "\
1836 "about the account and its specificities."),
1837 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
1838 'shortcut': fields.char('Shortcut', size=12),
1839 'note': fields.text('Note'),
1840 'parent_id': fields.many2one('account.account.template','Parent Account Template', ondelete='cascade'),
1841 'child_parent_ids':fields.one2many('account.account.template','parent_id','Children'),
1842 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel','account_id','tax_id', 'Default Taxes'),
1843 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
1847 'reconcile': lambda *a: False,
1848 'type' : lambda *a :'view',
1849 'nocreate': lambda *a: False,
1852 def _check_recursion(self, cr, uid, ids):
1855 cr.execute('select parent_id from account_account_template where id =ANY(%s)',(ids,))
1856 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1863 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
1867 def name_get(self, cr, uid, ids, context={}):
1870 reads = self.read(cr, uid, ids, ['name','code'], context)
1872 for record in reads:
1873 name = record['name']
1875 name = record['code']+' '+name
1876 res.append((record['id'],name ))
1879 account_account_template()
1881 class account_add_tmpl_wizard(osv.osv_memory):
1882 """Add one more account from the template.
1884 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
1885 _name = 'account.addtmpl.wizard'
1887 def _get_def_cparent(self, cr, uid, context):
1888 acc_obj=self.pool.get('account.account')
1889 tmpl_obj=self.pool.get('account.account.template')
1890 #print "Searching for ",context
1891 tids=tmpl_obj.read(cr, uid, [context['tmpl_ids']],['parent_id'])
1892 if not tids or not tids[0]['parent_id']:
1894 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]],['code'])
1895 if not ptids or not ptids[0]['code']:
1896 raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
1897 res = acc_obj.search(cr,uid,[('code','=',ptids[0]['code'])])
1904 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
1907 'cparent_id': _get_def_cparent,
1910 def action_create(self,cr,uid,ids,context=None):
1911 acc_obj=self.pool.get('account.account')
1912 tmpl_obj=self.pool.get('account.account.template')
1913 data= self.read(cr,uid,ids)
1914 company_id = acc_obj.read(cr,uid,[data[0]['cparent_id']],['company_id'])[0]['company_id'][0]
1915 account_template = tmpl_obj.browse(cr,uid,context['tmpl_ids'])
1917 #for tax in account_template.tax_ids:
1918 # tax_ids.append(tax_template_ref[tax.id])
1920 'name': account_template.name,
1921 #'sign': account_template.sign,
1922 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
1923 'code': account_template.code,
1924 'type': account_template.type,
1925 'user_type': account_template.user_type and account_template.user_type.id or False,
1926 'reconcile': account_template.reconcile,
1927 'shortcut': account_template.shortcut,
1928 'note': account_template.note,
1929 'parent_id': data[0]['cparent_id'],
1930 # 'tax_ids': [(6,0,tax_ids)], todo!!
1931 'company_id': company_id,
1933 # print "Creating:", vals
1934 new_account = acc_obj.create(cr,uid,vals)
1935 return {'type':'state', 'state': 'end' }
1937 def action_cancel(self,cr,uid,ids,context=None):
1938 return { 'type': 'state', 'state': 'end' }
1940 account_add_tmpl_wizard()
1942 class account_tax_code_template(osv.osv):
1944 _name = 'account.tax.code.template'
1945 _description = 'Tax Code Template'
1949 'name': fields.char('Tax Case Name', size=64, required=True),
1950 'code': fields.char('Case Code', size=64),
1951 'info': fields.text('Description'),
1952 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
1953 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
1954 'sign': fields.float('Sign for parent', required=True),
1955 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"),
1959 'sign': lambda *args: 1.0,
1960 'notprintable': lambda *a: False,
1963 def name_get(self, cr, uid, ids, context=None):
1966 if isinstance(ids, (int, long)):
1968 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1969 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1972 def _check_recursion(self, cr, uid, ids):
1975 cr.execute('select distinct parent_id from account_tax_code_template where id =ANY(%s)',(ids,))
1976 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1983 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
1985 _order = 'code,name'
1986 account_tax_code_template()
1989 class account_chart_template(osv.osv):
1990 _name="account.chart.template"
1991 _description= "Templates for Account Chart"
1994 'name': fields.char('Name', size=64, required=True),
1995 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
1996 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
1997 'tax_template_ids': fields.one2many('account.tax.template', 'chart_template_id', 'Tax Template List', help='List of all the taxes that have to be installed by the wizard'),
1998 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
1999 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2000 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2001 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2002 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2003 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2004 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2007 account_chart_template()
2009 class account_tax_template(osv.osv):
2011 _name = 'account.tax.template'
2012 _description = 'Templates for Taxes'
2015 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2016 'name': fields.char('Tax Name', size=64, required=True),
2017 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the taxes lines from lower sequences to higher ones. The order is important if you have a tax that has several tax children. In this case, the evaluation order is important."),
2018 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2019 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
2020 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True, help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
2021 'domain':fields.char('Domain', size=32, help="This field is only used if you develop your own module allowing developers to create specific taxes in a custom domain."),
2022 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2023 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2024 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2025 'child_depend':fields.boolean('Tax on Children', help="Set if the tax computation is based on the computation of child taxes rather than on the total amount."),
2026 'python_compute':fields.text('Python Code'),
2027 'python_compute_inv':fields.text('Python Code (reverse)'),
2028 'python_applicable':fields.text('Python Code'),
2029 'tax_group': fields.selection([('vat','VAT'),('other','Other')], 'Tax Group', help="If a default tax if given in the partner it only override taxes from account (or product) of the same group."),
2032 # Fields used for the VAT declaration
2034 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2035 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2036 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2037 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2039 # Same fields for refund invoices
2041 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2042 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2043 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2044 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2045 'include_base_amount': fields.boolean('Include in Base Amount', help="Set if the amount of tax must be included in the base amount before computing the next taxes."),
2046 'description': fields.char('Internal Name', size=32),
2047 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,)
2050 def name_get(self, cr, uid, ids, context={}):
2054 for record in self.read(cr, uid, ids, ['description','name'], context):
2055 name = record['description'] and record['description'] or record['name']
2056 res.append((record['id'],name ))
2059 def _default_company(self, cr, uid, context={}):
2060 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2062 return user.company_id.id
2063 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2066 'python_compute': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n# product : product.product object or None\n# partner : res.partner object or None\n\nresult = price_unit * 0.10''',
2067 'python_compute_inv': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n# product : product.product object or False\n\nresult = price_unit * 0.10''',
2068 'applicable_type': lambda *a: 'true',
2069 'type': lambda *a: 'percent',
2070 'amount': lambda *a: 0,
2071 'sequence': lambda *a: 1,
2072 'tax_group': lambda *a: 'vat',
2073 'ref_tax_sign': lambda *a: 1,
2074 'ref_base_sign': lambda *a: 1,
2075 'tax_sign': lambda *a: 1,
2076 'base_sign': lambda *a: 1,
2077 'include_base_amount': lambda *a: False,
2078 'type_tax_use': lambda *a: 'all',
2083 account_tax_template()
2085 # Fiscal Position Templates
2087 class account_fiscal_position_template(osv.osv):
2088 _name = 'account.fiscal.position.template'
2089 _description = 'Template for Fiscal Position'
2092 'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
2093 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2094 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2095 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2098 account_fiscal_position_template()
2100 class account_fiscal_position_tax_template(osv.osv):
2101 _name = 'account.fiscal.position.tax.template'
2102 _description = 'Fiscal Position Template Tax Mapping'
2103 _rec_name = 'position_id'
2106 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2107 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2108 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2111 account_fiscal_position_tax_template()
2113 class account_fiscal_position_account_template(osv.osv):
2114 _name = 'account.fiscal.position.account.template'
2115 _description = 'Fiscal Position Template Account Mapping'
2116 _rec_name = 'position_id'
2118 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2119 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2120 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2123 account_fiscal_position_account_template()
2125 # Multi charts of Accounts wizard
2127 class wizard_multi_charts_accounts(osv.osv_memory):
2129 Create a new account chart for a company.
2132 * an account chart template
2133 * a number of digits for formatting code of non-view accounts
2134 * a list of bank accounts owned by the company
2136 * generates all accounts from the template and assigns them to the right company
2137 * generates all taxes and tax codes, changing account assignations
2138 * generates all accounting properties and assigns them correctly
2140 _name='wizard.multi.charts.accounts'
2141 _inherit = 'res.config'
2144 'company_id':fields.many2one('res.company','Company',required=True),
2145 'chart_template_id': fields.many2one('account.chart.template','Chart Template',required=True),
2146 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts',required=True),
2147 'code_digits':fields.integer('# of Digits',required=True,help="No. of Digits to use for account code"),
2148 'seq_journal':fields.boolean('Separated Journal Sequences',help="Check this box if you want to use a different sequence for each created journal. Otherwise, all will use the same sequence."),
2151 def _get_chart(self, cr, uid, context={}):
2152 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2157 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr,uid,[uid],c)[0].company_id.id,
2158 'chart_template_id': _get_chart,
2159 'code_digits': lambda *a:6,
2162 def execute(self, cr, uid, ids, context=None):
2163 obj_multi = self.browse(cr,uid,ids[0])
2164 obj_acc = self.pool.get('account.account')
2165 obj_acc_tax = self.pool.get('account.tax')
2166 obj_journal = self.pool.get('account.journal')
2167 obj_sequence = self.pool.get('ir.sequence')
2168 obj_acc_template = self.pool.get('account.account.template')
2169 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2170 obj_fiscal_position = self.pool.get('account.fiscal.position')
2173 obj_acc_root = obj_multi.chart_template_id.account_root_id
2174 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2175 company_id = obj_multi.company_id.id
2178 acc_template_ref = {}
2179 tax_template_ref = {}
2180 tax_code_template_ref = {}
2183 #create all the tax code
2184 children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2185 children_tax_code_template.sort()
2186 for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2188 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2189 'code': tax_code_template.code,
2190 'info': tax_code_template.info,
2191 'parent_id': tax_code_template.parent_id and ((tax_code_template.parent_id.id in tax_code_template_ref) and tax_code_template_ref[tax_code_template.parent_id.id]) or False,
2192 'company_id': company_id,
2193 'sign': tax_code_template.sign,
2195 new_tax_code = self.pool.get('account.tax.code').create(cr,uid,vals)
2196 #recording the new tax code to do the mapping
2197 tax_code_template_ref[tax_code_template.id] = new_tax_code
2200 for tax in obj_multi.chart_template_id.tax_template_ids:
2204 'sequence': tax.sequence,
2205 'amount':tax.amount,
2207 'applicable_type': tax.applicable_type,
2208 'domain':tax.domain,
2209 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2210 'child_depend': tax.child_depend,
2211 'python_compute': tax.python_compute,
2212 'python_compute_inv': tax.python_compute_inv,
2213 'python_applicable': tax.python_applicable,
2214 'tax_group':tax.tax_group,
2215 'base_code_id': tax.base_code_id and ((tax.base_code_id.id in tax_code_template_ref) and tax_code_template_ref[tax.base_code_id.id]) or False,
2216 'tax_code_id': tax.tax_code_id and ((tax.tax_code_id.id in tax_code_template_ref) and tax_code_template_ref[tax.tax_code_id.id]) or False,
2217 'base_sign': tax.base_sign,
2218 'tax_sign': tax.tax_sign,
2219 'ref_base_code_id': tax.ref_base_code_id and ((tax.ref_base_code_id.id in tax_code_template_ref) and tax_code_template_ref[tax.ref_base_code_id.id]) or False,
2220 'ref_tax_code_id': tax.ref_tax_code_id and ((tax.ref_tax_code_id.id in tax_code_template_ref) and tax_code_template_ref[tax.ref_tax_code_id.id]) or False,
2221 'ref_base_sign': tax.ref_base_sign,
2222 'ref_tax_sign': tax.ref_tax_sign,
2223 'include_base_amount': tax.include_base_amount,
2224 'description':tax.description,
2225 'company_id': company_id,
2226 'type_tax_use': tax.type_tax_use
2228 new_tax = obj_acc_tax.create(cr,uid,vals_tax)
2229 #as the accounts have not been created yet, we have to wait before filling these fields
2230 todo_dict[new_tax] = {
2231 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2232 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2234 tax_template_ref[tax.id] = new_tax
2236 #deactivate the parent_store functionnality on account_account for rapidity purpose
2237 self.pool._init = True
2239 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2240 children_acc_template.sort()
2241 for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2243 for tax in account_template.tax_ids:
2244 tax_ids.append(tax_template_ref[tax.id])
2245 #create the account_account
2247 dig = obj_multi.code_digits
2248 code_main = account_template.code and len(account_template.code) or 0
2249 code_acc = account_template.code or ''
2250 if code_main>0 and code_main<=dig and account_template.type != 'view':
2251 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2253 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2254 #'sign': account_template.sign,
2255 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2257 'type': account_template.type,
2258 'user_type': account_template.user_type and account_template.user_type.id or False,
2259 'reconcile': account_template.reconcile,
2260 'shortcut': account_template.shortcut,
2261 'note': account_template.note,
2262 'parent_id': account_template.parent_id and ((account_template.parent_id.id in acc_template_ref) and acc_template_ref[account_template.parent_id.id]) or False,
2263 'tax_ids': [(6,0,tax_ids)],
2264 'company_id': company_id,
2266 new_account = obj_acc.create(cr,uid,vals)
2267 acc_template_ref[account_template.id] = new_account
2268 #reactivate the parent_store functionnality on account_account
2269 self.pool._init = False
2270 self.pool.get('account.account')._parent_store_compute(cr)
2272 for key,value in todo_dict.items():
2273 if value['account_collected_id'] or value['account_paid_id']:
2274 obj_acc_tax.write(cr, uid, [key], {
2275 'account_collected_id': acc_template_ref[value['account_collected_id']],
2276 'account_paid_id': acc_template_ref[value['account_paid_id']],
2281 view_id = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Journal View')])[0]
2282 seq_id = obj_sequence.search(cr,uid,[('name','=','Account Journal')])[0]
2284 if obj_multi.seq_journal:
2285 seq_id_sale = obj_sequence.search(cr,uid,[('name','=','Sale Journal')])[0]
2286 seq_id_purchase = obj_sequence.search(cr,uid,[('name','=','Purchase Journal')])[0]
2288 seq_id_sale = seq_id
2289 seq_id_purchase = seq_id
2291 vals_journal['view_id'] = view_id
2294 vals_journal['name'] = _('Sales Journal')
2295 vals_journal['type'] = 'sale'
2296 vals_journal['code'] = _('SAJ')
2297 vals_journal['sequence_id'] = seq_id_sale
2299 if obj_multi.chart_template_id.property_account_receivable:
2300 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2301 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2303 obj_journal.create(cr,uid,vals_journal)
2306 vals_journal['name'] = _('Purchase Journal')
2307 vals_journal['type'] = 'purchase'
2308 vals_journal['code'] = _('EXJ')
2309 vals_journal['sequence_id'] = seq_id_purchase
2311 if obj_multi.chart_template_id.property_account_payable:
2312 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2313 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2315 obj_journal.create(cr,uid,vals_journal)
2318 view_id_cash = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Cash Journal View')])[0]
2319 view_id_cur = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Multi-Currency Cash Journal View')])[0]
2320 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2323 for line in obj_multi.bank_accounts_id:
2324 #create the account_account for this bank journal
2325 tmp = self.pool.get('res.partner.bank').name_get(cr, uid, [line.acc_no.id])[0][1]
2326 dig = obj_multi.code_digits
2327 if ref_acc_bank.code:
2329 new_code = str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num)
2331 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)),'0')) + str(current_num)
2333 'name': line.acc_no.bank and line.acc_no.bank.name+' '+tmp or tmp,
2334 'currency_id': line.currency_id and line.currency_id.id or False,
2337 'user_type': account_template.user_type and account_template.user_type.id or False,
2339 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2340 'company_id': company_id,
2342 acc_cash_id = obj_acc.create(cr,uid,vals)
2344 if obj_multi.seq_journal:
2346 'name': _('Bank Journal ') + vals['name'],
2347 'code': 'account.journal',
2349 seq_id = obj_sequence.create(cr,uid,vals_seq)
2351 #create the bank journal
2352 vals_journal['name']= vals['name']
2353 vals_journal['code']= _('BNK') + str(current_num)
2354 vals_journal['sequence_id'] = seq_id
2355 vals_journal['type'] = 'cash'
2356 if line.currency_id:
2357 vals_journal['view_id'] = view_id_cur
2358 vals_journal['currency'] = line.currency_id.id
2360 vals_journal['view_id'] = view_id_cash
2361 vals_journal['default_credit_account_id'] = acc_cash_id
2362 vals_journal['default_debit_account_id'] = acc_cash_id
2363 obj_journal.create(cr,uid,vals_journal)
2367 #create the properties
2368 property_obj = self.pool.get('ir.property')
2369 fields_obj = self.pool.get('ir.model.fields')
2372 ('property_account_receivable','res.partner','account.account'),
2373 ('property_account_payable','res.partner','account.account'),
2374 ('property_account_expense_categ','product.category','account.account'),
2375 ('property_account_income_categ','product.category','account.account'),
2376 ('property_account_expense','product.template','account.account'),
2377 ('property_account_income','product.template','account.account')
2379 for record in todo_list:
2381 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2382 account = getattr(obj_multi.chart_template_id, record[0])
2383 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2386 'company_id': company_id,
2387 'fields_id': field[0],
2388 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2391 #the property exist: modify it
2392 property_obj.write(cr, uid, r, vals)
2394 #create the property
2395 property_obj.create(cr, uid, vals)
2397 fp_ids = obj_fiscal_position_template.search(cr, uid,[('chart_template_id', '=', obj_multi.chart_template_id.id)])
2400 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2403 'company_id' : company_id,
2404 'name' : position.name,
2406 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2408 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2409 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2411 for tax in position.tax_ids:
2413 'tax_src_id' : tax_template_ref[tax.tax_src_id.id],
2414 'tax_dest_id' : tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2415 'position_id' : new_fp,
2417 obj_tax_fp.create(cr, uid, vals_tax)
2419 for acc in position.account_ids:
2421 'account_src_id' : acc_template_ref[acc.account_src_id.id],
2422 'account_dest_id' : acc_template_ref[acc.account_dest_id.id],
2423 'position_id' : new_fp,
2425 obj_ac_fp.create(cr, uid, vals_acc)
2426 wizard_multi_charts_accounts()
2428 class account_bank_accounts_wizard(osv.osv_memory):
2429 _name='account.bank.accounts.wizard'
2432 'acc_no':fields.many2one('res.partner.bank','Account No.',required=True),
2433 'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2434 'currency_id':fields.many2one('res.currency', 'Currency'),
2437 account_bank_accounts_wizard()
2439 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: