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 ##############################################################################
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
25 from operator import itemgetter
29 from osv import fields, osv
30 import decimal_precision as dp
31 from tools.translate import _
33 def check_cycle(self, cr, uid, ids, context=None):
34 """ climbs the ``self._table.parent_id`` chains for 100 levels or
35 until it can't find any more parent(s)
37 Returns true if it runs out of parents (no cycle), false if
38 it can recurse 100 times without ending all chains
42 cr.execute('SELECT DISTINCT parent_id '\
43 'FROM '+self._table+' '\
45 'AND parent_id IS NOT NULL',(tuple(ids),))
46 ids = map(itemgetter(0), cr.fetchall())
52 class account_payment_term(osv.osv):
53 _name = "account.payment.term"
54 _description = "Payment Term"
56 'name': fields.char('Payment Term', size=64, translate=True, required=True),
57 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the payment term without removing it."),
58 'note': fields.text('Description', translate=True),
59 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
66 def compute(self, cr, uid, id, value, date_ref=False, context=None):
68 date_ref = datetime.now().strftime('%Y-%m-%d')
69 pt = self.browse(cr, uid, id, context=context)
72 obj_precision = self.pool.get('decimal.precision')
73 for line in pt.line_ids:
74 prec = obj_precision.precision_get(cr, uid, 'Account')
75 if line.value == 'fixed':
76 amt = round(line.value_amount, prec)
77 elif line.value == 'procent':
78 amt = round(value * line.value_amount, prec)
79 elif line.value == 'balance':
80 amt = round(amount, prec)
82 next_date = (datetime.strptime(date_ref, '%Y-%m-%d') + relativedelta(days=line.days))
84 next_first_date = next_date + relativedelta(day=1,months=1) #Getting 1st of next month
85 next_date = next_first_date + relativedelta(days=line.days2)
87 next_date += relativedelta(day=line.days2, months=1)
88 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
92 account_payment_term()
94 class account_payment_term_line(osv.osv):
95 _name = "account.payment.term.line"
96 _description = "Payment Term Line"
98 'name': fields.char('Line Name', size=32, required=True),
99 '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"),
100 'value': fields.selection([('procent', 'Percent'),
101 ('balance', 'Balance'),
102 ('fixed', 'Fixed Amount')], 'Valuation',
103 required=True, help="""Select here the kind of valuation related to this payment term line. Note that you should have your last line with the type 'Balance' to ensure that the whole amount will be threated."""),
105 'value_amount': fields.float('Amount To Pay', digits_compute=dp.get_precision('Payment Term'), help="For percent enter a ratio between 0-1."),
106 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
107 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
108 '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)."),
109 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
118 def _check_percent(self, cr, uid, ids, context=None):
119 obj = self.browse(cr, uid, ids[0], context=context)
120 if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
125 (_check_percent, 'Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% ', ['value_amount']),
128 account_payment_term_line()
130 class account_account_type(osv.osv):
131 _name = "account.account.type"
132 _description = "Account Type"
134 'name': fields.char('Account Type', size=64, required=True),
135 'code': fields.char('Code', size=32, required=True),
136 'close_method': fields.selection([('none', 'None'), ('balance', 'Balance'), ('detail', 'Detail'), ('unreconciled', 'Unreconciled')], 'Deferral Method', required=True, help="""Set here the method that will be used to generate the end of year journal entries for all the accounts of this type.
138 'None' means that nothing will be done.
139 'Balance' will generally be used for cash accounts.
140 'Detail' will copy each existing journal item of the previous year, even the reconciled ones.
141 'Unreconciled' will copy only the journal items that were unreconciled on the first day of the new fiscal year."""),
142 'sign': fields.selection([(-1, 'Reverse balance sign'), (1, 'Preserve balance sign')], 'Sign on Reports', required=True, help='For accounts that are typically more debited than credited and that you would like to print as negative amounts in your reports, you should reverse the sign of the balance; e.g.: Expense account. The same applies for accounts that are typically more credited than debited and that you would like to print as positive amounts in your reports; e.g.: Income account.'),
143 'report_type':fields.selection([
145 ('income','Profit & Loss (Income Accounts)'),
146 ('expense','Profit & Loss (Expense Accounts)'),
147 ('asset','Balance Sheet (Asset Accounts)'),
148 ('liability','Balance Sheet (Liability Accounts)')
149 ],'P&L / BS Category', select=True, readonly=False, help="This field is used to generate legal reports: profit and loss, balance sheet.", required=True),
150 'note': fields.text('Description'),
153 'close_method': 'none',
155 'report_type': 'none',
159 account_account_type()
161 def _code_get(self, cr, uid, context=None):
162 acc_type_obj = self.pool.get('account.account.type')
163 ids = acc_type_obj.search(cr, uid, [])
164 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context=context)
165 return [(r['code'], r['name']) for r in res]
167 #----------------------------------------------------------
169 #----------------------------------------------------------
171 class account_tax(osv.osv):
172 _name = 'account.tax'
175 class account_account(osv.osv):
176 _order = "parent_left"
177 _parent_order = "code"
178 _name = "account.account"
179 _description = "Account"
181 logger = netsvc.Logger()
183 def search(self, cr, uid, args, offset=0, limit=None, order=None,
184 context=None, count=False):
189 while pos < len(args):
191 if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
192 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
193 if args[pos][0] == 'journal_id':
197 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2], context=context)
198 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
199 args[pos] = ('type','not in',('consolidation','view'))
201 ids3 = map(lambda x: x.id, jour.type_control_ids)
202 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
203 ids1 += map(lambda x: x.id, jour.account_control_ids)
204 args[pos] = ('id', 'in', ids1)
207 if context and context.has_key('consolidate_children'): #add consolidated children of accounts
208 ids = super(account_account, self).search(cr, uid, args, offset, limit,
209 order, context=context, count=count)
210 for consolidate_child in self.browse(cr, uid, context['account_id'], context=context).child_consol_ids:
211 ids.append(consolidate_child.id)
214 return super(account_account, self).search(cr, uid, args, offset, limit,
215 order, context=context, count=count)
217 def _get_children_and_consol(self, cr, uid, ids, context=None):
218 #this function search for all the children and all consolidated children (recursively) of the given account ids
219 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
221 for rec in self.browse(cr, uid, ids2, context=context):
222 for child in rec.child_consol_ids:
223 ids3.append(child.id)
225 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
228 def __compute(self, cr, uid, ids, field_names, arg=None, context=None,
229 query='', query_params=()):
230 """ compute the balance, debit and/or credit for the provided
234 `field_names`: the fields to compute (a list of any of
235 'balance', 'debit' and 'credit')
236 `arg`: unused fields.function stuff
237 `query`: additional query filter (as a string)
238 `query_params`: parameters for the provided query string
239 (__compute will handle their escaping) as a
243 'balance': "COALESCE(SUM(l.debit),0) " \
244 "- COALESCE(SUM(l.credit), 0) as balance",
245 'debit': "COALESCE(SUM(l.debit), 0) as debit",
246 'credit': "COALESCE(SUM(l.credit), 0) as credit",
247 'foreign_balance': "COALESCE(SUM(l.amount_currency), 0) as foreign_balance",
249 #get all the necessary accounts
250 children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
251 #compute for each account the balance/debit/credit from the move lines
254 null_result = dict((fn, 0.0) for fn in field_names)
255 if children_and_consolidated:
256 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
260 wheres.append(query.strip())
261 if aml_query.strip():
262 wheres.append(aml_query.strip())
263 filters = " AND ".join(wheres)
264 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
265 'Filters: %s'%filters)
266 # IN might not work ideally in case there are too many
267 # children_and_consolidated, in that case join on a
269 # SELECT l.account_id as id FROM account_move_line l
270 # INNER JOIN (VALUES (id1), (id2), (id3), ...) AS tmp (id)
271 # ON l.account_id = tmp.id
272 # or make _get_children_and_consol return a query and join on that
273 request = ("SELECT l.account_id as id, " +\
274 ', '.join(map(mapping.__getitem__, mapping.keys())) +
275 " FROM account_move_line l" \
276 " WHERE l.account_id IN %s " \
278 " GROUP BY l.account_id")
279 params = (tuple(children_and_consolidated),) + query_params
280 cr.execute(request, params)
281 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
282 'Status: %s'%cr.statusmessage)
284 for res in cr.dictfetchall():
285 accounts[res['id']] = res
287 # consolidate accounts with direct children
288 children_and_consolidated.reverse()
289 brs = list(self.browse(cr, uid, children_and_consolidated, context=context))
291 currency_obj = self.pool.get('res.currency')
295 # for child in current.child_id:
296 # if child.id not in sums:
297 # can_compute = False
299 # brs.insert(0, brs.pop(brs.index(child)))
301 # brs.insert(0, child)
303 for fn in field_names:
304 sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
305 for child in current.child_id:
306 if child.company_id.currency_id.id == current.company_id.currency_id.id:
307 sums[current.id][fn] += sums[child.id][fn]
309 sums[current.id][fn] += currency_obj.compute(cr, uid, child.company_id.currency_id.id, current.company_id.currency_id.id, sums[child.id][fn], context=context)
311 # as we have to relay on values computed before this is calculated separately than previous fields
312 if current.currency_id and current.exchange_rate and \
313 ('adjusted_balance' in field_names or 'unrealized_gain_loss' in field_names):
314 # Computing Adjusted Balance and Unrealized Gains and losses
315 # Adjusted Balance = Foreign Balance / Exchange Rate
316 # Unrealized Gains and losses = Adjusted Balance - Balance
317 adj_bal = sums[current.id].get('foreign_balance', 0.0) / current.exchange_rate
318 sums[current.id].update({'adjusted_balance': adj_bal, 'unrealized_gain_loss': adj_bal - sums[current.id].get('balance', 0.0)})
321 res[id] = sums.get(id, null_result)
324 res[id] = null_result
327 def _get_company_currency(self, cr, uid, ids, field_name, arg, context=None):
329 for rec in self.browse(cr, uid, ids, context=context):
330 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.symbol)
333 def _get_child_ids(self, cr, uid, ids, field_name, arg, context=None):
335 for record in self.browse(cr, uid, ids, context=context):
336 if record.child_parent_ids:
337 result[record.id] = [x.id for x in record.child_parent_ids]
339 result[record.id] = []
341 if record.child_consol_ids:
342 for acc in record.child_consol_ids:
343 if acc.id not in result[record.id]:
344 result[record.id].append(acc.id)
348 def _get_level(self, cr, uid, ids, field_name, arg, context=None):
350 accounts = self.browse(cr, uid, ids, context=context)
351 for account in accounts:
353 parent = account.parent_id
356 parent = parent.parent_id
357 res[account.id] = level
360 def _set_credit_debit(self, cr, uid, account_id, name, value, arg, context=None):
361 if context.get('config_invisible', True):
364 account = self.browse(cr, uid, account_id, context=context)
365 diff = value - getattr(account,name)
369 journal_obj = self.pool.get('account.journal')
370 jids = journal_obj.search(cr, uid, [('type','=','situation'),('centralisation','=',1),('company_id','=',account.company_id.id)], context=context)
372 raise osv.except_osv(_('Error!'),_("You need an Opening journal with centralisation checked to set the initial balance!"))
374 period_obj = self.pool.get('account.period')
375 pids = period_obj.search(cr, uid, [('special','=',True),('company_id','=',account.company_id.id)], context=context)
377 raise osv.except_osv(_('Error!'),_("No opening/closing period defined, please create one to set the initial balance!"))
379 move_obj = self.pool.get('account.move.line')
380 move_id = move_obj.search(cr, uid, [
381 ('journal_id','=',jids[0]),
382 ('period_id','=',pids[0]),
383 ('account_id','=', account_id),
385 ('name','=', _('Opening Balance'))
388 move = move_obj.browse(cr, uid, move_id[0], context=context)
389 move_obj.write(cr, uid, move_id[0], {
390 name: diff+getattr(move,name)
394 raise osv.except_osv(_('Error!'),_("Unable to adapt the initial balance (negative value)!"))
395 nameinv = (name=='credit' and 'debit') or 'credit'
396 move_id = move_obj.create(cr, uid, {
397 'name': _('Opening Balance'),
398 'account_id': account_id,
399 'journal_id': jids[0],
400 'period_id': pids[0],
407 'name': fields.char('Name', size=256, required=True, select=True),
408 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
409 'code': fields.char('Code', size=64, required=True, select=1),
410 'type': fields.selection([
412 ('other', 'Regular'),
413 ('receivable', 'Receivable'),
414 ('payable', 'Payable'),
415 ('liquidity','Liquidity'),
416 ('consolidation', 'Consolidation'),
417 ('closed', 'Closed'),
418 ], 'Internal Type', required=True, help="The 'Internal Type' is used for features available on "\
419 "different types of accounts: view can not have journal items, consolidation are accounts that "\
420 "can have children accounts for multi-company consolidations, payable/receivable are for "\
421 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
422 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
423 help="Account Type is used for information purpose, to generate "
424 "country-specific legal reports, and set the rules to close a fiscal year and generate opening entries."),
425 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
426 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
427 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
428 'child_id': fields.function(_get_child_ids, type='many2many', relation="account.account", string="Child Accounts"),
429 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Balance', multi='balance'),
430 'credit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Credit', multi='balance'),
431 'debit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'),
432 'foreign_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Foreign Balance', multi='balance',
433 help="Total amount (in Secondary currency) for transactions held in secondary currency for this account."),
434 'adjusted_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Adjusted Balance', multi='balance',
435 help="Total amount (in Company currency) for transactions held in secondary currency for this account."),
436 'unrealized_gain_loss': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Unrealized Gain or Loss', multi='balance',
437 help="Value of Loss or Gain due to changes in exchange rate when doing multi-currency transactions."),
438 'reconcile': fields.boolean('Allow Reconciliation', help="Check this box if this account allows reconciliation of journal items."),
439 'exchange_rate': fields.related('currency_id', 'rate', type='float', string='Exchange Rate', digits=(12,6)),
440 'shortcut': fields.char('Shortcut', size=12),
441 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
442 'account_id', 'tax_id', 'Default Taxes'),
443 'note': fields.text('Note'),
444 'company_currency_id': fields.function(_get_company_currency, type='many2one', relation='res.currency', string='Company Currency'),
445 'company_id': fields.many2one('res.company', 'Company', required=True),
446 'active': fields.boolean('Active', select=2, help="If the active field is set to False, it will allow you to hide the account without removing it."),
448 'parent_left': fields.integer('Parent Left', select=1),
449 'parent_right': fields.integer('Parent Right', select=1),
450 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
452 'This will select how the current currency rate for outgoing transactions is computed. '\
453 'In most countries the legal method is "average" but only a few software systems are able to '\
454 'manage this. So if you import from another software system you may have to use the rate at date. ' \
455 'Incoming transactions always use the rate at date.', \
457 'level': fields.function(_get_level, string='Level', method=True, type='integer',
459 'account.account': (_get_children_and_consol, ['level', 'parent_id'], 10),
467 'currency_mode': 'current',
468 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
471 def _check_recursion(self, cr, uid, ids, context=None):
472 obj_self = self.browse(cr, uid, ids[0], context=context)
473 p_id = obj_self.parent_id and obj_self.parent_id.id
474 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
477 cr.execute('SELECT DISTINCT child_id '\
478 'FROM account_account_consol_rel '\
479 'WHERE parent_id IN %s', (tuple(ids),))
480 child_ids = map(itemgetter(0), cr.fetchall())
482 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
485 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
486 if p_id and (p_id in s_ids):
492 def _check_type(self, cr, uid, ids, context=None):
495 accounts = self.browse(cr, uid, ids, context=context)
496 for account in accounts:
497 if account.child_id and account.type not in ('view', 'consolidation'):
501 def _check_account_type(self, cr, uid, ids, context=None):
502 for account in self.browse(cr, uid, ids, context=context):
503 if account.type in ('receivable', 'payable') and account.user_type.close_method != 'unreconciled':
508 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id']),
509 (_check_type, 'Configuration Error! \nYou can not define children to an account with internal type different of "View"! ', ['type']),
510 (_check_account_type, 'Configuration Error! \nYou can not select an account type with a deferral method different of "Unreconciled" for accounts with internal type "Payable/Receivable"! ', ['user_type','type']),
513 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
515 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
521 if name and str(name).startswith('partner:'):
522 part_id = int(name.split(':')[1])
523 part = self.pool.get('res.partner').browse(cr, user, part_id, context=context)
524 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
526 if name and str(name).startswith('type:'):
527 type = name.split(':')[1]
528 args += [('type', '=', type)]
533 ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
535 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
537 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
538 if not ids and len(name.split()) >= 2:
539 #Separating code and name of account for searching
540 operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
541 ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
543 ids = self.search(cr, user, args, context=context, limit=limit)
544 return self.name_get(cr, user, ids, context=context)
546 def name_get(self, cr, uid, ids, context=None):
549 reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
552 name = record['name']
554 name = record['code'] + ' ' + name
555 res.append((record['id'], name))
558 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
559 account = self.browse(cr, uid, id, context=context)
563 default = default.copy()
564 default['code'] = (account['code'] or '') + '(copy)'
567 if account.id in done_list:
569 done_list.append(account.id)
571 for child in account.child_id:
572 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
574 new_child_ids.append(child_ids)
575 default['child_parent_ids'] = [(6, 0, new_child_ids)]
577 default['child_parent_ids'] = False
578 return super(account_account, self).copy(cr, uid, id, default, context=context)
580 def _check_moves(self, cr, uid, ids, method, context=None):
581 line_obj = self.pool.get('account.move.line')
582 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
584 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
585 if method == 'write':
586 raise osv.except_osv(_('Error !'), _('You can not desactivate an account that contains some journal items.'))
587 elif method == 'unlink':
588 raise osv.except_osv(_('Error !'), _('You can not remove an account containing journal items!. '))
589 #Checking whether the account is set as a property to any Partner or not
590 value = 'account.account,' + str(ids[0])
591 partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
593 raise osv.except_osv(_('Warning !'), _('You can not remove/desactivate an account which is set on a customer or supplier.'))
596 def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
597 group1 = ['payable', 'receivable', 'other']
598 group2 = ['consolidation','view']
599 line_obj = self.pool.get('account.move.line')
600 for account in self.browse(cr, uid, ids, context=context):
601 old_type = account.type
602 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
603 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
604 #Check for 'Closed' type
605 if old_type == 'closed' and new_type !='closed':
606 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains journal items!"))
607 #Check for change From group1 to group2 and vice versa
608 if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
609 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from '%s' to '%s' type as it contains journal items!") % (old_type,new_type,))
612 def write(self, cr, uid, ids, vals, context=None):
618 if isinstance(ids, (int, long)):
621 # Dont allow changing the company_id when account_move_line already exist
622 if 'company_id' in vals:
623 move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
625 # Allow the write if the value is the same
626 for i in [i['company_id'][0] for i in self.read(cr,uid,ids,['company_id'])]:
627 if vals['company_id']!=i:
628 raise osv.except_osv(_('Warning !'), _('You cannot modify Company of account as its related record exist in Entry Lines'))
629 if 'active' in vals and not vals['active']:
630 self._check_moves(cr, uid, ids, "write", context=context)
631 if 'type' in vals.keys():
632 self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
633 return super(account_account, self).write(cr, uid, ids, vals, context=context)
635 def unlink(self, cr, uid, ids, context=None):
636 self._check_moves(cr, uid, ids, "unlink", context=context)
637 return super(account_account, self).unlink(cr, uid, ids, context=context)
641 class account_journal_view(osv.osv):
642 _name = "account.journal.view"
643 _description = "Journal View"
645 'name': fields.char('Journal View', size=64, required=True),
646 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
650 account_journal_view()
653 class account_journal_column(osv.osv):
655 def _col_get(self, cr, user, context=None):
657 cols = self.pool.get('account.move.line')._columns
659 if col in ('period_id', 'journal_id'):
661 result.append( (col, cols[col].string) )
665 _name = "account.journal.column"
666 _description = "Journal Column"
668 'name': fields.char('Column Name', size=64, required=True),
669 'field': fields.selection(_col_get, 'Field Name', required=True, size=32),
670 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
671 'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column.", readonly=True),
672 'required': fields.boolean('Required'),
673 'readonly': fields.boolean('Readonly'),
675 _order = "view_id, sequence"
677 account_journal_column()
679 class account_journal(osv.osv):
680 _name = "account.journal"
681 _description = "Journal"
683 'name': fields.char('Journal Name', size=64, required=True),
684 'code': fields.char('Code', size=5, required=True, help="The code will be displayed on reports."),
685 'type': fields.selection([('sale', 'Sale'),('sale_refund','Sale Refund'), ('purchase', 'Purchase'), ('purchase_refund','Purchase Refund'), ('cash', 'Cash'), ('bank', 'Bank and Cheques'), ('general', 'General'), ('situation', 'Opening/Closing Situation')], 'Type', size=32, required=True,
686 help="Select 'Sale' for customer invoices journals."\
687 " Select 'Purchase' for supplier invoices journals."\
688 " Select 'Cash' or 'Bank' for journals that are used in customer or supplier payments."\
689 " Select 'General' for miscellaneous operations journals."\
690 " Select 'Opening/Closing Situation' for entries generated for new fiscal years."),
691 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
692 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
693 'view_id': fields.many2one('account.journal.view', 'Display Mode', required=True, help="Gives the view used when writing or browsing entries in this journal. The view tells OpenERP 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."),
694 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
695 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
696 '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."),
697 'update_posted': fields.boolean('Allow Cancelling Entries', help="Check this box if you want to allow the cancellation the entries related to this journal or of the invoice related to this journal"),
698 '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."),
699 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="This field contains the informatin related to the numbering of the journal entries of this journal.", required=True),
700 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
701 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
702 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
703 'entry_posted': fields.boolean('Skip \'Draft\' State for Manual Entries', help='Check this box if you don\'t want new journal entries to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation. \nNote that journal entries that are automatically created by the system are always skipping that state.'),
704 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
705 'allow_date':fields.boolean('Check Date in Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'),
709 'user_id': lambda self, cr, uid, context: uid,
710 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
713 ('code_company_uniq', 'unique (code, company_id)', 'The code of the journal must be unique per company !'),
714 ('name_company_uniq', 'unique (name, company_id)', 'The name of the journal must be unique per company !'),
719 def _check_currency(self, cr, uid, ids, context=None):
720 for journal in self.browse(cr, uid, ids, context=context):
722 if journal.default_credit_account_id and not journal.default_credit_account_id.currency_id.id == journal.currency.id:
724 if journal.default_debit_account_id and not journal.default_debit_account_id.currency_id.id == journal.currency.id:
729 (_check_currency, 'Configuration error! The currency chosen should be shared by the default accounts too.', ['currency','default_debit_account_id','default_credit_account_id']),
732 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
733 journal = self.browse(cr, uid, id, context=context)
736 default = default.copy()
737 default['code'] = (journal['code'] or '') + '(copy)'
738 default['name'] = (journal['name'] or '') + '(copy)'
739 default['sequence_id'] = False
740 return super(account_journal, self).copy(cr, uid, id, default, context=context)
742 def write(self, cr, uid, ids, vals, context=None):
745 if isinstance(ids, (int, long)):
747 for journal in self.browse(cr, uid, ids, context=context):
748 if 'company_id' in vals and journal.company_id.id != vals['company_id']:
749 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
751 raise osv.except_osv(_('Warning !'), _('You can not modify the company of this journal as its related record exist in journal items'))
752 return super(account_journal, self).write(cr, uid, ids, vals, context=context)
754 def create_sequence(self, cr, uid, vals, context=None):
755 """ Create new no_gap entry sequence for every new Joural
757 # in account.journal code is actually the prefix of the sequence
758 # whereas ir.sequence code is a key to lookup global sequences.
759 prefix = vals['code'].upper()
762 'name': vals['name'],
763 'implementation':'no_gap',
764 'prefix': prefix + "/%(year)s/",
766 'number_increment': 1
768 if 'company_id' in vals:
769 seq['company_id'] = vals['company_id']
770 return self.pool.get('ir.sequence').create(cr, uid, seq)
772 def create(self, cr, uid, vals, context=None):
773 if not 'sequence_id' in vals or not vals['sequence_id']:
774 vals.update({'sequence_id': self.create_sequence(cr, uid, vals, context)})
775 return super(account_journal, self).create(cr, uid, vals, context)
777 def name_get(self, cr, user, ids, context=None):
779 Returns a list of tupples containing id, name.
780 result format: {[(id, name), (id, name), ...]}
782 @param cr: A database cursor
783 @param user: ID of the user currently logged in
784 @param ids: list of ids for which name should be read
785 @param context: context arguments, like lang, time zone
787 @return: Returns a list of tupples containing id, name
789 result = self.browse(cr, user, ids, context=context)
793 currency = rs.currency
795 currency = rs.company_id.currency_id
796 name = "%s (%s)" % (rs.name, currency.name)
797 res += [(rs.id, name)]
800 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
806 if context.get('journal_type', False):
807 args += [('type','=',context.get('journal_type'))]
809 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
811 ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
813 return self.name_get(cr, user, ids, context=context)
815 def onchange_type(self, cr, uid, ids, type, currency, context=None):
816 obj_data = self.pool.get('ir.model.data')
817 user_pool = self.pool.get('res.users')
820 'sale':'account_sp_journal_view',
821 'sale_refund':'account_sp_refund_journal_view',
822 'purchase':'account_sp_journal_view',
823 'purchase_refund':'account_sp_refund_journal_view',
824 'cash':'account_journal_bank_view',
825 'bank':'account_journal_bank_view',
826 'general':'account_journal_view',
827 'situation':'account_journal_view'
831 view_id = type_map.get(type, 'account_journal_view')
832 user = user_pool.browse(cr, uid, uid)
833 if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
834 view_id = 'account_journal_bank_view_multi'
835 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
836 data = obj_data.browse(cr, uid, data_id[0], context=context)
839 'centralisation':type == 'situation',
840 'view_id':data.res_id,
848 class account_fiscalyear(osv.osv):
849 _name = "account.fiscalyear"
850 _description = "Fiscal Year"
852 'name': fields.char('Fiscal Year', size=64, required=True),
853 'code': fields.char('Code', size=6, required=True),
854 'company_id': fields.many2one('res.company', 'Company', required=True),
855 'date_start': fields.date('Start Date', required=True),
856 'date_stop': fields.date('End Date', required=True),
857 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
858 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True),
862 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
864 _order = "date_start, id"
867 def _check_duration(self, cr, uid, ids, context=None):
868 obj_fy = self.browse(cr, uid, ids[0], context=context)
869 if obj_fy.date_stop < obj_fy.date_start:
874 (_check_duration, 'Error! The start date of the fiscal year must be before his end date.', ['date_start','date_stop'])
877 def create_period3(self, cr, uid, ids, context=None):
878 return self.create_period(cr, uid, ids, context, 3)
880 def create_period(self, cr, uid, ids, context=None, interval=1):
881 period_obj = self.pool.get('account.period')
882 for fy in self.browse(cr, uid, ids, context=context):
883 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
884 period_obj.create(cr, uid, {
885 'name': "%s %s" % (_('Opening Period'), ds.strftime('%Y')),
886 'code': ds.strftime('00/%Y'),
890 'fiscalyear_id': fy.id,
892 while ds.strftime('%Y-%m-%d') < fy.date_stop:
893 de = ds + relativedelta(months=interval, days=-1)
895 if de.strftime('%Y-%m-%d') > fy.date_stop:
896 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
898 period_obj.create(cr, uid, {
899 'name': ds.strftime('%m/%Y'),
900 'code': ds.strftime('%m/%Y'),
901 'date_start': ds.strftime('%Y-%m-%d'),
902 'date_stop': de.strftime('%Y-%m-%d'),
903 'fiscalyear_id': fy.id,
905 ds = ds + relativedelta(months=interval)
908 def find(self, cr, uid, dt=None, exception=True, context=None):
909 res = self.finds(cr, uid, dt, exception, context=context)
910 return res and res[0] or False
912 def finds(self, cr, uid, dt=None, exception=True, context=None):
913 if context is None: context = {}
915 dt = time.strftime('%Y-%m-%d')
916 args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
917 if context.get('company_id', False):
918 args.append(('company_id', '=', context['company_id']))
920 company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
921 args.append(('company_id', '=', company_id))
922 ids = self.search(cr, uid, args, context=context)
925 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
930 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
937 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
939 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
940 return self.name_get(cr, user, ids, context=context)
944 class account_period(osv.osv):
945 _name = "account.period"
946 _description = "Account period"
948 'name': fields.char('Period Name', size=64, required=True),
949 'code': fields.char('Code', size=12),
950 'special': fields.boolean('Opening/Closing Period', size=12,
951 help="These periods can overlap."),
952 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
953 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
954 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
955 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True,
956 help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
957 'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
962 _order = "date_start, special desc"
964 ('name_company_uniq', 'unique(name, company_id)', 'The name of the period must be unique per company!'),
967 def _check_duration(self,cr,uid,ids,context=None):
968 obj_period = self.browse(cr, uid, ids[0], context=context)
969 if obj_period.date_stop < obj_period.date_start:
973 def _check_year_limit(self,cr,uid,ids,context=None):
974 for obj_period in self.browse(cr, uid, ids, context=context):
975 if obj_period.special:
978 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
979 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
980 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
981 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
984 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
985 for period in self.browse(cr, uid, pids):
986 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
991 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
992 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
995 def next(self, cr, uid, period, step, context=None):
996 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
1001 def find(self, cr, uid, dt=None, context=None):
1002 if context is None: context = {}
1004 dt = time.strftime('%Y-%m-%d')
1005 #CHECKME: shouldn't we check the state of the period?
1006 args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
1007 if context.get('company_id', False):
1008 args.append(('company_id', '=', context['company_id']))
1010 company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
1011 args.append(('company_id', '=', company_id))
1012 ids = self.search(cr, uid, args, context=context)
1014 raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create one.')%dt)
1017 def action_draft(self, cr, uid, ids, *args):
1019 cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),))
1020 cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),))
1023 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1030 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
1032 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
1033 return self.name_get(cr, user, ids, context=context)
1035 def write(self, cr, uid, ids, vals, context=None):
1036 if 'company_id' in vals:
1037 move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
1039 raise osv.except_osv(_('Warning !'), _('You can not modify company of this period as some journal items exists.'))
1040 return super(account_period, self).write(cr, uid, ids, vals, context=context)
1042 def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
1043 period_from = self.browse(cr, uid, period_from_id)
1044 period_date_start = period_from.date_start
1045 company1_id = period_from.company_id.id
1046 period_to = self.browse(cr, uid, period_to_id)
1047 period_date_stop = period_to.date_stop
1048 company2_id = period_to.company_id.id
1049 if company1_id != company2_id:
1050 raise osv.except_osv(_('Error'), _('You should have chosen periods that belongs to the same company'))
1051 if period_date_start > period_date_stop:
1052 raise osv.except_osv(_('Error'), _('Start period should be smaller then End period'))
1053 #for period from = january, we want to exclude the opening period (but it has same date_from, so we have to check if period_from is special or not to include that clause or not in the search).
1054 if period_from.special:
1055 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id)])
1056 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id), ('special', '=', False)])
1060 class account_journal_period(osv.osv):
1061 _name = "account.journal.period"
1062 _description = "Journal Period"
1064 def _icon_get(self, cr, uid, ids, field_name, arg=None, context=None):
1065 result = {}.fromkeys(ids, 'STOCK_NEW')
1066 for r in self.read(cr, uid, ids, ['state']):
1068 'draft': 'STOCK_NEW',
1069 'printed': 'STOCK_PRINT_PREVIEW',
1070 'done': 'STOCK_DIALOG_AUTHENTICATION',
1071 }.get(r['state'], 'STOCK_NEW')
1075 'name': fields.char('Journal-Period Name', size=64, required=True),
1076 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
1077 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
1078 'icon': fields.function(_icon_get, string='Icon', type='char', size=32),
1079 'active': fields.boolean('Active', required=True, help="If the active field is set to False, it will allow you to hide the journal period without removing it."),
1080 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
1081 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.'),
1082 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
1083 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
1086 def _check(self, cr, uid, ids, context=None):
1087 for obj in self.browse(cr, uid, ids, context=context):
1088 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))
1091 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
1094 def write(self, cr, uid, ids, vals, context=None):
1095 self._check(cr, uid, ids, context=context)
1096 return super(account_journal_period, self).write(cr, uid, ids, vals, context=context)
1098 def create(self, cr, uid, vals, context=None):
1099 period_id = vals.get('period_id',False)
1101 period = self.pool.get('account.period').browse(cr, uid, period_id, context=context)
1102 vals['state']=period.state
1103 return super(account_journal_period, self).create(cr, uid, vals, context)
1105 def unlink(self, cr, uid, ids, context=None):
1106 self._check(cr, uid, ids, context=context)
1107 return super(account_journal_period, self).unlink(cr, uid, ids, context=context)
1113 _order = "period_id"
1115 account_journal_period()
1117 class account_fiscalyear(osv.osv):
1118 _inherit = "account.fiscalyear"
1119 _description = "Fiscal Year"
1121 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1124 def copy(self, cr, uid, id, default={}, context=None):
1127 'end_journal_period_id': False
1129 return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1131 account_fiscalyear()
1132 #----------------------------------------------------------
1134 #----------------------------------------------------------
1135 class account_move(osv.osv):
1136 _name = "account.move"
1137 _description = "Account Entry"
1140 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1142 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1143 result format: {[(id, name), (id, name), ...]}
1145 @param cr: A database cursor
1146 @param user: ID of the user currently logged in
1147 @param name: name to search
1148 @param args: other arguments
1149 @param operator: default operator is 'ilike', it can be changed
1150 @param context: context arguments, like lang, time zone
1151 @param limit: Returns first 'n' ids of complete result, default is 80.
1153 @return: Returns a list of tuples containing id and name
1160 ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1162 if not ids and name and type(name) == int:
1163 ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1166 ids += self.search(cr, user, args, limit=limit, context=context)
1168 return self.name_get(cr, user, ids, context=context)
1170 def name_get(self, cursor, user, ids, context=None):
1171 if isinstance(ids, (int, long)):
1176 data_move = self.pool.get('account.move').browse(cursor, user, ids, context=context)
1177 for move in data_move:
1178 if move.state=='draft':
1179 name = '*' + str(move.id)
1182 res.append((move.id, name))
1185 def _get_period(self, cr, uid, context=None):
1186 periods = self.pool.get('account.period').find(cr, uid)
1191 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1192 if not ids: return {}
1193 cr.execute( 'SELECT move_id, SUM(debit) '\
1194 'FROM account_move_line '\
1195 'WHERE move_id IN %s '\
1196 'GROUP BY move_id', (tuple(ids),))
1197 result = dict(cr.fetchall())
1199 result.setdefault(id, 0.0)
1202 def _search_amount(self, cr, uid, obj, name, args, context):
1206 if isinstance(cond[2],(list,tuple)):
1207 if cond[1] in ['in','not in']:
1208 amount = tuple(cond[2])
1212 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1215 cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1216 res_ids = set(id[0] for id in cr.fetchall())
1217 ids = ids and (ids & res_ids) or res_ids
1219 return [('id', 'in', tuple(ids))]
1220 return [('id', '=', '0')]
1223 'name': fields.char('Number', size=64, required=True),
1224 'ref': fields.char('Reference', size=64),
1225 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1226 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1227 'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'State', required=True, readonly=True,
1228 help='All manually created new journal entries are usually in the state \'Unposted\', but you can set the option to skip that state on the related journal. In that case, they will be behave as journal entries automatically created by the system on document validation (invoices, bank statements...) and will be created in \'Posted\' state.'),
1229 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1230 'to_check': fields.boolean('To Review', help='Check this box if you are unsure of that journal entry and if you want to note it as \'to be reviewed\' by an accounting expert.'),
1231 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1232 'amount': fields.function(_amount_compute, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1233 'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}, select=True),
1234 'narration':fields.text('Internal Note'),
1235 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1240 'period_id': _get_period,
1241 'date': lambda *a: time.strftime('%Y-%m-%d'),
1242 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1245 def _check_centralisation(self, cursor, user, ids, context=None):
1246 for move in self.browse(cursor, user, ids, context=context):
1247 if move.journal_id.centralisation:
1248 move_ids = self.search(cursor, user, [
1249 ('period_id', '=', move.period_id.id),
1250 ('journal_id', '=', move.journal_id.id),
1252 if len(move_ids) > 1:
1257 (_check_centralisation,
1258 'You can not create more than one move per period on centralized journal',
1262 def post(self, cr, uid, ids, context=None):
1265 invoice = context.get('invoice', False)
1266 valid_moves = self.validate(cr, uid, ids, context)
1269 raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non-balanced entry !\nMake sure you have configured payment terms properly !\nThe latest payment term line should be of the type "Balance" !'))
1270 obj_sequence = self.pool.get('ir.sequence')
1271 for move in self.browse(cr, uid, valid_moves, context=context):
1274 journal = move.journal_id
1276 if invoice and invoice.internal_number:
1277 new_name = invoice.internal_number
1279 if journal.sequence_id:
1280 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1281 new_name = obj_sequence.next_by_id(cr, uid, journal.sequence_id.id, c)
1283 raise osv.except_osv(_('Error'), _('No sequence defined on the journal !'))
1286 self.write(cr, uid, [move.id], {'name':new_name})
1288 cr.execute('UPDATE account_move '\
1291 ('posted', tuple(valid_moves),))
1294 def button_validate(self, cursor, user, ids, context=None):
1295 for move in self.browse(cursor, user, ids, context=context):
1297 for line in move.line_id:
1298 account = line.account_id
1301 account = account.parent_id
1304 elif top<>account2.id:
1305 raise osv.except_osv(_('Error !'), _('You can not validate a journal entry unless all journal items belongs to the same chart of accounts !'))
1306 return self.post(cursor, user, ids, context=context)
1308 def button_cancel(self, cr, uid, ids, context=None):
1309 for line in self.browse(cr, uid, ids, context=context):
1310 if not line.journal_id.update_posted:
1311 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.'))
1313 cr.execute('UPDATE account_move '\
1315 'WHERE id IN %s', ('draft', tuple(ids),))
1318 def write(self, cr, uid, ids, vals, context=None):
1322 c['novalidate'] = True
1323 result = super(account_move, self).write(cr, uid, ids, vals, c)
1324 self.validate(cr, uid, ids, context=context)
1328 # TODO: Check if period is closed !
1330 def create(self, cr, uid, vals, context=None):
1333 if 'line_id' in vals and context.get('copy'):
1334 for l in vals['line_id']:
1337 'reconcile_id':False,
1338 'reconcil_partial_id':False,
1339 'analytic_lines':False,
1343 'account_tax_id':False,
1346 if 'journal_id' in vals and vals.get('journal_id', False):
1347 for l in vals['line_id']:
1349 l[2]['journal_id'] = vals['journal_id']
1350 context['journal_id'] = vals['journal_id']
1351 if 'period_id' in vals:
1352 for l in vals['line_id']:
1354 l[2]['period_id'] = vals['period_id']
1355 context['period_id'] = vals['period_id']
1357 default_period = self._get_period(cr, uid, context)
1358 for l in vals['line_id']:
1360 l[2]['period_id'] = default_period
1361 context['period_id'] = default_period
1363 if 'line_id' in vals:
1365 c['novalidate'] = True
1366 result = super(account_move, self).create(cr, uid, vals, c)
1367 self.validate(cr, uid, [result], context)
1369 result = super(account_move, self).create(cr, uid, vals, context)
1372 def copy(self, cr, uid, id, default={}, context=None):
1382 return super(account_move, self).copy(cr, uid, id, default, context)
1384 def unlink(self, cr, uid, ids, context=None, check=True):
1388 obj_move_line = self.pool.get('account.move.line')
1389 for move in self.browse(cr, uid, ids, context=context):
1390 if move['state'] != 'draft':
1391 raise osv.except_osv(_('UserError'),
1392 _('You can not delete a posted journal entry "%s"!') % \
1394 line_ids = map(lambda x: x.id, move.line_id)
1395 context['journal_id'] = move.journal_id.id
1396 context['period_id'] = move.period_id.id
1397 obj_move_line._update_check(cr, uid, line_ids, context)
1398 obj_move_line.unlink(cr, uid, line_ids, context=context)
1399 toremove.append(move.id)
1400 result = super(account_move, self).unlink(cr, uid, toremove, context)
1403 def _compute_balance(self, cr, uid, id, context=None):
1404 move = self.browse(cr, uid, id, context=context)
1406 for line in move.line_id:
1407 amount+= (line.debit - line.credit)
1410 def _centralise(self, cr, uid, move, mode, context=None):
1411 assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1412 currency_obj = self.pool.get('res.currency')
1417 account_id = move.journal_id.default_debit_account_id.id
1420 raise osv.except_osv(_('UserError'),
1421 _('There is no default default debit account defined \n' \
1422 'on journal "%s"') % move.journal_id.name)
1424 account_id = move.journal_id.default_credit_account_id.id
1427 raise osv.except_osv(_('UserError'),
1428 _('There is no default default credit account defined \n' \
1429 'on journal "%s"') % move.journal_id.name)
1431 # find the first line of this move with the current mode
1432 # or create it if it doesn't exist
1433 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1438 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1439 line_id = self.pool.get('account.move.line').create(cr, uid, {
1440 'name': _(mode.capitalize()+' Centralisation'),
1441 'centralisation': mode,
1442 'account_id': account_id,
1444 'journal_id': move.journal_id.id,
1445 'period_id': move.period_id.id,
1446 'date': move.period_id.date_stop,
1451 # find the first line of this move with the other mode
1452 # so that we can exclude it from our calculation
1453 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1460 cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1461 result = cr.fetchone()[0] or 0.0
1462 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1464 #adjust also the amount in currency if needed
1465 cr.execute("select currency_id, sum(amount_currency) as amount_currency from account_move_line where move_id = %s and currency_id is not null group by currency_id", (move.id,))
1466 for row in cr.dictfetchall():
1467 currency_id = currency_obj.browse(cr, uid, row['currency_id'], context=context)
1468 if not currency_obj.is_zero(cr, uid, currency_id, row['amount_currency']):
1469 amount_currency = row['amount_currency'] * -1
1470 account_id = amount_currency > 0 and move.journal_id.default_debit_account_id.id or move.journal_id.default_credit_account_id.id
1471 cr.execute('select id from account_move_line where move_id=%s and centralisation=\'currency\' and currency_id = %slimit 1', (move.id, row['currency_id']))
1474 cr.execute('update account_move_line set amount_currency=%s , account_id=%s where id=%s', (amount_currency, account_id, res[0]))
1476 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1477 line_id = self.pool.get('account.move.line').create(cr, uid, {
1478 'name': _('Currency Adjustment'),
1479 'centralisation': 'currency',
1480 'account_id': account_id,
1482 'journal_id': move.journal_id.id,
1483 'period_id': move.period_id.id,
1484 'date': move.period_id.date_stop,
1487 'currency_id': row['currency_id'],
1488 'amount_currency': amount_currency,
1494 # Validate a balanced move. If it is a centralised journal, create a move.
1496 def validate(self, cr, uid, ids, context=None):
1497 if context and ('__last_update' in context):
1498 del context['__last_update']
1500 valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1501 obj_analytic_line = self.pool.get('account.analytic.line')
1502 obj_move_line = self.pool.get('account.move.line')
1503 for move in self.browse(cr, uid, ids, context):
1504 # Unlink old analytic lines on move_lines
1505 for obj_line in move.line_id:
1506 for obj in obj_line.analytic_lines:
1507 obj_analytic_line.unlink(cr,uid,obj.id)
1509 journal = move.journal_id
1514 for line in move.line_id:
1515 amount += line.debit - line.credit
1516 line_ids.append(line.id)
1517 if line.state=='draft':
1518 line_draft_ids.append(line.id)
1521 company_id = line.account_id.company_id.id
1522 if not company_id == line.account_id.company_id.id:
1523 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1525 if line.account_id.currency_id and line.currency_id:
1526 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):
1527 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))
1529 if abs(amount) < 10 ** -4:
1530 # If the move is balanced
1531 # Add to the list of valid moves
1532 # (analytic lines will be created later for valid moves)
1533 valid_moves.append(move)
1535 # Check whether the move lines are confirmed
1537 if not line_draft_ids:
1539 # Update the move lines (set them as valid)
1541 obj_move_line.write(cr, uid, line_draft_ids, {
1543 }, context, check=False)
1548 if journal.type in ('purchase','sale'):
1549 for line in move.line_id:
1551 key = (line.account_id.id, line.tax_code_id.id)
1553 code = account2[key][0]
1554 amount = account2[key][1] * (line.debit + line.credit)
1555 elif line.account_id.id in account:
1556 code = account[line.account_id.id][0]
1557 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1558 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1559 obj_move_line.write(cr, uid, [line.id], {
1560 'tax_code_id': code,
1561 'tax_amount': amount
1562 }, context, check=False)
1563 elif journal.centralisation:
1564 # If the move is not balanced, it must be centralised...
1566 # Add to the list of valid moves
1567 # (analytic lines will be created later for valid moves)
1568 valid_moves.append(move)
1571 # Update the move lines (set them as valid)
1573 self._centralise(cr, uid, move, 'debit', context=context)
1574 self._centralise(cr, uid, move, 'credit', context=context)
1575 obj_move_line.write(cr, uid, line_draft_ids, {
1577 }, context, check=False)
1579 # We can't validate it (it's unbalanced)
1580 # Setting the lines as draft
1581 obj_move_line.write(cr, uid, line_ids, {
1583 }, context, check=False)
1584 # Create analytic lines for the valid moves
1585 for record in valid_moves:
1586 obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1588 valid_moves = [move.id for move in valid_moves]
1589 return len(valid_moves) > 0 and valid_moves or False
1593 class account_move_reconcile(osv.osv):
1594 _name = "account.move.reconcile"
1595 _description = "Account Reconciliation"
1597 'name': fields.char('Name', size=64, required=True),
1598 'type': fields.char('Type', size=16, required=True),
1599 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1600 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1601 'create_date': fields.date('Creation date', readonly=True),
1604 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1607 def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None):
1609 for rec in self.browse(cr, uid, ids, context=context):
1610 for line in rec.line_partial_ids:
1611 if line.account_id.currency_id:
1612 total += line.amount_currency
1614 total += (line.debit or 0.0) - (line.credit or 0.0)
1616 self.pool.get('account.move.line').write(cr, uid,
1617 map(lambda x: x.id, rec.line_partial_ids),
1618 {'reconcile_id': rec.id }
1622 def name_get(self, cr, uid, ids, context=None):
1626 for r in self.browse(cr, uid, ids, context=context):
1627 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1629 name = '%s (%.2f)' % (r.name, total)
1630 result.append((r.id,name))
1632 result.append((r.id,r.name))
1635 account_move_reconcile()
1637 #----------------------------------------------------------
1639 #----------------------------------------------------------
1642 child_depend: la taxe depend des taxes filles
1644 class account_tax_code(osv.osv):
1646 A code for the tax object.
1648 This code is used for some tax declarations.
1650 def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1651 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1652 if context.get('based_on', 'invoices') == 'payments':
1653 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1654 FROM account_move_line AS line, \
1655 account_move AS move \
1656 LEFT JOIN account_invoice invoice ON \
1657 (invoice.move_id = move.id) \
1658 WHERE line.tax_code_id IN %s '+where+' \
1659 AND move.id = line.move_id \
1660 AND ((invoice.state = \'paid\') \
1661 OR (invoice.id IS NULL)) \
1662 GROUP BY line.tax_code_id',
1663 (parent_ids,) + where_params)
1665 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1666 FROM account_move_line AS line, \
1667 account_move AS move \
1668 WHERE line.tax_code_id IN %s '+where+' \
1669 AND move.id = line.move_id \
1670 GROUP BY line.tax_code_id',
1671 (parent_ids,) + where_params)
1672 res=dict(cr.fetchall())
1673 obj_precision = self.pool.get('decimal.precision')
1675 for record in self.browse(cr, uid, ids, context=context):
1676 def _rec_get(record):
1677 amount = res.get(record.id, 0.0)
1678 for rec in record.child_ids:
1679 amount += _rec_get(rec) * rec.sign
1681 res2[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1684 def _sum_year(self, cr, uid, ids, name, args, context=None):
1687 move_state = ('posted', )
1688 if context.get('state', 'all') == 'all':
1689 move_state = ('draft', 'posted', )
1690 if context.get('fiscalyear_id', False):
1691 fiscalyear_id = [context['fiscalyear_id']]
1693 fiscalyear_id = self.pool.get('account.fiscalyear').finds(cr, uid, exception=False)
1698 for fy in fiscalyear_id:
1699 pids += map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fy).period_ids)
1701 where = ' AND line.period_id IN %s AND move.state IN %s '
1702 where_params = (tuple(pids), move_state)
1703 return self._sum(cr, uid, ids, name, args, context,
1704 where=where, where_params=where_params)
1706 def _sum_period(self, cr, uid, ids, name, args, context):
1709 move_state = ('posted', )
1710 if context.get('state', False) == 'all':
1711 move_state = ('draft', 'posted', )
1712 if context.get('period_id', False):
1713 period_id = context['period_id']
1715 period_id = self.pool.get('account.period').find(cr, uid)
1717 return dict.fromkeys(ids, 0.0)
1718 period_id = period_id[0]
1719 return self._sum(cr, uid, ids, name, args, context,
1720 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1722 _name = 'account.tax.code'
1723 _description = 'Tax Code'
1726 'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1727 'code': fields.char('Case Code', size=64),
1728 'info': fields.text('Description'),
1729 'sum': fields.function(_sum_year, string="Year Sum"),
1730 'sum_period': fields.function(_sum_period, string="Period Sum"),
1731 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1732 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1733 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1734 'company_id': fields.many2one('res.company', 'Company', required=True),
1735 'sign': fields.float('Coefficent for parent', required=True, help='You can specify here the coefficient that will be used when consolidating the amount of this case into its parent. For example, set 1/-1 if you want to add/substract it.'),
1736 '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"),
1739 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1744 ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1745 return self.name_get(cr, user, ids, context)
1747 def name_get(self, cr, uid, ids, context=None):
1748 if isinstance(ids, (int, long)):
1752 if isinstance(ids, (int, long)):
1754 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1755 return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1758 def _default_company(self, cr, uid, context=None):
1759 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1761 return user.company_id.id
1762 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1764 'company_id': _default_company,
1766 'notprintable': False,
1769 def copy(self, cr, uid, id, default=None, context=None):
1772 default = default.copy()
1773 default.update({'line_ids': []})
1774 return super(account_tax_code, self).copy(cr, uid, id, default, context)
1776 _check_recursion = check_cycle
1778 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1784 class account_tax(osv.osv):
1788 Type: percent, fixed, none, code
1789 PERCENT: tax = price * amount
1790 FIXED: tax = price + amount
1792 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1793 return result in the context
1794 Ex: result=round(price_unit*0.21,4)
1797 def get_precision_tax():
1798 def change_digit_tax(cr):
1799 res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account')
1801 return change_digit_tax
1803 _name = 'account.tax'
1804 _description = 'Tax'
1806 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1807 '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."),
1808 'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For taxes of type percentage, enter % ratio between 0-1."),
1809 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the tax without removing it."),
1810 'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1811 help="The computation method for the tax amount."),
1812 'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1813 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1814 '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."),
1815 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1816 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1817 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1818 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1819 '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."),
1820 'python_compute':fields.text('Python Code'),
1821 'python_compute_inv':fields.text('Python Code (reverse)'),
1822 'python_applicable':fields.text('Python Code'),
1825 # Fields used for the VAT declaration
1827 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1828 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1829 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1830 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1832 # Same fields for refund invoices
1834 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1835 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1836 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1837 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1838 '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"),
1839 'company_id': fields.many2one('res.company', 'Company', required=True),
1840 'description': fields.char('Tax Code',size=32),
1841 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1842 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1845 _sql_constraints = [
1846 ('name_company_uniq', 'unique(name, company_id)', 'Tax Name must be unique per company!'),
1849 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1851 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1852 result format: {[(id, name), (id, name), ...]}
1854 @param cr: A database cursor
1855 @param user: ID of the user currently logged in
1856 @param name: name to search
1857 @param args: other arguments
1858 @param operator: default operator is 'ilike', it can be changed
1859 @param context: context arguments, like lang, time zone
1860 @param limit: Returns first 'n' ids of complete result, default is 80.
1862 @return: Returns a list of tupples containing id and name
1870 ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context)
1872 ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context)
1874 ids = self.search(cr, user, args, limit=limit, context=context or {})
1875 return self.name_get(cr, user, ids, context=context)
1877 def write(self, cr, uid, ids, vals, context=None):
1878 if vals.get('type', False) and vals['type'] in ('none', 'code'):
1879 vals.update({'amount': 0.0})
1880 return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1882 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1883 journal_pool = self.pool.get('account.journal')
1885 if context and context.has_key('type'):
1886 if context.get('type') in ('out_invoice','out_refund'):
1887 args += [('type_tax_use','in',['sale','all'])]
1888 elif context.get('type') in ('in_invoice','in_refund'):
1889 args += [('type_tax_use','in',['purchase','all'])]
1891 if context and context.has_key('journal_id'):
1892 journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1893 if journal.type in ('sale', 'purchase'):
1894 args += [('type_tax_use','in',[journal.type,'all'])]
1896 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1898 def name_get(self, cr, uid, ids, context=None):
1902 for record in self.read(cr, uid, ids, ['description','name'], context=context):
1903 name = record['description'] and record['description'] or record['name']
1904 res.append((record['id'],name ))
1907 def _default_company(self, cr, uid, context=None):
1908 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1910 return user.company_id.id
1911 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1914 'python_compute': '''# 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''',
1915 'python_compute_inv': '''# price_unit\n# address: res.partner.address object or False\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
1916 'applicable_type': 'true',
1921 'type_tax_use': 'all',
1927 'include_base_amount': False,
1928 'company_id': _default_company,
1932 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1934 obj_partener_address = self.pool.get('res.partner.address')
1936 if tax.applicable_type=='code':
1937 localdict = {'price_unit':price_unit, 'address':obj_partener_address.browse(cr, uid, address_id), 'product':product, 'partner':partner}
1938 exec tax.python_applicable in localdict
1939 if localdict.get('result', False):
1945 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1946 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1948 cur_price_unit=price_unit
1949 obj_partener_address = self.pool.get('res.partner.address')
1951 # we compute the amount for the current tax object and append it to the result
1952 data = {'id':tax.id,
1953 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1954 'account_collected_id':tax.account_collected_id.id,
1955 'account_paid_id':tax.account_paid_id.id,
1956 'base_code_id': tax.base_code_id.id,
1957 'ref_base_code_id': tax.ref_base_code_id.id,
1958 'sequence': tax.sequence,
1959 'base_sign': tax.base_sign,
1960 'tax_sign': tax.tax_sign,
1961 'ref_base_sign': tax.ref_base_sign,
1962 'ref_tax_sign': tax.ref_tax_sign,
1963 'price_unit': cur_price_unit,
1964 'tax_code_id': tax.tax_code_id.id,
1965 'ref_tax_code_id': tax.ref_tax_code_id.id,
1968 if tax.type=='percent':
1969 amount = cur_price_unit * tax.amount
1970 data['amount'] = amount
1972 elif tax.type=='fixed':
1973 data['amount'] = tax.amount
1974 data['tax_amount']=quantity
1975 # data['amount'] = quantity
1976 elif tax.type=='code':
1977 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1978 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1979 exec tax.python_compute in localdict
1980 amount = localdict['result']
1981 data['amount'] = amount
1982 elif tax.type=='balance':
1983 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1984 data['balance'] = cur_price_unit
1986 amount2 = data.get('amount', 0.0)
1988 if tax.child_depend:
1991 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1992 res.extend(child_tax)
1993 if tax.child_depend:
1995 for name in ('base','ref_base'):
1996 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1997 r[name+'_code_id'] = latest[name+'_code_id']
1998 r[name+'_sign'] = latest[name+'_sign']
1999 r['price_unit'] = latest['price_unit']
2000 latest[name+'_code_id'] = False
2001 for name in ('tax','ref_tax'):
2002 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2003 r[name+'_code_id'] = latest[name+'_code_id']
2004 r[name+'_sign'] = latest[name+'_sign']
2005 r['amount'] = data['amount']
2006 latest[name+'_code_id'] = False
2007 if tax.include_base_amount:
2008 cur_price_unit+=amount2
2011 def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None, force_excluded=False):
2013 :param force_excluded: boolean used to say that we don't want to consider the value of field price_include of
2014 tax. It's used in encoding by line where you don't matter if you encoded a tax with that boolean to True or
2017 'total': 0.0, # Total without taxes
2018 'total_included: 0.0, # Total with taxes
2019 'taxes': [] # List of taxes, see compute for the format
2022 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2023 totalin = totalex = round(price_unit * quantity, precision)
2027 if not tax.price_include or force_excluded:
2031 tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
2033 totalex -= r.get('amount', 0.0)
2036 totlex_qty = totalex/quantity
2039 tex = self._compute(cr, uid, tex, totlex_qty, quantity, address_id=address_id, product=product, partner=partner)
2041 totalin += r.get('amount', 0.0)
2044 'total_included': totalin,
2048 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
2049 logger = netsvc.Logger()
2050 logger.notifyChannel("warning", netsvc.LOG_WARNING,
2051 "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
2052 return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
2054 def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
2056 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2060 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2061 one tax for each tax id in IDS and their children
2063 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
2065 precision_pool = self.pool.get('decimal.precision')
2067 if r.get('balance',False):
2068 r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
2070 r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
2071 total += r['amount']
2074 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
2075 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
2076 obj_partener_address = self.pool.get('res.partner.address')
2079 cur_price_unit = price_unit
2081 tax_parent_tot = 0.0
2083 if (tax.type=='percent') and not tax.include_base_amount:
2084 tax_parent_tot += tax.amount
2087 if (tax.type=='fixed') and not tax.include_base_amount:
2088 cur_price_unit -= tax.amount
2091 if tax.type=='percent':
2092 if tax.include_base_amount:
2093 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
2095 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
2097 elif tax.type=='fixed':
2100 elif tax.type=='code':
2101 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
2102 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
2103 exec tax.python_compute_inv in localdict
2104 amount = localdict['result']
2105 elif tax.type=='balance':
2106 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2108 if tax.include_base_amount:
2109 cur_price_unit -= amount
2118 'account_collected_id': tax.account_collected_id.id,
2119 'account_paid_id': tax.account_paid_id.id,
2120 'base_code_id': tax.base_code_id.id,
2121 'ref_base_code_id': tax.ref_base_code_id.id,
2122 'sequence': tax.sequence,
2123 'base_sign': tax.base_sign,
2124 'tax_sign': tax.tax_sign,
2125 'ref_base_sign': tax.ref_base_sign,
2126 'ref_tax_sign': tax.ref_tax_sign,
2127 'price_unit': cur_price_unit,
2128 'tax_code_id': tax.tax_code_id.id,
2129 'ref_tax_code_id': tax.ref_tax_code_id.id,
2132 if tax.child_depend:
2136 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
2137 res.extend(parent_tax)
2142 total += r['amount']
2144 r['price_unit'] -= total
2148 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
2150 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2151 Price Unit is a VAT included price
2155 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2156 one tax for each tax id in IDS and their children
2158 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
2160 obj_precision = self.pool.get('decimal.precision')
2162 prec = obj_precision.precision_get(cr, uid, 'Account')
2163 if r.get('balance',False):
2164 r['amount'] = round(r['balance'] * quantity, prec) - total
2166 r['amount'] = round(r['amount'] * quantity, prec)
2167 total += r['amount']
2172 # ---------------------------------------------------------
2173 # Account Entries Models
2174 # ---------------------------------------------------------
2176 class account_model(osv.osv):
2177 _name = "account.model"
2178 _description = "Account Model"
2180 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2181 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2182 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2183 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2184 'legend': fields.text('Legend', readonly=True, size=100),
2188 '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'),
2190 def generate(self, cr, uid, ids, datas={}, context=None):
2193 account_move_obj = self.pool.get('account.move')
2194 account_move_line_obj = self.pool.get('account.move.line')
2195 pt_obj = self.pool.get('account.payment.term')
2196 period_obj = self.pool.get('account.period')
2201 if datas.get('date', False):
2202 context.update({'date': datas['date']})
2204 move_date = context.get('date', time.strftime('%Y-%m-%d'))
2205 move_date = datetime.strptime(move_date,"%Y-%m-%d")
2206 for model in self.browse(cr, uid, ids, context=context):
2207 ctx = context.copy()
2208 ctx.update({'company_id': model.company_id.id})
2209 period_ids = period_obj.find(cr, uid, dt=context.get('date', False), context=ctx)
2210 period_id = period_ids and period_ids[0] or False
2211 ctx.update({'journal_id': model.journal_id.id,'period_id': period_id})
2213 entry['name'] = model.name%{'year': move_date.strftime('%Y'), 'month': move_date.strftime('%m'), 'date': move_date.strftime('%Y-%m')}
2215 raise osv.except_osv(_('Wrong model !'), _('You have a wrong expression "%(...)s" in your model !'))
2216 move_id = account_move_obj.create(cr, uid, {
2217 'ref': entry['name'],
2218 'period_id': period_id,
2219 'journal_id': model.journal_id.id,
2220 'date': context.get('date',time.strftime('%Y-%m-%d'))
2222 move_ids.append(move_id)
2223 for line in model.lines_id:
2224 analytic_account_id = False
2225 if line.analytic_account_id:
2226 if not model.journal_id.analytic_journal_id:
2227 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2228 analytic_account_id = line.analytic_account_id.id
2231 'journal_id': model.journal_id.id,
2232 'period_id': period_id,
2233 'analytic_account_id': analytic_account_id
2236 date_maturity = context.get('date',time.strftime('%Y-%m-%d'))
2237 if line.date_maturity == 'partner':
2238 if not line.partner_id:
2239 raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2240 "\nPlease define partner on it!")%(line.name, model.name))
2241 if line.partner_id.property_payment_term:
2242 payment_term_id = line.partner_id.property_payment_term.id
2243 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2245 pterm_list = [l[0] for l in pterm_list]
2247 date_maturity = pterm_list[-1]
2251 'quantity': line.quantity,
2252 'debit': line.debit,
2253 'credit': line.credit,
2254 'account_id': line.account_id.id,
2256 'partner_id': line.partner_id.id,
2257 'date': context.get('date',time.strftime('%Y-%m-%d')),
2258 'date_maturity': date_maturity
2260 account_move_line_obj.create(cr, uid, val, context=ctx)
2266 class account_model_line(osv.osv):
2267 _name = "account.model.line"
2268 _description = "Account Model Entries"
2270 'name': fields.char('Name', size=64, required=True),
2271 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones."),
2272 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries."),
2273 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2274 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2275 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2276 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2277 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2278 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2279 'currency_id': fields.many2one('res.currency', 'Currency'),
2280 'partner_id': fields.many2one('res.partner', 'Partner'),
2281 '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."),
2284 _sql_constraints = [
2285 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model, they must be positive!'),
2286 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model, they must be positive!'),
2288 account_model_line()
2290 # ---------------------------------------------------------
2291 # Account Subscription
2292 # ---------------------------------------------------------
2295 class account_subscription(osv.osv):
2296 _name = "account.subscription"
2297 _description = "Account Subscription"
2299 'name': fields.char('Name', size=64, required=True),
2300 'ref': fields.char('Reference', size=16),
2301 'model_id': fields.many2one('account.model', 'Model', required=True),
2302 'date_start': fields.date('Start Date', required=True),
2303 'period_total': fields.integer('Number of Periods', required=True),
2304 'period_nbr': fields.integer('Period', required=True),
2305 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2306 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
2307 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2310 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
2311 'period_type': 'month',
2316 def state_draft(self, cr, uid, ids, context=None):
2317 self.write(cr, uid, ids, {'state':'draft'})
2320 def check(self, cr, uid, ids, context=None):
2322 for sub in self.browse(cr, uid, ids, context=context):
2324 for line in sub.lines_id:
2325 if not line.move_id.id:
2329 todone.append(sub.id)
2331 self.write(cr, uid, todone, {'state':'done'})
2334 def remove_line(self, cr, uid, ids, context=None):
2336 for sub in self.browse(cr, uid, ids, context=context):
2337 for line in sub.lines_id:
2338 if not line.move_id.id:
2339 toremove.append(line.id)
2341 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2342 self.write(cr, uid, ids, {'state':'draft'})
2345 def compute(self, cr, uid, ids, context=None):
2346 for sub in self.browse(cr, uid, ids, context=context):
2348 for i in range(sub.period_total):
2349 self.pool.get('account.subscription.line').create(cr, uid, {
2351 'subscription_id': sub.id,
2353 if sub.period_type=='day':
2354 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2355 if sub.period_type=='month':
2356 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2357 if sub.period_type=='year':
2358 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2359 self.write(cr, uid, ids, {'state':'running'})
2362 account_subscription()
2364 class account_subscription_line(osv.osv):
2365 _name = "account.subscription.line"
2366 _description = "Account Subscription Line"
2368 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2369 'date': fields.date('Date', required=True),
2370 'move_id': fields.many2one('account.move', 'Entry'),
2373 def move_create(self, cr, uid, ids, context=None):
2376 obj_model = self.pool.get('account.model')
2377 for line in self.browse(cr, uid, ids, context=context):
2381 move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2382 tocheck[line.subscription_id.id] = True
2383 self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2384 all_moves.extend(move_ids)
2386 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2391 account_subscription_line()
2393 # ---------------------------------------------------------------
2394 # Account Templates: Account, Tax, Tax Code and chart. + Wizard
2395 # ---------------------------------------------------------------
2397 class account_tax_template(osv.osv):
2398 _name = 'account.tax.template'
2399 account_tax_template()
2401 class account_account_template(osv.osv):
2403 _name = "account.account.template"
2404 _description ='Templates for Accounts'
2407 'name': fields.char('Name', size=256, required=True, select=True),
2408 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2409 'code': fields.char('Code', size=64, select=1),
2410 'type': fields.selection([
2411 ('receivable','Receivable'),
2412 ('payable','Payable'),
2414 ('consolidation','Consolidation'),
2415 ('liquidity','Liquidity'),
2416 ('other','Regular'),
2417 ('closed','Closed'),
2418 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2419 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2420 "can have children accounts for multi-company consolidations, payable/receivable are for "\
2421 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2422 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2423 help="These types are defined according to your country. The type contains more information "\
2424 "about the account and its specificities."),
2425 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2426 'shortcut': fields.char('Shortcut', size=12),
2427 'note': fields.text('Note'),
2428 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2429 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2430 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2431 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2432 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template'),
2441 def _check_type(self, cr, uid, ids, context=None):
2444 accounts = self.browse(cr, uid, ids, context=context)
2445 for account in accounts:
2446 if account.parent_id and account.parent_id.type != 'view':
2450 _check_recursion = check_cycle
2452 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id']),
2453 (_check_type, 'Configuration Error!\nYou can not define children to an account with internal type different of "View"! ', ['type']),
2457 def name_get(self, cr, uid, ids, context=None):
2460 reads = self.read(cr, uid, ids, ['name','code'], context=context)
2462 for record in reads:
2463 name = record['name']
2465 name = record['code']+' '+name
2466 res.append((record['id'],name ))
2469 def generate_account(self, cr, uid, template_id, tax_template_ref, acc_template_ref, code_digits, company_id, context=None):
2471 This method for generating accounts from templates.
2472 @param cr: A database cursor.
2473 @param uid: ID of the user currently logged in.
2474 @param account_root_id: Root account id getting from current template.
2475 @param tax_template_ref: Taxes templates reference for write taxes_id in account_account.
2476 @param code_digits: Digit getting from wizard.multi.charts.accounts.,this is use for account code.
2477 @param company_id: company_id selected from wizard.multi.charts.accounts.
2478 @return : return acc_template_ref for reference purpose.
2482 obj_acc = self.pool.get('account.account')
2483 company_name = self.pool.get('res.company').browse(cr, uid, company_id, context=context).name
2484 template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
2485 #deactivate the parent_store functionnality on account_account for rapidity purpose
2486 ctx = context.copy()
2487 ctx.update({'defer_parent_store_computation': True})
2488 children_acc_template = self.search(cr, uid, ['|', ('chart_template_id','=', [template_id]),'&',('parent_id','child_of', [template.account_root_id.id]),('chart_template_id','=', False), ('nocreate','!=',True)], order='id')
2489 for account_template in self.browse(cr, uid, children_acc_template, context=context):
2490 # skip the root of COA if it's not the main one
2491 if (template.account_root_id.id == account_template.id) and template.parent_id:
2494 for tax in account_template.tax_ids:
2495 tax_ids.append(tax_template_ref[tax.id])
2497 code_main = account_template.code and len(account_template.code) or 0
2498 code_acc = account_template.code or ''
2499 if code_main > 0 and code_main <= code_digits and account_template.type != 'view':
2500 code_acc = str(code_acc) + (str('0'*(code_digits-code_main)))
2502 'name': (template.account_root_id.id == account_template.id) and company_name or account_template.name,
2503 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2505 'type': account_template.type,
2506 'user_type': account_template.user_type and account_template.user_type.id or False,
2507 'reconcile': account_template.reconcile,
2508 'shortcut': account_template.shortcut,
2509 'note': account_template.note,
2510 '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,
2511 'tax_ids': [(6,0,tax_ids)],
2512 'company_id': company_id,
2514 new_account = obj_acc.create(cr, uid, vals, context=ctx)
2515 acc_template_ref[account_template.id] = new_account
2517 #reactivate the parent_store functionnality on account_account
2518 obj_acc._parent_store_compute(cr)
2519 return acc_template_ref
2521 account_account_template()
2523 class account_add_tmpl_wizard(osv.osv_memory):
2524 """Add one more account from the template.
2526 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2527 _name = 'account.addtmpl.wizard'
2529 def _get_def_cparent(self, cr, uid, context=None):
2530 acc_obj = self.pool.get('account.account')
2531 tmpl_obj = self.pool.get('account.account.template')
2532 tids = tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2533 if not tids or not tids[0]['parent_id']:
2535 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2537 if not ptids or not ptids[0]['code']:
2538 raise osv.except_osv(_('Error !'), _('I can not locate a parent code for the template account!'))
2539 res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2540 return res and res[0] or False
2543 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2546 'cparent_id': _get_def_cparent,
2549 def action_create(self,cr,uid,ids,context=None):
2552 acc_obj = self.pool.get('account.account')
2553 tmpl_obj = self.pool.get('account.account.template')
2554 data = self.read(cr, uid, ids)
2555 company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2556 account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2558 'name': account_template.name,
2559 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2560 'code': account_template.code,
2561 'type': account_template.type,
2562 'user_type': account_template.user_type and account_template.user_type.id or False,
2563 'reconcile': account_template.reconcile,
2564 'shortcut': account_template.shortcut,
2565 'note': account_template.note,
2566 'parent_id': data[0]['cparent_id'],
2567 'company_id': company_id,
2569 acc_obj.create(cr, uid, vals)
2570 return {'type':'state', 'state': 'end' }
2572 def action_cancel(self, cr, uid, ids, context=None):
2573 return { 'type': 'state', 'state': 'end' }
2575 account_add_tmpl_wizard()
2577 class account_tax_code_template(osv.osv):
2579 _name = 'account.tax.code.template'
2580 _description = 'Tax Code Template'
2584 'name': fields.char('Tax Case Name', size=64, required=True),
2585 'code': fields.char('Case Code', size=64),
2586 'info': fields.text('Description'),
2587 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2588 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2589 'sign': fields.float('Sign For Parent', required=True),
2590 '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"),
2595 'notprintable': False,
2598 def generate_tax_code(self, cr, uid, tax_code_root_id, company_id, context=None):
2600 This function generates the tax codes from the templates of tax code that are children of the given one passed
2601 in argument. Then it returns a dictionary with the mappping between the templates and the real objects.
2603 :param tax_code_root_id: id of the root of all the tax code templates to process
2604 :param company_id: id of the company the wizard is running for
2605 :returns: dictionary with the mappping between the templates and the real objects.
2608 obj_tax_code_template = self.pool.get('account.tax.code.template')
2609 obj_tax_code = self.pool.get('account.tax.code')
2610 tax_code_template_ref = {}
2611 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
2613 #find all the children of the tax_code_root_id
2614 children_tax_code_template = obj_tax_code_template.search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2615 for tax_code_template in obj_tax_code_template.browse(cr, uid, children_tax_code_template, context=context):
2617 'name': (tax_code_root_id == tax_code_template.id) and company.name or tax_code_template.name,
2618 'code': tax_code_template.code,
2619 'info': tax_code_template.info,
2620 '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,
2621 'company_id': company_id,
2622 'sign': tax_code_template.sign,
2624 #check if this tax code already exists
2625 rec_list = obj_tax_code.search(cr, uid, [('name', '=', vals['name']),('code', '=', vals['code']),('company_id', '=', vals['company_id'])], context=context)
2627 #if not yet, create it
2628 new_tax_code = obj_tax_code.create(cr, uid, vals)
2629 #recording the new tax code to do the mapping
2630 tax_code_template_ref[tax_code_template.id] = new_tax_code
2631 return tax_code_template_ref
2633 def name_get(self, cr, uid, ids, context=None):
2636 if isinstance(ids, (int, long)):
2638 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2639 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2642 _check_recursion = check_cycle
2644 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2646 _order = 'code,name'
2647 account_tax_code_template()
2650 class account_chart_template(osv.osv):
2651 _name="account.chart.template"
2652 _description= "Templates for Account Chart"
2655 'name': fields.char('Name', size=64, required=True),
2656 'parent_id': fields.many2one('account.chart.template', 'Parent Chart Template'),
2657 'code_digits': fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2658 'visible': fields.boolean('Can be Visible?', help="Set this to False if you don't want this template to be used actively in the wizard that generate Chart of Accounts from templates, this is useful when you want to generate accounts of this template only when loading its child template."),
2659 'complete_tax_set': fields.boolean('Complete Set of Taxes', help='This boolean helps you to choose if you want to propose to the user to encode the sale and purchase rates or choose from list of taxes. This last choice assumes that the set of tax defined on this template is complete'),
2660 'account_root_id': fields.many2one('account.account.template', 'Root Account', domain=[('parent_id','=',False)]),
2661 'tax_code_root_id': fields.many2one('account.tax.code.template', 'Root Tax Code', domain=[('parent_id','=',False)]),
2662 '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'),
2663 'bank_account_view_id': fields.many2one('account.account.template', 'Bank Account'),
2664 'property_account_receivable': fields.many2one('account.account.template', 'Receivable Account'),
2665 'property_account_payable': fields.many2one('account.account.template', 'Payable Account'),
2666 'property_account_expense_categ': fields.many2one('account.account.template', 'Expense Category Account'),
2667 'property_account_income_categ': fields.many2one('account.account.template', 'Income Category Account'),
2668 'property_account_expense': fields.many2one('account.account.template', 'Expense Account on Product Template'),
2669 'property_account_income': fields.many2one('account.account.template', 'Income Account on Product Template'),
2670 'property_reserve_and_surplus_account': fields.many2one('account.account.template', 'Reserve and Profit/Loss Account', domain=[('type', '=', 'payable')], help='This Account is used for transferring Profit/Loss(If It is Profit: Amount will be added, Loss: Amount will be deducted.), Which is calculated from Profilt & Loss Report'),
2671 'property_account_income_opening': fields.many2one('account.account.template', 'Opening Entries Income Account'),
2672 'property_account_expense_opening': fields.many2one('account.account.template', 'Opening Entries Expense Account'),
2678 'complete_tax_set': True,
2681 account_chart_template()
2683 class account_tax_template(osv.osv):
2685 _name = 'account.tax.template'
2686 _description = 'Templates for Taxes'
2689 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2690 'name': fields.char('Tax Name', size=64, required=True),
2691 '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."),
2692 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2693 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True),
2694 '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."),
2695 '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."),
2696 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2697 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2698 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2699 '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."),
2700 'python_compute':fields.text('Python Code'),
2701 'python_compute_inv':fields.text('Python Code (reverse)'),
2702 'python_applicable':fields.text('Python Code'),
2705 # Fields used for the VAT declaration
2707 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2708 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2709 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2710 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2712 # Same fields for refund invoices
2714 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2715 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2716 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2717 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2718 '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."),
2719 'description': fields.char('Internal Name', size=32),
2720 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,),
2721 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
2722 'installable': fields.boolean('Should be Installed', help="Set this to False if you do not want to create real tax object from this template.")
2725 def name_get(self, cr, uid, ids, context=None):
2729 for record in self.read(cr, uid, ids, ['description','name'], context=context):
2730 name = record['description'] and record['description'] or record['name']
2731 res.append((record['id'],name ))
2734 def _default_company(self, cr, uid, context=None):
2735 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2737 return user.company_id.id
2738 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2741 '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''',
2742 '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''',
2743 'applicable_type': 'true',
2751 'include_base_amount': False,
2752 'type_tax_use': 'all',
2758 def _generate_tax(self, cr, uid, tax_templates, tax_code_template_ref, company_id, context=None):
2760 This method generate taxes from templates.
2762 :param tax_templates: list of browse record of the tax templates to process
2763 :param tax_code_template_ref: Taxcode templates reference.
2764 :param company_id: id of the company the wizard is running for
2767 'tax_template_to_tax': mapping between tax template and the newly generated taxes corresponding,
2768 'account_dict': dictionary containing a to-do list with all the accounts to assign on new taxes
2775 tax_template_to_tax = {}
2776 for tax in tax_templates:
2779 'sequence': tax.sequence,
2780 'amount': tax.amount,
2782 'applicable_type': tax.applicable_type,
2783 'domain': tax.domain,
2784 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_to_tax) and tax_template_to_tax[tax.parent_id.id]) or False,
2785 'child_depend': tax.child_depend,
2786 'python_compute': tax.python_compute,
2787 'python_compute_inv': tax.python_compute_inv,
2788 'python_applicable': tax.python_applicable,
2789 '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,
2790 '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,
2791 'base_sign': tax.base_sign,
2792 'tax_sign': tax.tax_sign,
2793 '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,
2794 '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,
2795 'ref_base_sign': tax.ref_base_sign,
2796 'ref_tax_sign': tax.ref_tax_sign,
2797 'include_base_amount': tax.include_base_amount,
2798 'description': tax.description,
2799 'company_id': company_id,
2800 'type_tax_use': tax.type_tax_use,
2801 'price_include': tax.price_include
2803 new_tax = self.pool.get('account.tax').create(cr, uid, vals_tax)
2804 tax_template_to_tax[tax.id] = new_tax
2805 #as the accounts have not been created yet, we have to wait before filling these fields
2806 todo_dict[new_tax] = {
2807 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2808 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2810 res.update({'tax_template_to_tax': tax_template_to_tax, 'account_dict': todo_dict})
2813 account_tax_template()
2815 # Fiscal Position Templates
2817 class account_fiscal_position_template(osv.osv):
2818 _name = 'account.fiscal.position.template'
2819 _description = 'Template for Fiscal Position'
2822 'name': fields.char('Fiscal Position Template', size=64, required=True),
2823 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2824 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2825 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping'),
2826 'note': fields.text('Notes', translate=True),
2829 def generate_fiscal_position(self, cr, uid, chart_temp_id, tax_template_ref, acc_template_ref, company_id, context=None):
2831 This method generate Fiscal Position , Fiscal Position Accounts and Fiscal Position Taxes from templates.
2832 @param cr: A database cursor.
2833 @param uid: ID of the user currently logged in.
2834 @param chart_temp_id: Chart Template Id.
2835 @param taxes_ids: Taxes templates reference for generating account.fiscal.position.tax.
2836 @param acc_template_ref: Account templates reference for generating account.fiscal.position.account.
2837 @param company_id: company_id selected from wizard.multi.charts.accounts.
2841 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2842 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2843 obj_fiscal_position = self.pool.get('account.fiscal.position')
2844 fp_ids = self.search(cr, uid, [('chart_template_id', '=', chart_temp_id)])
2845 for position in self.browse(cr, uid, fp_ids, context=context):
2846 new_fp = obj_fiscal_position.create(cr, uid, {'company_id': company_id, 'name': position.name})
2847 for tax in position.tax_ids:
2848 obj_tax_fp.create(cr, uid, {
2849 'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2850 'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2851 'position_id': new_fp
2853 for acc in position.account_ids:
2854 obj_ac_fp.create(cr, uid, {
2855 'account_src_id': acc_template_ref[acc.account_src_id.id],
2856 'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2857 'position_id': new_fp
2861 account_fiscal_position_template()
2863 class account_fiscal_position_tax_template(osv.osv):
2864 _name = 'account.fiscal.position.tax.template'
2865 _description = 'Template Tax Fiscal Position'
2866 _rec_name = 'position_id'
2869 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2870 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2871 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2874 account_fiscal_position_tax_template()
2876 class account_fiscal_position_account_template(osv.osv):
2877 _name = 'account.fiscal.position.account.template'
2878 _description = 'Template Account Fiscal Mapping'
2879 _rec_name = 'position_id'
2881 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2882 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2883 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2886 account_fiscal_position_account_template()
2888 # ---------------------------------------------------------
2889 # Account Financial Report
2890 # ---------------------------------------------------------
2892 class account_financial_report(osv.osv):
2893 _name = "account.financial.report"
2894 _description = "Account Report"
2896 def _get_level(self, cr, uid, ids, field_name, arg, context=None):
2898 for report in self.browse(cr, uid, ids, context=context):
2900 if report.parent_id:
2901 level = report.parent_id.level + 1
2902 res[report.id] = level
2905 def _get_children_by_order(self, cr, uid, ids, context=None):
2909 ids2 = self.search(cr, uid, [('parent_id', '=', id)], order='sequence ASC', context=context)
2910 res += self._get_children_by_order(cr, uid, ids2, context=context)
2913 def _get_balance(self, cr, uid, ids, name, args, context=None):
2916 for report in self.browse(cr, uid, ids, context=context):
2918 if report.id in res_all:
2919 balance = res_all[report.id]
2920 elif report.type == 'accounts':
2921 # it's the sum of balance of the linked accounts
2922 for a in report.account_ids:
2923 balance += a.balance
2924 elif report.type == 'account_report' and report.account_report_id:
2925 # it's the amount of the linked report
2926 res2 = self._get_balance(cr, uid, [report.account_report_id.id], 'balance', False, context=context)
2927 res_all.update(res2)
2928 for key, value in res2.items():
2930 elif report.type == 'sum':
2931 # it's the sum of balance of the children of this account.report
2932 #for child in report.children_ids:
2933 res2 = self._get_balance(cr, uid, [rec.id for rec in report.children_ids], 'balance', False, context=context)
2934 res_all.update(res2)
2935 for key, value in res2.items():
2937 res[report.id] = balance
2938 res_all[report.id] = balance
2942 'name': fields.char('Report Name', size=128, required=True, translate=True),
2943 'parent_id': fields.many2one('account.financial.report', 'Parent'),
2944 'children_ids': fields.one2many('account.financial.report', 'parent_id', 'Account Report'),
2945 'sequence': fields.integer('Sequence'),
2946 'note': fields.text('Notes'),
2947 'balance': fields.function(_get_balance, 'Balance'),
2948 'level': fields.function(_get_level, string='Level', store=True, type='integer'),
2949 'type': fields.selection([
2951 ('accounts','Accounts'),
2952 ('account_type','Account Type'),
2953 ('account_report','Report Value'),
2955 'account_ids': fields.many2many('account.account', 'account_account_financial_report', 'report_line_id', 'account_id', 'Accounts'),
2956 'display_detail': fields.boolean('Display details', help='Display every account with its balance instead of the sum.'),
2957 'account_report_id': fields.many2one('account.financial.report', 'Report Value'),
2958 'account_type_ids': fields.many2many('account.account.type', 'account_account_financial_report_type', 'report_id', 'account_type_id', 'Account Types'),
2965 account_financial_report()
2967 # ---------------------------------------------------------
2968 # Account generation from template wizards
2969 # ---------------------------------------------------------
2971 class wizard_multi_charts_accounts(osv.osv_memory):
2973 Create a new account chart for a company.
2976 * an account chart template
2977 * a number of digits for formatting code of non-view accounts
2978 * a list of bank accounts owned by the company
2980 * generates all accounts from the template and assigns them to the right company
2981 * generates all taxes and tax codes, changing account assignations
2982 * generates all accounting properties and assigns them correctly
2984 _name='wizard.multi.charts.accounts'
2985 _inherit = 'res.config'
2988 'company_id':fields.many2one('res.company', 'Company', required=True),
2989 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2990 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Cash and Banks', required=True),
2991 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2992 '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."),
2993 "sale_tax": fields.many2one("account.tax.template", "Default Sales Tax"),
2994 "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
2995 'sale_tax_rate': fields.float('Sales Tax(%)'),
2996 'purchase_tax_rate': fields.float('Purchase Tax(%)'),
2997 'complete_tax_set': fields.boolean('Complete Set of Taxes', help='This boolean helps you to choose if you want to propose to the user to encode the sales and purchase rates or use the usual m2o fields. This last choice assumes that the set of tax defined for the chosen template is complete'),
2999 def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
3001 tax_templ_obj = self.pool.get('account.tax.template')
3002 res['value'] = {'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False}
3003 if chart_template_id:
3004 data = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3005 res['value'].update({'complete_tax_set': data.complete_tax_set})
3006 if data.complete_tax_set:
3007 # default tax is given by the lowest sequence. For same sequence we will take the latest created as it will be the case for tax created while isntalling the generic chart of account
3008 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3009 , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all')), ('installable', '=', True)], order="sequence, id desc")
3010 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3011 , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all')), ('installable', '=', True)], order="sequence, id desc")
3012 res['value'].update({'sale_tax': sale_tax_ids and sale_tax_ids[0] or False, 'purchase_tax': purchase_tax_ids and purchase_tax_ids[0] or False})
3014 if data.code_digits:
3015 res['value'].update({'code_digits': data.code_digits})
3019 def default_get(self, cr, uid, fields, context=None):
3020 res = super(wizard_multi_charts_accounts, self).default_get(cr, uid, fields, context=context)
3021 tax_templ_obj = self.pool.get('account.tax.template')
3023 if 'bank_accounts_id' in fields:
3024 res.update({'bank_accounts_id': [{'acc_name': _('Current'), 'account_type': 'bank'},
3025 {'acc_name': _('Deposit'), 'account_type': 'bank'},
3026 {'acc_name': _('Cash'), 'account_type': 'cash'}]})
3027 if 'company_id' in fields:
3028 res.update({'company_id': self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0].company_id.id})
3029 if 'seq_journal' in fields:
3030 res.update({'seq_journal': True})
3032 ids = self.pool.get('account.chart.template').search(cr, uid, [('visible', '=', True)], context=context)
3034 if 'chart_template_id' in fields:
3035 res.update({'chart_template_id': ids[0]})
3036 if 'sale_tax' in fields:
3037 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3038 , "=", ids[0]), ('type_tax_use', 'in', ('sale','all')), ('installable', '=', True)], order="sequence")
3039 res.update({'sale_tax': sale_tax_ids and sale_tax_ids[0] or False})
3040 if 'purchase_tax' in fields:
3041 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3042 , "=", ids[0]), ('type_tax_use', 'in', ('purchase','all')), ('installable', '=', True)], order="sequence")
3043 res.update({'purchase_tax': purchase_tax_ids and purchase_tax_ids[0] or False})
3046 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
3047 res = super(wizard_multi_charts_accounts, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
3049 acc_template_obj = self.pool.get('account.chart.template')
3050 company_obj = self.pool.get('res.company')
3052 template_ids = acc_template_obj.search(cr, uid, [('visible', '=', True)], context=context)
3053 company_ids = company_obj.search(cr, uid, [], context=context)
3054 #display in the widget selection of companies, only the companies that haven't been configured yet (but don't care about the demo chart of accounts)
3055 cr.execute("SELECT company_id FROM account_account WHERE active = 't' AND account_account.parent_id IS NULL AND name != %s", ("Chart For Automated Tests",))
3056 configured_cmp = [r[0] for r in cr.fetchall()]
3057 unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
3058 for field in res['fields']:
3059 if field == 'company_id':
3060 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
3061 res['fields'][field]['selection'] = [('', '')]
3062 if unconfigured_cmp:
3063 cmp_select = [(line.id, line.name) for line in company_obj.browse(cr, uid, unconfigured_cmp)]
3064 res['fields'][field]['selection'] = cmp_select
3065 if field == 'chart_template_id':
3066 res['fields'][field]['selection'] = [('', '')]
3068 template_select = [(template.id, template.name) for template in acc_template_obj.browse(cr, uid, template_ids)]
3069 res['fields'][field]['selection'] = template_select
3072 def check_created_journals(self, cr, uid, vals_journal, company_id, context=None):
3074 This method used for checking journals already created or not. If not then create new journal.
3076 obj_journal = self.pool.get('account.journal')
3077 rec_list = obj_journal.search(cr, uid, [('name','=', vals_journal['name']),('company_id', '=', company_id)], context=context)
3079 obj_journal.create(cr, uid, vals_journal, context=context)
3082 def generate_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3084 This method used for creating journals.
3085 @param cr: A database cursor.
3086 @param uid: ID of the user currently logged in.
3087 @param chart_temp_id: Chart Template Id.
3088 @param acc_template_ref: Account templates reference.
3089 @param company_id: company_id selected from wizard.multi.charts.accounts.
3091 journal_data = self._prepare_all_journals(cr, uid, chart_template_id, acc_template_ref, company_id, context=context)
3092 for vals_journal in journal_data:
3093 self.check_created_journals(cr, uid, vals_journal, company_id, context=context)
3096 def _prepare_all_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3097 def _get_analytic_journal(journal_type):
3098 # Get the analytic journal
3099 analytic_journal_ids = []
3100 if journal_type in ('sale', 'sale_refund'):
3101 analytical_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','sale')], context=context)
3102 elif journal_type in ('purchase', 'purchase_refund'):
3103 analytical_journal_ids = analytic_journal_obj.search(cr, uid, [('type','=','purchase')], context=context)
3104 elif journal_type == 'general':
3105 analytical_journal_ids = analytic_journal_obj.search(cr, uid, [('type', '=', 'situation')], context=context)
3106 return analytic_journal_ids and analytic_journal_ids[0] or False
3108 def _get_default_account(journal_type, type='debit'):
3109 # Get the default accounts
3110 default_account = False
3111 if journal_type in ('sale', 'sale_refund'):
3112 default_account = acc_template_ref.get(template.property_account_income_categ.id)
3113 elif journal_type in ('purchase', 'purchase_refund'):
3114 default_account = acc_template_ref.get(template.property_account_expense_categ.id)
3115 elif journal_type == 'situation':
3117 default_account = acc_template_ref.get(template.property_account_expense_opening.id)
3119 default_account = acc_template_ref.get(template.property_account_income_opening.id)
3120 return default_account
3122 def _get_view_id(journal_type):
3123 # Get the journal views
3124 if journal_type in ('general', 'situation'):
3125 data = obj_data.get_object_reference(cr, uid, 'account', 'account_journal_view')
3126 elif journal_type in ('sale_refund', 'purchase_refund'):
3127 data = obj_data.get_object_reference(cr, uid, 'account', 'account_sp_refund_journal_view')
3129 data = obj_data.get_object_reference(cr, uid, 'account', 'account_sp_journal_view')
3130 return data and data[1] or False
3133 'sale': _('Sales Journal'),
3134 'purchase': _('Purchase Journal'),
3135 'sale_refund': _('Sales Refund Journal'),
3136 'purchase_refund': _('Purchase Refund Journal'),
3137 'general': _('Miscellaneous Journal'),
3138 'situation': _('Opening Entries Journal'),
3142 'purchase': _('EXJ'),
3143 'sale_refund': _('SCNJ'),
3144 'purchase_refund': _('ECNJ'),
3145 'general': _('MISC'),
3146 'situation': _('OPEJ'),
3149 obj_data = self.pool.get('ir.model.data')
3150 analytic_journal_obj = self.pool.get('account.analytic.journal')
3151 template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3154 for journal_type in ['sale', 'purchase', 'sale_refund', 'purchase_refund', 'general', 'situation']:
3156 'type': journal_type,
3157 'name': journal_names[journal_type],
3158 'code': journal_codes[journal_type],
3159 'company_id': company_id,
3160 'centralisation': journal_type == 'situation',
3161 'view_id': _get_view_id(journal_type),
3162 'analytic_journal_id': _get_analytic_journal(journal_type),
3163 'default_credit_account_id': _get_default_account(journal_type, 'credit'),
3164 'default_debit_account_id': _get_default_account(journal_type, 'debit'),
3166 journal_data.append(vals)
3169 def generate_properties(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3171 This method used for creating properties.
3173 :param chart_template_id: id of the current chart template for which we need to create properties
3174 :param acc_template_ref: Mapping between ids of account templates and real accounts created from them
3175 :param company_id: company_id selected from wizard.multi.charts.accounts.
3178 property_obj = self.pool.get('ir.property')
3179 field_obj = self.pool.get('ir.model.fields')
3181 ('property_account_receivable','res.partner','account.account'),
3182 ('property_account_payable','res.partner','account.account'),
3183 ('property_account_expense_categ','product.category','account.account'),
3184 ('property_account_income_categ','product.category','account.account'),
3185 ('property_account_expense','product.template','account.account'),
3186 ('property_account_income','product.template','account.account'),
3187 ('property_reserve_and_surplus_account','res.company','account.account')
3189 template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3190 for record in todo_list:
3191 account = getattr(template, record[0])
3192 value = account and 'account.account,' + str(acc_template_ref[account.id]) or False
3194 field = field_obj.search(cr, uid, [('name', '=', record[0]),('model', '=', record[1]),('relation', '=', record[2])], context=context)
3197 'company_id': company_id,
3198 'fields_id': field[0],
3201 property_ids = property_obj.search(cr, uid, [('name','=', record[0]),('company_id', '=', company_id)], context=context)
3203 #the property exist: modify it
3204 property_obj.write(cr, uid, property_ids, vals, context=context)
3206 #create the property
3207 property_obj.create(cr, uid, vals, context=context)
3210 def _install_template(self, cr, uid, template_id, company_id, code_digits=None, obj_wizard=None, acc_ref={}, taxes_ref={}, tax_code_ref={}, context=None):
3212 This function recursively loads the template objects and create the real objects from them.
3214 :param template_id: id of the chart template to load
3215 :param company_id: id of the company the wizard is running for
3216 :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3217 :param obj_wizard: the current wizard for generating the COA from the templates
3218 :returns: return a tuple with a dictionary containing
3219 * the mapping between the account template ids and the ids of the real accounts that have been generated
3220 from them, as first item,
3221 * a similar dictionary for mapping the tax templates and taxes, as second item,
3222 * a last identical containing the mapping of tax code templates and tax codes
3223 :rtype: tuple(dict, dict, dict)
3225 template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3226 if template.parent_id:
3227 tmp1, tmp2, tmp3 = self._install_template(cr, uid, template.parent_id.id, company_id, code_digits=code_digits, acc_ref=acc_ref, taxes_ref=taxes_ref, tax_code_ref=tax_code_ref, context=context)
3228 acc_ref.update(tmp1)
3229 taxes_ref.update(tmp2)
3230 tax_code_ref.update(tmp3)
3231 tmp1, tmp2, tmp3 = self._load_template(cr, uid, template_id, company_id, code_digits=code_digits, obj_wizard=obj_wizard, account_ref=acc_ref, taxes_ref=taxes_ref, tax_code_ref=tax_code_ref, context=context)
3232 acc_ref.update(tmp1)
3233 taxes_ref.update(tmp2)
3234 tax_code_ref.update(tmp3)
3235 return acc_ref, taxes_ref, tax_code_ref
3237 def _load_template(self, cr, uid, template_id, company_id, code_digits=None, obj_wizard=None, account_ref={}, taxes_ref={}, tax_code_ref={}, context=None):
3239 This function generates all the objects from the templates
3241 :param template_id: id of the chart template to load
3242 :param company_id: id of the company the wizard is running for
3243 :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3244 :param obj_wizard: the current wizard for generating the COA from the templates
3245 :returns: return a tuple with a dictionary containing
3246 * the mapping between the account template ids and the ids of the real accounts that have been generated
3247 from them, as first item,
3248 * a similar dictionary for mapping the tax templates and taxes, as second item,
3249 * a last identical containing the mapping of tax code templates and tax codes
3250 :rtype: tuple(dict, dict, dict)
3252 template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3253 obj_tax_code_template = self.pool.get('account.tax.code.template')
3254 obj_acc_tax = self.pool.get('account.tax')
3255 obj_tax_temp = self.pool.get('account.tax.template')
3256 obj_acc_template = self.pool.get('account.account.template')
3257 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
3259 # create all the tax code.
3260 tax_code_ref.update(obj_tax_code_template.generate_tax_code(cr, uid, template.tax_code_root_id.id, company_id, context=context))
3262 # Generate taxes from templates.
3263 tax_templates = [x for x in template.tax_template_ids if x.installable]
3264 generated_tax_res = obj_tax_temp._generate_tax(cr, uid, tax_templates, tax_code_ref, company_id, context=context)
3265 taxes_ref.update(generated_tax_res['tax_template_to_tax'])
3267 # Generating Accounts from templates.
3268 account_template_ref = obj_acc_template.generate_account(cr, uid, template_id, taxes_ref, account_ref, code_digits, company_id, context=context)
3269 account_ref.update(account_template_ref)
3271 # writing account values on tax after creation of accounts
3272 for key,value in generated_tax_res['account_dict'].items():
3273 if value['account_collected_id'] or value['account_paid_id']:
3274 obj_acc_tax.write(cr, uid, [key], {
3275 'account_collected_id': account_ref.get(value['account_collected_id'], False),
3276 'account_paid_id': account_ref.get(value['account_paid_id'], False),
3280 self.generate_journals(cr, uid, template_id, account_ref, company_id, context=context)
3282 # generate properties function
3283 self.generate_properties(cr, uid, template_id, account_ref, company_id, context=context)
3285 # Generate Fiscal Position , Fiscal Position Accounts and Fiscal Position Taxes from templates
3286 obj_fiscal_position_template.generate_fiscal_position(cr, uid, template_id, taxes_ref, account_ref, company_id, context=context)
3288 return account_ref, taxes_ref, tax_code_ref
3290 def _create_tax_templates_from_rates(self, cr, uid, obj_wizard, company_id, context=None):
3292 This function checks if the chosen chart template is configured as containing a full set of taxes, and if
3293 it's not the case, it creates the templates for account.tax.code and for account.account.tax objects accordingly
3294 to the provided sale/purchase rates.
3296 :param obj_wizard: browse record of wizard to generate COA from templates
3297 :param company_id: id of the company for wich the wizard is running
3300 obj_tax_code_template = self.pool.get('account.tax.code.template')
3301 obj_tax_temp = self.pool.get('account.tax.template')
3302 chart_template = obj_wizard.chart_template_id
3304 # create tax templates and tax code templates from purchase_tax_rate and sale_tax_rate fields
3305 if not chart_template.complete_tax_set:
3307 'sale': obj_wizard.sale_tax_rate,
3308 'purchase': obj_wizard.purchase_tax_rate,
3311 for tax_type, value in tax_data.items():
3312 # don't consider cases where entered value in rates are lower than 0
3314 #create the tax code templates for base and tax
3316 'name': (tax_type == 'sale' and _('Taxable Sales at %s') or _('Taxable Purchases at %s')) % value,
3317 'code': (tax_type == 'sale' and _('BASE-S-%s') or _('BASE-P-%s')) %value,
3318 'parent_id': chart_template.tax_code_root_id.id,
3319 'company_id': company_id,
3321 new_base_code_id = obj_tax_code_template.create(cr, uid, base_code_vals, context=context)
3323 'name': (tax_type == 'sale' and _('Tax Received at %s') or _('Tax Paid at %s')) % value,
3324 'code': (tax_type == 'sale' and _('TAX-S-%s') or _('TAX-P-%s')) %value,
3325 'parent_id': chart_template.tax_code_root_id.id,
3326 'company_id': company_id,
3328 new_tax_code_id = obj_tax_code_template.create(cr, uid, tax_code_vals, context=context)
3330 tax_template_id = obj_tax_temp.create(cr, uid, {
3331 'name': _('Tax %s%%') % value,
3332 'amount': value/100,
3333 'base_code_id': new_base_code_id,
3334 'tax_code_id': new_tax_code_id,
3335 'ref_base_code_id': new_base_code_id,
3336 'ref_tax_code_id': new_tax_code_id,
3337 'type_tax_use': tax_type,
3338 'installable': True,
3341 'chart_template_id': chart_template.id or False,
3343 #record this new tax_template as default for this chart template
3344 field_name = tax_type == 'sale' and 'sale_tax' or 'purchase_tax'
3345 vals[field_name] = tax_template_id
3346 self.write(cr, uid, obj_wizard.id, vals, context=context)
3349 def execute(self, cr, uid, ids, context=None):
3351 This function is called at the confirmation of the wizard to generate the COA from the templates. It will read
3352 all the provided information to create the accounts, the banks, the journals, the taxes, the tax codes, the
3353 accounting properties... accordingly for the chosen company.
3355 ir_values_obj = self.pool.get('ir.values')
3356 obj_wizard = self.browse(cr, uid, ids[0])
3357 company_id = obj_wizard.company_id.id
3358 # If the floats for sale/purchase rates have been filled, create templates from them
3359 self._create_tax_templates_from_rates(cr, uid, obj_wizard, company_id, context=context)
3361 # Install all the templates objects and generate the real objects
3362 acc_template_ref, taxes_ref, tax_code_ref = self._install_template(cr, uid, obj_wizard.chart_template_id.id, company_id, code_digits=obj_wizard.code_digits, obj_wizard=obj_wizard, context=context)
3364 # write values of default taxes for product
3365 if obj_wizard.sale_tax and taxes_ref:
3366 ir_values_obj.set(cr, uid, key='default', key2=False, name="taxes_id", company=company_id,
3367 models =[('product.product',False)], value=[taxes_ref[obj_wizard.sale_tax.id]])
3368 if obj_wizard.purchase_tax and taxes_ref:
3369 ir_values_obj.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=company_id,
3370 models =[('product.product',False)], value=[taxes_ref[obj_wizard.purchase_tax.id]])
3372 # Create Bank journals
3373 self._create_bank_journals_from_o2m(cr, uid, obj_wizard, company_id, acc_template_ref, context=context)
3376 def _prepare_bank_journal(self, cr, uid, line, current_num, default_account_id, company_id, context=None):
3378 This function prepares the value to use for the creation of a bank journal created through the wizard of
3379 generating COA from templates.
3381 :param line: dictionary containing the values encoded by the user related to his bank account
3382 :param current_num: integer corresponding to a counter of the already created bank journals through this wizard.
3383 :param default_account_id: id of the default debit.credit account created before for this journal.
3384 :param company_id: id of the company for which the wizard is running
3385 :return: mapping of field names and values
3388 obj_data = self.pool.get('ir.model.data')
3389 # Get the id of journal views
3390 tmp = obj_data.get_object_reference(cr, uid, 'account', 'account_journal_bank_view_multi')
3391 view_id_cur = tmp and tmp[1] or False
3392 tmp = obj_data.get_object_reference(cr, uid, 'account', 'account_journal_bank_view')
3393 view_id_cash = tmp and tmp[1] or False
3395 'name': line['acc_name'],
3396 'code': _('BNK') + str(current_num),
3397 'type': line['account_type'] == 'cash' and 'cash' or 'bank',
3398 'company_id': company_id,
3399 'analytic_journal_id': False,
3401 'default_credit_account_id': default_account_id,
3402 'default_debit_account_id': default_account_id,
3404 if line['currency_id']:
3405 vals['view_id'] = view_id_cur
3406 vals['currency'] = line['currency_id']
3408 vals['view_id'] = view_id_cash
3411 def _prepare_bank_account(self, cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=None):
3413 This function prepares the value to use for the creation of the default debit and credit accounts of a
3414 bank journal created through the wizard of generating COA from templates.
3416 :param line: dictionary containing the values encoded by the user related to his bank account
3417 :param new_code: integer corresponding to the next available number to use as account code
3418 :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3419 of the accounts that have been generated from them.
3420 :param ref_acc_bank: browse record of the account template set as root of all bank accounts for the chosen
3422 :param company_id: id of the company for which the wizard is running
3423 :return: mapping of field names and values
3426 obj_data = self.pool.get('ir.model.data')
3428 # Get the id of the user types fr-or cash and bank
3429 tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_cash')
3430 cash_type = tmp and tmp[1] or False
3431 tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_bank')
3432 bank_type = tmp and tmp[1] or False
3434 'name': line['acc_name'],
3435 'currency_id': line['currency_id'],
3437 'type': 'liquidity',
3438 'user_type': line['account_type'] == 'cash' and cash_type or bank_type,
3439 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
3440 'company_id': company_id,
3443 def _create_bank_journals_from_o2m(self, cr, uid, obj_wizard, company_id, acc_template_ref, context=None):
3445 This function creates bank journals and its accounts for each line encoded in the field bank_accounts_id of the
3448 :param obj_wizard: the current wizard that generates the COA from the templates.
3449 :param company_id: the id of the company for which the wizard is running.
3450 :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3451 of the accounts that have been generated from them.
3454 obj_acc = self.pool.get('account.account')
3455 obj_journal = self.pool.get('account.journal')
3456 code_digits = obj_wizard.code_digits
3458 # Build a list with all the data to process
3460 if obj_wizard.bank_accounts_id:
3461 for acc in obj_wizard.bank_accounts_id:
3463 'acc_name': acc.acc_name,
3464 'account_type': acc.account_type,
3465 'currency_id': acc.currency_id.id,
3467 journal_data.append(vals)
3468 ref_acc_bank = obj_wizard.chart_template_id.bank_account_view_id
3469 if journal_data and not ref_acc_bank.code:
3470 raise osv.except_osv(_('Configuration Error !'), _('The bank account defined on the selected chart of accounts hasn\'t a code.'))
3473 for line in journal_data:
3474 # Seek the next available number for the account code
3476 new_code = str(ref_acc_bank.code.ljust(code_digits-len(str(current_num)), '0')) + str(current_num)
3477 ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)])
3482 # Create the default debit/credit accounts for this bank journal
3483 vals = self._prepare_bank_account(cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=context)
3484 default_account_id = obj_acc.create(cr, uid, vals, context=context)
3486 #create the bank journal
3487 vals_journal = self._prepare_bank_journal(cr, uid, line, current_num, default_account_id, company_id, context=context)
3488 obj_journal.create(cr, uid, vals_journal)
3492 wizard_multi_charts_accounts()
3494 class account_bank_accounts_wizard(osv.osv_memory):
3495 _name='account.bank.accounts.wizard'
3498 'acc_name': fields.char('Account Name.', size=64, required=True),
3499 'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
3500 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
3501 'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
3504 account_bank_accounts_wizard()
3506 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: