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 (Assets 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"
248 #get all the necessary accounts
249 children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
250 #compute for each account the balance/debit/credit from the move lines
253 null_result = dict((fn, 0.0) for fn in field_names)
254 if children_and_consolidated:
255 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
259 wheres.append(query.strip())
260 if aml_query.strip():
261 wheres.append(aml_query.strip())
262 filters = " AND ".join(wheres)
263 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
264 'Filters: %s'%filters)
265 # IN might not work ideally in case there are too many
266 # children_and_consolidated, in that case join on a
268 # SELECT l.account_id as id FROM account_move_line l
269 # INNER JOIN (VALUES (id1), (id2), (id3), ...) AS tmp (id)
270 # ON l.account_id = tmp.id
271 # or make _get_children_and_consol return a query and join on that
272 request = ("SELECT l.account_id as id, " +\
273 ', '.join(map(mapping.__getitem__, field_names)) +
274 " FROM account_move_line l" \
275 " WHERE l.account_id IN %s " \
277 " GROUP BY l.account_id")
278 params = (tuple(children_and_consolidated),) + query_params
279 cr.execute(request, params)
280 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
281 'Status: %s'%cr.statusmessage)
283 for res in cr.dictfetchall():
284 accounts[res['id']] = res
286 # consolidate accounts with direct children
287 children_and_consolidated.reverse()
288 brs = list(self.browse(cr, uid, children_and_consolidated, context=context))
290 currency_obj = self.pool.get('res.currency')
294 # for child in current.child_id:
295 # if child.id not in sums:
296 # can_compute = False
298 # brs.insert(0, brs.pop(brs.index(child)))
300 # 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 res[id] = sums.get(id, null_result)
314 res[id] = null_result
317 def _get_company_currency(self, cr, uid, ids, field_name, arg, context=None):
319 for rec in self.browse(cr, uid, ids, context=context):
320 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.symbol)
323 def _get_child_ids(self, cr, uid, ids, field_name, arg, context=None):
325 for record in self.browse(cr, uid, ids, context=context):
326 if record.child_parent_ids:
327 result[record.id] = [x.id for x in record.child_parent_ids]
329 result[record.id] = []
331 if record.child_consol_ids:
332 for acc in record.child_consol_ids:
333 if acc.id not in result[record.id]:
334 result[record.id].append(acc.id)
338 def _get_level(self, cr, uid, ids, field_name, arg, context=None):
340 accounts = self.browse(cr, uid, ids, context=context)
341 for account in accounts:
343 if account.parent_id:
344 obj = self.browse(cr, uid, account.parent_id.id)
345 level = obj.level + 1
346 res[account.id] = level
349 def _set_credit_debit(self, cr, uid, account_id, name, value, arg, context=None):
350 if context.get('config_invisible', True):
353 account = self.browse(cr, uid, account_id, context=context)
354 diff = value - getattr(account,name)
358 journal_obj = self.pool.get('account.journal')
359 jids = journal_obj.search(cr, uid, [('type','=','situation'),('centralisation','=',1),('company_id','=',account.company_id.id)], context=context)
361 raise osv.except_osv(_('Error!'),_("You need an Opening journal with centralisation checked to set the initial balance!"))
363 period_obj = self.pool.get('account.period')
364 pids = period_obj.search(cr, uid, [('special','=',True),('company_id','=',account.company_id.id)], context=context)
366 raise osv.except_osv(_('Error!'),_("No opening/closing period defined, please create one to set the initial balance!"))
368 move_obj = self.pool.get('account.move.line')
369 move_id = move_obj.search(cr, uid, [
370 ('journal_id','=',jids[0]),
371 ('period_id','=',pids[0]),
372 ('account_id','=', account_id),
374 ('name','=', _('Opening Balance'))
377 move = move_obj.browse(cr, uid, move_id[0], context=context)
378 move_obj.write(cr, uid, move_id[0], {
379 name: diff+getattr(move,name)
383 raise osv.except_osv(_('Error!'),_("Unable to adapt the initial balance (negative value)!"))
384 nameinv = (name=='credit' and 'debit') or 'credit'
385 move_id = move_obj.create(cr, uid, {
386 'name': _('Opening Balance'),
387 'account_id': account_id,
388 'journal_id': jids[0],
389 'period_id': pids[0],
396 'name': fields.char('Name', size=128, required=True, select=True),
397 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
398 'code': fields.char('Code', size=64, required=True, select=1),
399 'type': fields.selection([
401 ('other', 'Regular'),
402 ('receivable', 'Receivable'),
403 ('payable', 'Payable'),
404 ('liquidity','Liquidity'),
405 ('consolidation', 'Consolidation'),
406 ('closed', 'Closed'),
407 ], 'Internal Type', required=True, help="The 'Internal Type' is used for features available on "\
408 "different types of accounts: view can not have journal items, consolidation are accounts that "\
409 "can have children accounts for multi-company consolidations, payable/receivable are for "\
410 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
411 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
412 help="Account Type is used for information purpose, to generate "
413 "country-specific legal reports, and set the rules to close a fiscal year and generate opening entries."),
414 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
415 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
416 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
417 'child_id': fields.function(_get_child_ids, type='many2many', relation="account.account", string="Child Accounts"),
418 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Balance', multi='balance'),
419 'credit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Credit', multi='balance'),
420 'debit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'),
421 # 'foreign_balance': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'),
422 'foreign_balance': fields.char('Foreign Balance', size=10),
423 'adjusted_balance': fields.char('Adjusted Balance', size=10),
424 'gain_loss_unrealized_balance':fields.char('Unrealized Gain Loss Balance', size=10),
425 # 'adjusted_balance': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'),
426 # 'gain_loss_unrealized_balance': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'),
427 'reconcile': fields.boolean('Allow Reconciliation', help="Check this box if this account allows reconciliation of journal items."),
428 'shortcut': fields.char('Shortcut', size=12),
429 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
430 'account_id', 'tax_id', 'Default Taxes'),
431 'note': fields.text('Note'),
432 'company_currency_id': fields.function(_get_company_currency, type='many2one', relation='res.currency', string='Company Currency'),
433 'company_id': fields.many2one('res.company', 'Company', required=True),
434 '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."),
436 'parent_left': fields.integer('Parent Left', select=1),
437 'parent_right': fields.integer('Parent Right', select=1),
438 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
440 'This will select how the current currency rate for outgoing transactions is computed. '\
441 'In most countries the legal method is "average" but only a few software systems are able to '\
442 'manage this. So if you import from another software system you may have to use the rate at date. ' \
443 'Incoming transactions always use the rate at date.', \
445 'level': fields.function(_get_level, string='Level', store=True, type='integer'),
452 'currency_mode': 'current',
453 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
456 def _check_recursion(self, cr, uid, ids, context=None):
457 obj_self = self.browse(cr, uid, ids[0], context=context)
458 p_id = obj_self.parent_id and obj_self.parent_id.id
459 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
462 cr.execute('SELECT DISTINCT child_id '\
463 'FROM account_account_consol_rel '\
464 'WHERE parent_id IN %s', (tuple(ids),))
465 child_ids = map(itemgetter(0), cr.fetchall())
467 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
470 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
471 if p_id and (p_id in s_ids):
477 def _check_type(self, cr, uid, ids, context=None):
480 accounts = self.browse(cr, uid, ids, context=context)
481 for account in accounts:
482 if account.child_id and account.type not in ('view', 'consolidation'):
486 def _check_account_type(self, cr, uid, ids, context=None):
487 for account in self.browse(cr, uid, ids, context=context):
488 if account.type in ('receivable', 'payable') and account.user_type.close_method != 'unreconciled':
493 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id']),
494 (_check_type, 'Configuration Error! \nYou can not define children to an account with internal type different of "View"! ', ['type']),
495 (_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']),
498 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
500 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
506 if name and str(name).startswith('partner:'):
507 part_id = int(name.split(':')[1])
508 part = self.pool.get('res.partner').browse(cr, user, part_id, context=context)
509 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
511 if name and str(name).startswith('type:'):
512 type = name.split(':')[1]
513 args += [('type', '=', type)]
518 ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
520 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
522 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
523 if not ids and len(name.split()) >= 2:
524 #Separating code and name of account for searching
525 operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
526 ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
528 ids = self.search(cr, user, args, context=context, limit=limit)
529 return self.name_get(cr, user, ids, context=context)
531 def name_get(self, cr, uid, ids, context=None):
534 reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
537 name = record['name']
539 name = record['code'] + ' ' + name
540 res.append((record['id'], name))
543 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
544 account = self.browse(cr, uid, id, context=context)
548 default = default.copy()
549 default['code'] = (account['code'] or '') + '(copy)'
552 if account.id in done_list:
554 done_list.append(account.id)
556 for child in account.child_id:
557 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
559 new_child_ids.append(child_ids)
560 default['child_parent_ids'] = [(6, 0, new_child_ids)]
562 default['child_parent_ids'] = False
563 return super(account_account, self).copy(cr, uid, id, default, context=context)
565 def _check_moves(self, cr, uid, ids, method, context=None):
566 line_obj = self.pool.get('account.move.line')
567 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
569 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
570 if method == 'write':
571 raise osv.except_osv(_('Error !'), _('You can not desactivate an account that contains some journal items.'))
572 elif method == 'unlink':
573 raise osv.except_osv(_('Error !'), _('You can not remove an account containing journal items!. '))
574 #Checking whether the account is set as a property to any Partner or not
575 value = 'account.account,' + str(ids[0])
576 partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
578 raise osv.except_osv(_('Warning !'), _('You can not remove/desactivate an account which is set on a customer or supplier.'))
581 def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
582 group1 = ['payable', 'receivable', 'other']
583 group2 = ['consolidation','view']
584 line_obj = self.pool.get('account.move.line')
585 for account in self.browse(cr, uid, ids, context=context):
586 old_type = account.type
587 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
588 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
589 #Check for 'Closed' type
590 if old_type == 'closed' and new_type !='closed':
591 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains journal items!"))
592 #Check for change From group1 to group2 and vice versa
593 if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
594 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,))
597 def write(self, cr, uid, ids, vals, context=None):
603 if isinstance(ids, (int, long)):
606 # Dont allow changing the company_id when account_move_line already exist
607 if 'company_id' in vals:
608 move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
610 # Allow the write if the value is the same
611 for i in [i['company_id'][0] for i in self.read(cr,uid,ids,['company_id'])]:
612 if vals['company_id']!=i:
613 raise osv.except_osv(_('Warning !'), _('You cannot modify Company of account as its related record exist in Entry Lines'))
614 if 'active' in vals and not vals['active']:
615 self._check_moves(cr, uid, ids, "write", context=context)
616 if 'type' in vals.keys():
617 self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
618 return super(account_account, self).write(cr, uid, ids, vals, context=context)
620 def unlink(self, cr, uid, ids, context=None):
621 self._check_moves(cr, uid, ids, "unlink", context=context)
622 return super(account_account, self).unlink(cr, uid, ids, context=context)
626 class account_journal_view(osv.osv):
627 _name = "account.journal.view"
628 _description = "Journal View"
630 'name': fields.char('Journal View', size=64, required=True),
631 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
635 account_journal_view()
638 class account_journal_column(osv.osv):
640 def _col_get(self, cr, user, context=None):
642 cols = self.pool.get('account.move.line')._columns
644 if col in ('period_id', 'journal_id'):
646 result.append( (col, cols[col].string) )
650 _name = "account.journal.column"
651 _description = "Journal Column"
653 'name': fields.char('Column Name', size=64, required=True),
654 'field': fields.selection(_col_get, 'Field Name', required=True, size=32),
655 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
656 'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column.", readonly=True),
657 'required': fields.boolean('Required'),
658 'readonly': fields.boolean('Readonly'),
660 _order = "view_id, sequence"
662 account_journal_column()
664 class account_journal(osv.osv):
665 _name = "account.journal"
666 _description = "Journal"
668 'name': fields.char('Journal Name', size=64, required=True),
669 'code': fields.char('Code', size=5, required=True, help="The code will be displayed on reports."),
670 '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,
671 help="Select 'Sale' for customer invoices journals."\
672 " Select 'Purchase' for supplier invoices journals."\
673 " Select 'Cash' or 'Bank' for journals that are used in customer or supplier payments."\
674 " Select 'General' for miscellaneous operations journals."\
675 " Select 'Opening/Closing Situation' for entries generated for new fiscal years."),
676 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
677 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
678 '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."),
679 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
680 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
681 '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."),
682 '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"),
683 '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."),
684 '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),
685 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
686 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
687 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
688 '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.'),
689 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
690 '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'),
694 'user_id': lambda self, cr, uid, context: uid,
695 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
698 ('code_company_uniq', 'unique (code, company_id)', 'The code of the journal must be unique per company !'),
699 ('name_company_uniq', 'unique (name, company_id)', 'The name of the journal must be unique per company !'),
704 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
705 journal = self.browse(cr, uid, id, context=context)
708 default = default.copy()
709 default['code'] = (journal['code'] or '') + '(copy)'
710 default['name'] = (journal['name'] or '') + '(copy)'
711 default['sequence_id'] = False
712 return super(account_journal, self).copy(cr, uid, id, default, context=context)
714 def write(self, cr, uid, ids, vals, context=None):
717 if isinstance(ids, (int, long)):
719 for journal in self.browse(cr, uid, ids, context=context):
720 if 'company_id' in vals and journal.company_id.id != vals['company_id']:
721 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
723 raise osv.except_osv(_('Warning !'), _('You can not modify the company of this journal as its related record exist in journal items'))
724 return super(account_journal, self).write(cr, uid, ids, vals, context=context)
726 def create_sequence(self, cr, uid, vals, context=None):
727 """ Create new no_gap entry sequence for every new Joural
729 # in account.journal code is actually the prefix of the sequence
730 # whereas ir.sequence code is a key to lookup global sequences.
731 prefix = vals['code'].upper()
734 'name': vals['name'],
735 'implementation':'no_gap',
736 'prefix': prefix + "/%(year)s/",
738 'number_increment': 1
740 if 'company_id' in vals:
741 seq['company_id'] = vals['company_id']
742 return self.pool.get('ir.sequence').create(cr, uid, seq)
744 def create(self, cr, uid, vals, context=None):
745 if not 'sequence_id' in vals or not vals['sequence_id']:
746 vals.update({'sequence_id': self.create_sequence(cr, uid, vals, context)})
747 return super(account_journal, self).create(cr, uid, vals, context)
749 def name_get(self, cr, user, ids, context=None):
751 Returns a list of tupples containing id, name.
752 result format: {[(id, name), (id, name), ...]}
754 @param cr: A database cursor
755 @param user: ID of the user currently logged in
756 @param ids: list of ids for which name should be read
757 @param context: context arguments, like lang, time zone
759 @return: Returns a list of tupples containing id, name
761 result = self.browse(cr, user, ids, context=context)
766 name = "%s (%s)" % (rs.name, rs.currency.name)
767 res += [(rs.id, name)]
770 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
776 if context.get('journal_type', False):
777 args += [('type','=',context.get('journal_type'))]
779 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
781 ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
783 return self.name_get(cr, user, ids, context=context)
785 def onchange_type(self, cr, uid, ids, type, currency, context=None):
786 obj_data = self.pool.get('ir.model.data')
787 user_pool = self.pool.get('res.users')
790 'sale':'account_sp_journal_view',
791 'sale_refund':'account_sp_refund_journal_view',
792 'purchase':'account_sp_journal_view',
793 'purchase_refund':'account_sp_refund_journal_view',
794 'cash':'account_journal_bank_view',
795 'bank':'account_journal_bank_view',
796 'general':'account_journal_view',
797 'situation':'account_journal_view'
801 view_id = type_map.get(type, 'account_journal_view')
802 user = user_pool.browse(cr, uid, uid)
803 if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
804 view_id = 'account_journal_bank_view_multi'
805 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
806 data = obj_data.browse(cr, uid, data_id[0], context=context)
809 'centralisation':type == 'situation',
810 'view_id':data.res_id,
818 class account_fiscalyear(osv.osv):
819 _name = "account.fiscalyear"
820 _description = "Fiscal Year"
822 'name': fields.char('Fiscal Year', size=64, required=True),
823 'code': fields.char('Code', size=6, required=True),
824 'company_id': fields.many2one('res.company', 'Company', required=True),
825 'date_start': fields.date('Start Date', required=True),
826 'date_stop': fields.date('End Date', required=True),
827 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
828 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True),
832 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
834 _order = "date_start"
836 def _check_fiscal_year(self, cr, uid, ids, context=None):
837 current_fiscal_yr = self.browse(cr, uid, ids, context=context)[0]
838 obj_fiscal_ids = self.search(cr, uid, [('company_id', '=', current_fiscal_yr.company_id.id)], context=context)
839 obj_fiscal_ids.remove(ids[0])
840 data_fiscal_yr = self.browse(cr, uid, obj_fiscal_ids, context=context)
842 for old_fy in data_fiscal_yr:
843 if old_fy.company_id.id == current_fiscal_yr['company_id'].id:
844 # Condition to check if the current fiscal year falls in between any previously defined fiscal year
845 if old_fy.date_start <= current_fiscal_yr['date_start'] <= old_fy.date_stop or \
846 old_fy.date_start <= current_fiscal_yr['date_stop'] <= old_fy.date_stop:
850 def _check_duration(self, cr, uid, ids, context=None):
851 obj_fy = self.browse(cr, uid, ids[0], context=context)
852 if obj_fy.date_stop < obj_fy.date_start:
857 (_check_duration, 'Error! The start date of the fiscal year must be before his end date.', ['date_start','date_stop']),
858 (_check_fiscal_year, 'Error! You can not define overlapping fiscal years for the same company.',['date_start', 'date_stop'])
861 def create_period3(self, cr, uid, ids, context=None):
862 return self.create_period(cr, uid, ids, context, 3)
864 def create_period(self, cr, uid, ids, context=None, interval=1):
865 period_obj = self.pool.get('account.period')
866 for fy in self.browse(cr, uid, ids, context=context):
867 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
868 period_obj.create(cr, uid, {
869 'name': _('Opening Period'),
870 'code': ds.strftime('00/%Y'),
874 'fiscalyear_id': fy.id,
876 while ds.strftime('%Y-%m-%d') < fy.date_stop:
877 de = ds + relativedelta(months=interval, days=-1)
879 if de.strftime('%Y-%m-%d') > fy.date_stop:
880 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
882 period_obj.create(cr, uid, {
883 'name': ds.strftime('%m/%Y'),
884 'code': ds.strftime('%m/%Y'),
885 'date_start': ds.strftime('%Y-%m-%d'),
886 'date_stop': de.strftime('%Y-%m-%d'),
887 'fiscalyear_id': fy.id,
889 ds = ds + relativedelta(months=interval)
892 def find(self, cr, uid, dt=None, exception=True, context=None):
894 dt = time.strftime('%Y-%m-%d')
895 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
898 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
903 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
910 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
912 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
913 return self.name_get(cr, user, ids, context=context)
917 class account_period(osv.osv):
918 _name = "account.period"
919 _description = "Account period"
921 'name': fields.char('Period Name', size=64, required=True),
922 'code': fields.char('Code', size=12),
923 'special': fields.boolean('Opening/Closing Period', size=12,
924 help="These periods can overlap."),
925 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
926 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
927 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
928 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True,
929 help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
930 'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
935 _order = "date_start, special desc"
937 ('name_company_uniq', 'unique(name, company_id)', 'The name of the period must be unique per company!'),
940 def _check_duration(self,cr,uid,ids,context=None):
941 obj_period = self.browse(cr, uid, ids[0], context=context)
942 if obj_period.date_stop < obj_period.date_start:
946 def _check_year_limit(self,cr,uid,ids,context=None):
947 for obj_period in self.browse(cr, uid, ids, context=context):
948 if obj_period.special:
951 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
952 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
953 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
954 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
957 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
958 for period in self.browse(cr, uid, pids):
959 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
964 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
965 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
968 def next(self, cr, uid, period, step, context=None):
969 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
974 def find(self, cr, uid, dt=None, context=None):
975 if context is None: context = {}
977 dt = time.strftime('%Y-%m-%d')
978 #CHECKME: shouldn't we check the state of the period?
979 args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
980 if context.get('company_id', False):
981 args.append(('company_id', '=', context['company_id']))
983 company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
984 args.append(('company_id', '=', company_id))
985 ids = self.search(cr, uid, args, context=context)
987 raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create one.')%dt)
990 def action_draft(self, cr, uid, ids, *args):
992 cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),))
993 cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),))
996 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1003 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
1005 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
1006 return self.name_get(cr, user, ids, context=context)
1008 def write(self, cr, uid, ids, vals, context=None):
1009 if 'company_id' in vals:
1010 move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
1012 raise osv.except_osv(_('Warning !'), _('You can not modify company of this period as some journal items exists.'))
1013 return super(account_period, self).write(cr, uid, ids, vals, context=context)
1015 def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
1016 period_from = self.browse(cr, uid, period_from_id)
1017 period_date_start = period_from.date_start
1018 company1_id = period_from.company_id.id
1019 period_to = self.browse(cr, uid, period_to_id)
1020 period_date_stop = period_to.date_stop
1021 company2_id = period_to.company_id.id
1022 if company1_id != company2_id:
1023 raise osv.except_osv(_('Error'), _('You should have chosen periods that belongs to the same company'))
1024 if period_date_start > period_date_stop:
1025 raise osv.except_osv(_('Error'), _('Start period should be smaller then End period'))
1026 #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).
1027 if period_from.special:
1028 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id)])
1029 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id), ('special', '=', False)])
1033 class account_journal_period(osv.osv):
1034 _name = "account.journal.period"
1035 _description = "Journal Period"
1037 def _icon_get(self, cr, uid, ids, field_name, arg=None, context=None):
1038 result = {}.fromkeys(ids, 'STOCK_NEW')
1039 for r in self.read(cr, uid, ids, ['state']):
1041 'draft': 'STOCK_NEW',
1042 'printed': 'STOCK_PRINT_PREVIEW',
1043 'done': 'STOCK_DIALOG_AUTHENTICATION',
1044 }.get(r['state'], 'STOCK_NEW')
1048 'name': fields.char('Journal-Period Name', size=64, required=True),
1049 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
1050 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
1051 'icon': fields.function(_icon_get, string='Icon', type='char', size=32),
1052 '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."),
1053 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
1054 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.'),
1055 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
1056 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
1059 def _check(self, cr, uid, ids, context=None):
1060 for obj in self.browse(cr, uid, ids, context=context):
1061 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))
1064 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
1067 def write(self, cr, uid, ids, vals, context=None):
1068 self._check(cr, uid, ids, context=context)
1069 return super(account_journal_period, self).write(cr, uid, ids, vals, context=context)
1071 def create(self, cr, uid, vals, context=None):
1072 period_id = vals.get('period_id',False)
1074 period = self.pool.get('account.period').browse(cr, uid, period_id, context=context)
1075 vals['state']=period.state
1076 return super(account_journal_period, self).create(cr, uid, vals, context)
1078 def unlink(self, cr, uid, ids, context=None):
1079 self._check(cr, uid, ids, context=context)
1080 return super(account_journal_period, self).unlink(cr, uid, ids, context=context)
1086 _order = "period_id"
1088 account_journal_period()
1090 class account_fiscalyear(osv.osv):
1091 _inherit = "account.fiscalyear"
1092 _description = "Fiscal Year"
1094 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1097 def copy(self, cr, uid, id, default={}, context=None):
1100 'end_journal_period_id': False
1102 return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1104 account_fiscalyear()
1105 #----------------------------------------------------------
1107 #----------------------------------------------------------
1108 class account_move(osv.osv):
1109 _name = "account.move"
1110 _description = "Account Entry"
1113 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1115 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1116 result format: {[(id, name), (id, name), ...]}
1118 @param cr: A database cursor
1119 @param user: ID of the user currently logged in
1120 @param name: name to search
1121 @param args: other arguments
1122 @param operator: default operator is 'ilike', it can be changed
1123 @param context: context arguments, like lang, time zone
1124 @param limit: Returns first 'n' ids of complete result, default is 80.
1126 @return: Returns a list of tuples containing id and name
1133 ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1135 if not ids and name and type(name) == int:
1136 ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1139 ids += self.search(cr, user, args, limit=limit, context=context)
1141 return self.name_get(cr, user, ids, context=context)
1143 def name_get(self, cursor, user, ids, context=None):
1144 if isinstance(ids, (int, long)):
1149 data_move = self.pool.get('account.move').browse(cursor, user, ids, context=context)
1150 for move in data_move:
1151 if move.state=='draft':
1152 name = '*' + str(move.id)
1155 res.append((move.id, name))
1158 def _get_period(self, cr, uid, context=None):
1159 periods = self.pool.get('account.period').find(cr, uid)
1164 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1165 if not ids: return {}
1166 cr.execute( 'SELECT move_id, SUM(debit) '\
1167 'FROM account_move_line '\
1168 'WHERE move_id IN %s '\
1169 'GROUP BY move_id', (tuple(ids),))
1170 result = dict(cr.fetchall())
1172 result.setdefault(id, 0.0)
1175 def _search_amount(self, cr, uid, obj, name, args, context):
1179 if isinstance(cond[2],(list,tuple)):
1180 if cond[1] in ['in','not in']:
1181 amount = tuple(cond[2])
1185 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1188 cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1189 res_ids = set(id[0] for id in cr.fetchall())
1190 ids = ids and (ids & res_ids) or res_ids
1192 return [('id', 'in', tuple(ids))]
1193 return [('id', '=', '0')]
1196 'name': fields.char('Number', size=64, required=True),
1197 'ref': fields.char('Reference', size=64),
1198 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1199 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1200 'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'State', required=True, readonly=True,
1201 help='All manually created new journal entry 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.'),
1202 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1203 '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.'),
1204 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1205 'amount': fields.function(_amount_compute, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1206 'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}, select=True),
1207 'narration':fields.text('Internal Note'),
1208 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1213 'period_id': _get_period,
1214 'date': lambda *a: time.strftime('%Y-%m-%d'),
1215 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1218 def _check_centralisation(self, cursor, user, ids, context=None):
1219 for move in self.browse(cursor, user, ids, context=context):
1220 if move.journal_id.centralisation:
1221 move_ids = self.search(cursor, user, [
1222 ('period_id', '=', move.period_id.id),
1223 ('journal_id', '=', move.journal_id.id),
1225 if len(move_ids) > 1:
1230 (_check_centralisation,
1231 'You can not create more than one move per period on centralized journal',
1235 def post(self, cr, uid, ids, context=None):
1238 invoice = context.get('invoice', False)
1239 valid_moves = self.validate(cr, uid, ids, context)
1242 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" !'))
1243 obj_sequence = self.pool.get('ir.sequence')
1244 for move in self.browse(cr, uid, valid_moves, context=context):
1247 journal = move.journal_id
1249 if invoice and invoice.internal_number:
1250 new_name = invoice.internal_number
1252 if journal.sequence_id:
1253 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1254 new_name = obj_sequence.next_by_id(cr, uid, journal.sequence_id.id, c)
1256 raise osv.except_osv(_('Error'), _('No sequence defined on the journal !'))
1259 self.write(cr, uid, [move.id], {'name':new_name})
1261 cr.execute('UPDATE account_move '\
1264 ('posted', tuple(valid_moves),))
1267 def button_validate(self, cursor, user, ids, context=None):
1268 for move in self.browse(cursor, user, ids, context=context):
1270 for line in move.line_id:
1271 account = line.account_id
1274 account = account.parent_id
1277 elif top<>account2.id:
1278 raise osv.except_osv(_('Error !'), _('You can not validate a journal entry unless all journal items belongs to the same chart of accounts !'))
1279 return self.post(cursor, user, ids, context=context)
1281 def button_cancel(self, cr, uid, ids, context=None):
1282 for line in self.browse(cr, uid, ids, context=context):
1283 if not line.journal_id.update_posted:
1284 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.'))
1286 cr.execute('UPDATE account_move '\
1288 'WHERE id IN %s', ('draft', tuple(ids),))
1291 def write(self, cr, uid, ids, vals, context=None):
1295 c['novalidate'] = True
1296 result = super(account_move, self).write(cr, uid, ids, vals, c)
1297 self.validate(cr, uid, ids, context=context)
1301 # TODO: Check if period is closed !
1303 def create(self, cr, uid, vals, context=None):
1306 if 'line_id' in vals and context.get('copy'):
1307 for l in vals['line_id']:
1310 'reconcile_id':False,
1311 'reconcil_partial_id':False,
1312 'analytic_lines':False,
1316 'account_tax_id':False,
1319 if 'journal_id' in vals and vals.get('journal_id', False):
1320 for l in vals['line_id']:
1322 l[2]['journal_id'] = vals['journal_id']
1323 context['journal_id'] = vals['journal_id']
1324 if 'period_id' in vals:
1325 for l in vals['line_id']:
1327 l[2]['period_id'] = vals['period_id']
1328 context['period_id'] = vals['period_id']
1330 default_period = self._get_period(cr, uid, context)
1331 for l in vals['line_id']:
1333 l[2]['period_id'] = default_period
1334 context['period_id'] = default_period
1336 if 'line_id' in vals:
1338 c['novalidate'] = True
1339 result = super(account_move, self).create(cr, uid, vals, c)
1340 self.validate(cr, uid, [result], context)
1342 result = super(account_move, self).create(cr, uid, vals, context)
1345 def copy(self, cr, uid, id, default={}, context=None):
1355 return super(account_move, self).copy(cr, uid, id, default, context)
1357 def unlink(self, cr, uid, ids, context=None, check=True):
1361 obj_move_line = self.pool.get('account.move.line')
1362 for move in self.browse(cr, uid, ids, context=context):
1363 if move['state'] != 'draft':
1364 raise osv.except_osv(_('UserError'),
1365 _('You can not delete a posted journal entry "%s"!') % \
1367 line_ids = map(lambda x: x.id, move.line_id)
1368 context['journal_id'] = move.journal_id.id
1369 context['period_id'] = move.period_id.id
1370 obj_move_line._update_check(cr, uid, line_ids, context)
1371 obj_move_line.unlink(cr, uid, line_ids, context=context)
1372 toremove.append(move.id)
1373 result = super(account_move, self).unlink(cr, uid, toremove, context)
1376 def _compute_balance(self, cr, uid, id, context=None):
1377 move = self.browse(cr, uid, id, context=context)
1379 for line in move.line_id:
1380 amount+= (line.debit - line.credit)
1383 def _centralise(self, cr, uid, move, mode, context=None):
1384 assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1385 currency_obj = self.pool.get('res.currency')
1390 account_id = move.journal_id.default_debit_account_id.id
1393 raise osv.except_osv(_('UserError'),
1394 _('There is no default default debit account defined \n' \
1395 'on journal "%s"') % move.journal_id.name)
1397 account_id = move.journal_id.default_credit_account_id.id
1400 raise osv.except_osv(_('UserError'),
1401 _('There is no default default credit account defined \n' \
1402 'on journal "%s"') % move.journal_id.name)
1404 # find the first line of this move with the current mode
1405 # or create it if it doesn't exist
1406 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1411 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1412 line_id = self.pool.get('account.move.line').create(cr, uid, {
1413 'name': _(mode.capitalize()+' Centralisation'),
1414 'centralisation': mode,
1415 'account_id': account_id,
1417 'journal_id': move.journal_id.id,
1418 'period_id': move.period_id.id,
1419 'date': move.period_id.date_stop,
1424 # find the first line of this move with the other mode
1425 # so that we can exclude it from our calculation
1426 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1433 cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1434 result = cr.fetchone()[0] or 0.0
1435 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1437 #adjust also the amount in currency if needed
1438 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,))
1439 for row in cr.dictfetchall():
1440 currency_id = currency_obj.browse(cr, uid, row['currency_id'], context=context)
1441 if not currency_obj.is_zero(cr, uid, currency_id, row['amount_currency']):
1442 amount_currency = row['amount_currency'] * -1
1443 account_id = amount_currency > 0 and move.journal_id.default_debit_account_id.id or move.journal_id.default_credit_account_id.id
1444 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']))
1447 cr.execute('update account_move_line set amount_currency=%s , account_id=%s where id=%s', (amount_currency, account_id, res[0]))
1449 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1450 line_id = self.pool.get('account.move.line').create(cr, uid, {
1451 'name': _('Currency Adjustment'),
1452 'centralisation': 'currency',
1453 'account_id': account_id,
1455 'journal_id': move.journal_id.id,
1456 'period_id': move.period_id.id,
1457 'date': move.period_id.date_stop,
1460 'currency_id': row['currency_id'],
1461 'amount_currency': amount_currency,
1467 # Validate a balanced move. If it is a centralised journal, create a move.
1469 def validate(self, cr, uid, ids, context=None):
1470 if context and ('__last_update' in context):
1471 del context['__last_update']
1473 valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1474 obj_analytic_line = self.pool.get('account.analytic.line')
1475 obj_move_line = self.pool.get('account.move.line')
1476 for move in self.browse(cr, uid, ids, context):
1477 # Unlink old analytic lines on move_lines
1478 for obj_line in move.line_id:
1479 for obj in obj_line.analytic_lines:
1480 obj_analytic_line.unlink(cr,uid,obj.id)
1482 journal = move.journal_id
1487 for line in move.line_id:
1488 amount += line.debit - line.credit
1489 line_ids.append(line.id)
1490 if line.state=='draft':
1491 line_draft_ids.append(line.id)
1494 company_id = line.account_id.company_id.id
1495 if not company_id == line.account_id.company_id.id:
1496 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1498 if line.account_id.currency_id and line.currency_id:
1499 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):
1500 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))
1502 if abs(amount) < 10 ** -4:
1503 # If the move is balanced
1504 # Add to the list of valid moves
1505 # (analytic lines will be created later for valid moves)
1506 valid_moves.append(move)
1508 # Check whether the move lines are confirmed
1510 if not line_draft_ids:
1512 # Update the move lines (set them as valid)
1514 obj_move_line.write(cr, uid, line_draft_ids, {
1516 }, context, check=False)
1521 if journal.type in ('purchase','sale'):
1522 for line in move.line_id:
1524 key = (line.account_id.id, line.tax_code_id.id)
1526 code = account2[key][0]
1527 amount = account2[key][1] * (line.debit + line.credit)
1528 elif line.account_id.id in account:
1529 code = account[line.account_id.id][0]
1530 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1531 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1532 obj_move_line.write(cr, uid, [line.id], {
1533 'tax_code_id': code,
1534 'tax_amount': amount
1535 }, context, check=False)
1536 elif journal.centralisation:
1537 # If the move is not balanced, it must be centralised...
1539 # Add to the list of valid moves
1540 # (analytic lines will be created later for valid moves)
1541 valid_moves.append(move)
1544 # Update the move lines (set them as valid)
1546 self._centralise(cr, uid, move, 'debit', context=context)
1547 self._centralise(cr, uid, move, 'credit', context=context)
1548 obj_move_line.write(cr, uid, line_draft_ids, {
1550 }, context, check=False)
1552 # We can't validate it (it's unbalanced)
1553 # Setting the lines as draft
1554 obj_move_line.write(cr, uid, line_ids, {
1556 }, context, check=False)
1557 # Create analytic lines for the valid moves
1558 for record in valid_moves:
1559 obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1561 valid_moves = [move.id for move in valid_moves]
1562 return len(valid_moves) > 0 and valid_moves or False
1566 class account_move_reconcile(osv.osv):
1567 _name = "account.move.reconcile"
1568 _description = "Account Reconciliation"
1570 'name': fields.char('Name', size=64, required=True),
1571 'type': fields.char('Type', size=16, required=True),
1572 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1573 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1574 'create_date': fields.date('Creation date', readonly=True),
1577 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1580 def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None):
1582 for rec in self.browse(cr, uid, ids, context=context):
1583 for line in rec.line_partial_ids:
1584 if line.account_id.currency_id:
1585 total += line.amount_currency
1587 total += (line.debit or 0.0) - (line.credit or 0.0)
1589 self.pool.get('account.move.line').write(cr, uid,
1590 map(lambda x: x.id, rec.line_partial_ids),
1591 {'reconcile_id': rec.id }
1595 def name_get(self, cr, uid, ids, context=None):
1599 for r in self.browse(cr, uid, ids, context=context):
1600 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1602 name = '%s (%.2f)' % (r.name, total)
1603 result.append((r.id,name))
1605 result.append((r.id,r.name))
1608 account_move_reconcile()
1610 #----------------------------------------------------------
1612 #----------------------------------------------------------
1615 child_depend: la taxe depend des taxes filles
1617 class account_tax_code(osv.osv):
1619 A code for the tax object.
1621 This code is used for some tax declarations.
1623 def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1624 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1625 if context.get('based_on', 'invoices') == 'payments':
1626 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1627 FROM account_move_line AS line, \
1628 account_move AS move \
1629 LEFT JOIN account_invoice invoice ON \
1630 (invoice.move_id = move.id) \
1631 WHERE line.tax_code_id IN %s '+where+' \
1632 AND move.id = line.move_id \
1633 AND ((invoice.state = \'paid\') \
1634 OR (invoice.id IS NULL)) \
1635 GROUP BY line.tax_code_id',
1636 (parent_ids,) + where_params)
1638 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1639 FROM account_move_line AS line, \
1640 account_move AS move \
1641 WHERE line.tax_code_id IN %s '+where+' \
1642 AND move.id = line.move_id \
1643 GROUP BY line.tax_code_id',
1644 (parent_ids,) + where_params)
1645 res=dict(cr.fetchall())
1646 obj_precision = self.pool.get('decimal.precision')
1648 for record in self.browse(cr, uid, ids, context=context):
1649 def _rec_get(record):
1650 amount = res.get(record.id, 0.0)
1651 for rec in record.child_ids:
1652 amount += _rec_get(rec) * rec.sign
1654 res2[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1657 def _sum_year(self, cr, uid, ids, name, args, context=None):
1660 move_state = ('posted', )
1661 if context.get('state', 'all') == 'all':
1662 move_state = ('draft', 'posted', )
1663 if context.get('fiscalyear_id', False):
1664 fiscalyear_id = context['fiscalyear_id']
1666 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1670 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1672 where = ' AND line.period_id IN %s AND move.state IN %s '
1673 where_params = (tuple(pids), move_state)
1674 return self._sum(cr, uid, ids, name, args, context,
1675 where=where, where_params=where_params)
1677 def _sum_period(self, cr, uid, ids, name, args, context):
1680 move_state = ('posted', )
1681 if context.get('state', False) == 'all':
1682 move_state = ('draft', 'posted', )
1683 if context.get('period_id', False):
1684 period_id = context['period_id']
1686 period_id = self.pool.get('account.period').find(cr, uid)
1688 return dict.fromkeys(ids, 0.0)
1689 period_id = period_id[0]
1690 return self._sum(cr, uid, ids, name, args, context,
1691 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1693 _name = 'account.tax.code'
1694 _description = 'Tax Code'
1697 'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1698 'code': fields.char('Case Code', size=64),
1699 'info': fields.text('Description'),
1700 'sum': fields.function(_sum_year, string="Year Sum"),
1701 'sum_period': fields.function(_sum_period, string="Period Sum"),
1702 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1703 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1704 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1705 'company_id': fields.many2one('res.company', 'Company', required=True),
1706 '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.'),
1707 '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"),
1710 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1715 ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1716 return self.name_get(cr, user, ids, context)
1718 def name_get(self, cr, uid, ids, context=None):
1719 if isinstance(ids, (int, long)):
1723 if isinstance(ids, (int, long)):
1725 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1726 return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1729 def _default_company(self, cr, uid, context=None):
1730 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1732 return user.company_id.id
1733 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1735 'company_id': _default_company,
1737 'notprintable': False,
1740 def copy(self, cr, uid, id, default=None, context=None):
1743 default = default.copy()
1744 default.update({'line_ids': []})
1745 return super(account_tax_code, self).copy(cr, uid, id, default, context)
1747 _check_recursion = check_cycle
1749 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1755 class account_tax(osv.osv):
1759 Type: percent, fixed, none, code
1760 PERCENT: tax = price * amount
1761 FIXED: tax = price + amount
1763 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1764 return result in the context
1765 Ex: result=round(price_unit*0.21,4)
1768 def get_precision_tax():
1769 def change_digit_tax(cr):
1770 res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account')
1772 return change_digit_tax
1774 _name = 'account.tax'
1775 _description = 'Tax'
1777 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1778 '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."),
1779 'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For taxes of type percentage, enter % ratio between 0-1."),
1780 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the tax without removing it."),
1781 'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1782 help="The computation method for the tax amount."),
1783 'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1784 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1785 '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."),
1786 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1787 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1788 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1789 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1790 '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."),
1791 'python_compute':fields.text('Python Code'),
1792 'python_compute_inv':fields.text('Python Code (reverse)'),
1793 'python_applicable':fields.text('Python Code'),
1796 # Fields used for the VAT declaration
1798 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1799 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1800 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1801 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1803 # Same fields for refund invoices
1805 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1806 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1807 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1808 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1809 '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"),
1810 'company_id': fields.many2one('res.company', 'Company', required=True),
1811 'description': fields.char('Tax Code',size=32),
1812 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1813 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1816 _sql_constraints = [
1817 ('name_company_uniq', 'unique(name, company_id)', 'Tax Name must be unique per company!'),
1820 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1822 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1823 result format: {[(id, name), (id, name), ...]}
1825 @param cr: A database cursor
1826 @param user: ID of the user currently logged in
1827 @param name: name to search
1828 @param args: other arguments
1829 @param operator: default operator is 'ilike', it can be changed
1830 @param context: context arguments, like lang, time zone
1831 @param limit: Returns first 'n' ids of complete result, default is 80.
1833 @return: Returns a list of tupples containing id and name
1841 ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context)
1843 ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context)
1845 ids = self.search(cr, user, args, limit=limit, context=context or {})
1846 return self.name_get(cr, user, ids, context=context)
1848 def write(self, cr, uid, ids, vals, context=None):
1849 if vals.get('type', False) and vals['type'] in ('none', 'code'):
1850 vals.update({'amount': 0.0})
1851 return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1853 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1854 journal_pool = self.pool.get('account.journal')
1856 if context and context.has_key('type'):
1857 if context.get('type') in ('out_invoice','out_refund'):
1858 args += [('type_tax_use','in',['sale','all'])]
1859 elif context.get('type') in ('in_invoice','in_refund'):
1860 args += [('type_tax_use','in',['purchase','all'])]
1862 if context and context.has_key('journal_id'):
1863 journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1864 if journal.type in ('sale', 'purchase'):
1865 args += [('type_tax_use','in',[journal.type,'all'])]
1867 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1869 def name_get(self, cr, uid, ids, context=None):
1873 for record in self.read(cr, uid, ids, ['description','name'], context=context):
1874 name = record['description'] and record['description'] or record['name']
1875 res.append((record['id'],name ))
1878 def _default_company(self, cr, uid, context=None):
1879 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1881 return user.company_id.id
1882 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1885 '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''',
1886 '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''',
1887 'applicable_type': 'true',
1892 'type_tax_use': 'all',
1898 'include_base_amount': False,
1899 'company_id': _default_company,
1903 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1905 obj_partener_address = self.pool.get('res.partner.address')
1907 if tax.applicable_type=='code':
1908 localdict = {'price_unit':price_unit, 'address':obj_partener_address.browse(cr, uid, address_id), 'product':product, 'partner':partner}
1909 exec tax.python_applicable in localdict
1910 if localdict.get('result', False):
1916 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1917 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1919 cur_price_unit=price_unit
1920 obj_partener_address = self.pool.get('res.partner.address')
1922 # we compute the amount for the current tax object and append it to the result
1923 data = {'id':tax.id,
1924 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1925 'account_collected_id':tax.account_collected_id.id,
1926 'account_paid_id':tax.account_paid_id.id,
1927 'base_code_id': tax.base_code_id.id,
1928 'ref_base_code_id': tax.ref_base_code_id.id,
1929 'sequence': tax.sequence,
1930 'base_sign': tax.base_sign,
1931 'tax_sign': tax.tax_sign,
1932 'ref_base_sign': tax.ref_base_sign,
1933 'ref_tax_sign': tax.ref_tax_sign,
1934 'price_unit': cur_price_unit,
1935 'tax_code_id': tax.tax_code_id.id,
1936 'ref_tax_code_id': tax.ref_tax_code_id.id,
1939 if tax.type=='percent':
1940 amount = cur_price_unit * tax.amount
1941 data['amount'] = amount
1943 elif tax.type=='fixed':
1944 data['amount'] = tax.amount
1945 data['tax_amount']=quantity
1946 # data['amount'] = quantity
1947 elif tax.type=='code':
1948 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1949 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1950 exec tax.python_compute in localdict
1951 amount = localdict['result']
1952 data['amount'] = amount
1953 elif tax.type=='balance':
1954 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1955 data['balance'] = cur_price_unit
1957 amount2 = data.get('amount', 0.0)
1959 if tax.child_depend:
1962 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1963 res.extend(child_tax)
1964 if tax.child_depend:
1966 for name in ('base','ref_base'):
1967 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1968 r[name+'_code_id'] = latest[name+'_code_id']
1969 r[name+'_sign'] = latest[name+'_sign']
1970 r['price_unit'] = latest['price_unit']
1971 latest[name+'_code_id'] = False
1972 for name in ('tax','ref_tax'):
1973 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1974 r[name+'_code_id'] = latest[name+'_code_id']
1975 r[name+'_sign'] = latest[name+'_sign']
1976 r['amount'] = data['amount']
1977 latest[name+'_code_id'] = False
1978 if tax.include_base_amount:
1979 cur_price_unit+=amount2
1982 def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1985 'total': 0.0, # Total without taxes
1986 'total_included: 0.0, # Total with taxes
1987 'taxes': [] # List of taxes, see compute for the format
1990 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1991 totalin = totalex = round(price_unit * quantity, precision)
1995 if tax.price_include:
1999 tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
2001 totalex -= r.get('amount', 0.0)
2004 totlex_qty = totalex/quantity
2007 tex = self._compute(cr, uid, tex, totlex_qty, quantity, address_id=address_id, product=product, partner=partner)
2009 totalin += r.get('amount', 0.0)
2012 'total_included': totalin,
2016 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
2017 logger = netsvc.Logger()
2018 logger.notifyChannel("warning", netsvc.LOG_WARNING,
2019 "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
2020 return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
2022 def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
2024 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2028 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2029 one tax for each tax id in IDS and their children
2031 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
2033 precision_pool = self.pool.get('decimal.precision')
2035 if r.get('balance',False):
2036 r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
2038 r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
2039 total += r['amount']
2042 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
2043 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
2044 obj_partener_address = self.pool.get('res.partner.address')
2047 cur_price_unit = price_unit
2049 tax_parent_tot = 0.0
2051 if (tax.type=='percent') and not tax.include_base_amount:
2052 tax_parent_tot += tax.amount
2055 if (tax.type=='fixed') and not tax.include_base_amount:
2056 cur_price_unit -= tax.amount
2059 if tax.type=='percent':
2060 if tax.include_base_amount:
2061 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
2063 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
2065 elif tax.type=='fixed':
2068 elif tax.type=='code':
2069 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
2070 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
2071 exec tax.python_compute_inv in localdict
2072 amount = localdict['result']
2073 elif tax.type=='balance':
2074 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2076 if tax.include_base_amount:
2077 cur_price_unit -= amount
2086 'account_collected_id': tax.account_collected_id.id,
2087 'account_paid_id': tax.account_paid_id.id,
2088 'base_code_id': tax.base_code_id.id,
2089 'ref_base_code_id': tax.ref_base_code_id.id,
2090 'sequence': tax.sequence,
2091 'base_sign': tax.base_sign,
2092 'tax_sign': tax.tax_sign,
2093 'ref_base_sign': tax.ref_base_sign,
2094 'ref_tax_sign': tax.ref_tax_sign,
2095 'price_unit': cur_price_unit,
2096 'tax_code_id': tax.tax_code_id.id,
2097 'ref_tax_code_id': tax.ref_tax_code_id.id,
2100 if tax.child_depend:
2104 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
2105 res.extend(parent_tax)
2110 total += r['amount']
2112 r['price_unit'] -= total
2116 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
2118 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2119 Price Unit is a VAT included price
2123 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2124 one tax for each tax id in IDS and their children
2126 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
2128 obj_precision = self.pool.get('decimal.precision')
2130 prec = obj_precision.precision_get(cr, uid, 'Account')
2131 if r.get('balance',False):
2132 r['amount'] = round(r['balance'] * quantity, prec) - total
2134 r['amount'] = round(r['amount'] * quantity, prec)
2135 total += r['amount']
2140 # ---------------------------------------------------------
2141 # Account Entries Models
2142 # ---------------------------------------------------------
2144 class account_model(osv.osv):
2145 _name = "account.model"
2146 _description = "Account Model"
2148 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2149 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2150 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2151 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2152 'legend': fields.text('Legend', readonly=True, size=100),
2156 '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'),
2158 def generate(self, cr, uid, ids, datas={}, context=None):
2161 account_move_obj = self.pool.get('account.move')
2162 account_move_line_obj = self.pool.get('account.move.line')
2163 pt_obj = self.pool.get('account.payment.term')
2168 if datas.get('date', False):
2169 context.update({'date': datas['date']})
2171 period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
2173 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
2174 period_id = period_id[0]
2176 move_date = context.get('date', time.strftime('%Y-%m-%d'))
2177 move_date = datetime.strptime(move_date,"%Y-%m-%d")
2178 for model in self.browse(cr, uid, ids, context=context):
2180 entry['name'] = model.name%{'year': move_date.strftime('%Y'), 'month': move_date.strftime('%m'), 'date': move_date.strftime('%Y-%m')}
2182 raise osv.except_osv(_('Wrong model !'), _('You have a wrong expression "%(...)s" in your model !'))
2183 move_id = account_move_obj.create(cr, uid, {
2184 'ref': entry['name'],
2185 'period_id': period_id,
2186 'journal_id': model.journal_id.id,
2187 'date': context.get('date',time.strftime('%Y-%m-%d'))
2189 move_ids.append(move_id)
2190 for line in model.lines_id:
2191 analytic_account_id = False
2192 if line.analytic_account_id:
2193 if not model.journal_id.analytic_journal_id:
2194 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2195 analytic_account_id = line.analytic_account_id.id
2198 'journal_id': model.journal_id.id,
2199 'period_id': period_id,
2200 'analytic_account_id': analytic_account_id
2203 date_maturity = context.get('date',time.strftime('%Y-%m-%d'))
2204 if line.date_maturity == 'partner':
2205 if not line.partner_id:
2206 raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2207 "\nPlease define partner on it!")%(line.name, model.name))
2208 if line.partner_id.property_payment_term:
2209 payment_term_id = line.partner_id.property_payment_term.id
2210 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2212 pterm_list = [l[0] for l in pterm_list]
2214 date_maturity = pterm_list[-1]
2218 'quantity': line.quantity,
2219 'debit': line.debit,
2220 'credit': line.credit,
2221 'account_id': line.account_id.id,
2223 'partner_id': line.partner_id.id,
2224 'date': context.get('date',time.strftime('%Y-%m-%d')),
2225 'date_maturity': date_maturity
2228 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
2229 account_move_line_obj.create(cr, uid, val, context=c)
2235 class account_model_line(osv.osv):
2236 _name = "account.model.line"
2237 _description = "Account Model Entries"
2239 'name': fields.char('Name', size=64, required=True),
2240 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones."),
2241 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries."),
2242 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2243 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2244 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2245 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2246 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2247 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2248 'currency_id': fields.many2one('res.currency', 'Currency'),
2249 'partner_id': fields.many2one('res.partner', 'Partner'),
2250 '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."),
2253 _sql_constraints = [
2254 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model, they must be positive!'),
2255 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model, they must be positive!'),
2257 account_model_line()
2259 # ---------------------------------------------------------
2260 # Account Subscription
2261 # ---------------------------------------------------------
2264 class account_subscription(osv.osv):
2265 _name = "account.subscription"
2266 _description = "Account Subscription"
2268 'name': fields.char('Name', size=64, required=True),
2269 'ref': fields.char('Reference', size=16),
2270 'model_id': fields.many2one('account.model', 'Model', required=True),
2271 'date_start': fields.date('Start Date', required=True),
2272 'period_total': fields.integer('Number of Periods', required=True),
2273 'period_nbr': fields.integer('Period', required=True),
2274 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2275 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
2276 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2279 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
2280 'period_type': 'month',
2285 def state_draft(self, cr, uid, ids, context=None):
2286 self.write(cr, uid, ids, {'state':'draft'})
2289 def check(self, cr, uid, ids, context=None):
2291 for sub in self.browse(cr, uid, ids, context=context):
2293 for line in sub.lines_id:
2294 if not line.move_id.id:
2298 todone.append(sub.id)
2300 self.write(cr, uid, todone, {'state':'done'})
2303 def remove_line(self, cr, uid, ids, context=None):
2305 for sub in self.browse(cr, uid, ids, context=context):
2306 for line in sub.lines_id:
2307 if not line.move_id.id:
2308 toremove.append(line.id)
2310 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2311 self.write(cr, uid, ids, {'state':'draft'})
2314 def compute(self, cr, uid, ids, context=None):
2315 for sub in self.browse(cr, uid, ids, context=context):
2317 for i in range(sub.period_total):
2318 self.pool.get('account.subscription.line').create(cr, uid, {
2320 'subscription_id': sub.id,
2322 if sub.period_type=='day':
2323 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2324 if sub.period_type=='month':
2325 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2326 if sub.period_type=='year':
2327 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2328 self.write(cr, uid, ids, {'state':'running'})
2331 account_subscription()
2333 class account_subscription_line(osv.osv):
2334 _name = "account.subscription.line"
2335 _description = "Account Subscription Line"
2337 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2338 'date': fields.date('Date', required=True),
2339 'move_id': fields.many2one('account.move', 'Entry'),
2342 def move_create(self, cr, uid, ids, context=None):
2345 obj_model = self.pool.get('account.model')
2346 for line in self.browse(cr, uid, ids, context=context):
2350 move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2351 tocheck[line.subscription_id.id] = True
2352 self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2353 all_moves.extend(move_ids)
2355 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2360 account_subscription_line()
2362 # ---------------------------------------------------------------
2363 # Account Templates: Account, Tax, Tax Code and chart. + Wizard
2364 # ---------------------------------------------------------------
2366 class account_tax_template(osv.osv):
2367 _name = 'account.tax.template'
2368 account_tax_template()
2370 class account_account_template(osv.osv):
2372 _name = "account.account.template"
2373 _description ='Templates for Accounts'
2376 'name': fields.char('Name', size=128, required=True, select=True),
2377 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2378 'code': fields.char('Code', size=64, select=1),
2379 'type': fields.selection([
2380 ('receivable','Receivable'),
2381 ('payable','Payable'),
2383 ('consolidation','Consolidation'),
2384 ('liquidity','Liquidity'),
2385 ('other','Regular'),
2386 ('closed','Closed'),
2387 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2388 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2389 "can have children accounts for multi-company consolidations, payable/receivable are for "\
2390 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2391 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2392 help="These types are defined according to your country. The type contains more information "\
2393 "about the account and its specificities."),
2394 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2395 'shortcut': fields.char('Shortcut', size=12),
2396 'note': fields.text('Note'),
2397 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2398 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2399 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2400 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2409 def _check_type(self, cr, uid, ids, context=None):
2412 accounts = self.browse(cr, uid, ids, context=context)
2413 for account in accounts:
2414 if account.parent_id and account.parent_id.type != 'view':
2418 _check_recursion = check_cycle
2420 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id']),
2421 (_check_type, 'Configuration Error!\nYou can not define children to an account with internal type different of "View"! ', ['type']),
2425 def name_get(self, cr, uid, ids, context=None):
2428 reads = self.read(cr, uid, ids, ['name','code'], context=context)
2430 for record in reads:
2431 name = record['name']
2433 name = record['code']+' '+name
2434 res.append((record['id'],name ))
2437 account_account_template()
2439 class account_add_tmpl_wizard(osv.osv_memory):
2440 """Add one more account from the template.
2442 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2443 _name = 'account.addtmpl.wizard'
2445 def _get_def_cparent(self, cr, uid, context=None):
2446 acc_obj = self.pool.get('account.account')
2447 tmpl_obj = self.pool.get('account.account.template')
2448 tids = tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2449 if not tids or not tids[0]['parent_id']:
2451 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2453 if not ptids or not ptids[0]['code']:
2454 raise osv.except_osv(_('Error !'), _('I can not locate a parent code for the template account!'))
2455 res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2456 return res and res[0] or False
2459 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2462 'cparent_id': _get_def_cparent,
2465 def action_create(self,cr,uid,ids,context=None):
2468 acc_obj = self.pool.get('account.account')
2469 tmpl_obj = self.pool.get('account.account.template')
2470 data = self.read(cr, uid, ids)
2471 company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2472 account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2474 'name': account_template.name,
2475 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2476 'code': account_template.code,
2477 'type': account_template.type,
2478 'user_type': account_template.user_type and account_template.user_type.id or False,
2479 'reconcile': account_template.reconcile,
2480 'shortcut': account_template.shortcut,
2481 'note': account_template.note,
2482 'parent_id': data[0]['cparent_id'],
2483 'company_id': company_id,
2485 acc_obj.create(cr, uid, vals)
2486 return {'type':'state', 'state': 'end' }
2488 def action_cancel(self, cr, uid, ids, context=None):
2489 return { 'type': 'state', 'state': 'end' }
2491 account_add_tmpl_wizard()
2493 class account_tax_code_template(osv.osv):
2495 _name = 'account.tax.code.template'
2496 _description = 'Tax Code Template'
2500 'name': fields.char('Tax Case Name', size=64, required=True),
2501 'code': fields.char('Case Code', size=64),
2502 'info': fields.text('Description'),
2503 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2504 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2505 'sign': fields.float('Sign For Parent', required=True),
2506 '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"),
2511 'notprintable': False,
2514 def name_get(self, cr, uid, ids, context=None):
2517 if isinstance(ids, (int, long)):
2519 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2520 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2523 _check_recursion = check_cycle
2525 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2527 _order = 'code,name'
2528 account_tax_code_template()
2531 class account_chart_template(osv.osv):
2532 _name="account.chart.template"
2533 _description= "Templates for Account Chart"
2536 'name': fields.char('Name', size=64, required=True),
2537 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
2538 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
2539 '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'),
2540 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
2541 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2542 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2543 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2544 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2545 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2546 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2547 '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'),
2548 'property_account_income_opening': fields.many2one('account.account.template','Opening Entries Income Account'),
2549 'property_account_expense_opening': fields.many2one('account.account.template','Opening Entries Expense Account'),
2552 account_chart_template()
2554 class account_tax_template(osv.osv):
2556 _name = 'account.tax.template'
2557 _description = 'Templates for Taxes'
2560 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2561 'name': fields.char('Tax Name', size=64, required=True),
2562 '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."),
2563 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2564 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True),
2565 '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."),
2566 '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."),
2567 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2568 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2569 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2570 '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."),
2571 'python_compute':fields.text('Python Code'),
2572 'python_compute_inv':fields.text('Python Code (reverse)'),
2573 'python_applicable':fields.text('Python Code'),
2576 # Fields used for the VAT declaration
2578 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2579 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2580 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2581 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2583 # Same fields for refund invoices
2585 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2586 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2587 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2588 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2589 '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."),
2590 'description': fields.char('Internal Name', size=32),
2591 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,),
2592 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
2595 def name_get(self, cr, uid, ids, context=None):
2599 for record in self.read(cr, uid, ids, ['description','name'], context=context):
2600 name = record['description'] and record['description'] or record['name']
2601 res.append((record['id'],name ))
2604 def _default_company(self, cr, uid, context=None):
2605 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2607 return user.company_id.id
2608 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2611 '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''',
2612 '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''',
2613 'applicable_type': 'true',
2621 'include_base_amount': False,
2622 'type_tax_use': 'all',
2627 account_tax_template()
2629 # Fiscal Position Templates
2631 class account_fiscal_position_template(osv.osv):
2632 _name = 'account.fiscal.position.template'
2633 _description = 'Template for Fiscal Position'
2636 'name': fields.char('Fiscal Position Template', size=64, required=True),
2637 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2638 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2639 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping'),
2640 'note': fields.text('Notes', translate=True),
2643 account_fiscal_position_template()
2645 class account_fiscal_position_tax_template(osv.osv):
2646 _name = 'account.fiscal.position.tax.template'
2647 _description = 'Template Tax Fiscal Position'
2648 _rec_name = 'position_id'
2651 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2652 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2653 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2656 account_fiscal_position_tax_template()
2658 class account_fiscal_position_account_template(osv.osv):
2659 _name = 'account.fiscal.position.account.template'
2660 _description = 'Template Account Fiscal Mapping'
2661 _rec_name = 'position_id'
2663 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2664 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2665 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2668 account_fiscal_position_account_template()
2670 # ---------------------------------------------------------
2671 # Account Financial Report
2672 # ---------------------------------------------------------
2674 class account_financial_report(osv.osv):
2675 _name = "account.financial.report"
2676 _description = "Account Report"
2678 def _get_level(self, cr, uid, ids, field_name, arg, context=None):
2680 for report in self.browse(cr, uid, ids, context=context):
2682 if report.parent_id:
2683 level = report.parent_id.level + 1
2684 res[report.id] = level
2687 def _get_children_by_order(self, cr, uid, ids, context=None):
2691 ids2 = self.search(cr, uid, [('parent_id', '=', id)], order='sequence ASC', context=context)
2692 res += self._get_children_by_order(cr, uid, ids2, context=context)
2695 def _get_balance(self, cr, uid, ids, name, args, context=None):
2698 for report in self.browse(cr, uid, ids, context=context):
2700 if report.id in res_all:
2701 balance = res_all[report.id]
2702 elif report.type == 'accounts':
2703 # it's the sum of balance of the linked accounts
2704 for a in report.account_ids:
2705 balance += a.balance
2706 elif report.type == 'account_report' and report.account_report_id:
2707 # it's the amount of the linked report
2708 res2 = self._get_balance(cr, uid, [report.account_report_id.id], 'balance', False, context=context)
2709 res_all.update(res2)
2710 for key, value in res2.items():
2712 elif report.type == 'sum':
2713 # it's the sum of balance of the children of this account.report
2714 #for child in report.children_ids:
2715 res2 = self._get_balance(cr, uid, [rec.id for rec in report.children_ids], 'balance', False, context=context)
2716 res_all.update(res2)
2717 for key, value in res2.items():
2719 res[report.id] = balance
2720 res_all[report.id] = balance
2724 'name': fields.char('Report Name', size=128, required=True, translate=True),
2725 'parent_id': fields.many2one('account.financial.report', 'Parent'),
2726 'children_ids': fields.one2many('account.financial.report', 'parent_id', 'Account Report'),
2727 'sequence': fields.integer('Sequence'),
2728 'note': fields.text('Notes'),
2729 'balance': fields.function(_get_balance, 'Balance'),
2730 'level': fields.function(_get_level, string='Level', store=True, type='integer'),
2731 'type': fields.selection([
2733 ('accounts','Accounts'),
2734 ('account_type','Account Type'),
2735 ('account_report','Report Value'),
2737 'account_ids': fields.many2many('account.account', 'account_account_financial_report', 'report_line_id', 'account_id', 'Accounts'),
2738 'display_detail': fields.boolean('Display details', help='Display every account with its balance instead of the sum.'),
2739 'account_report_id': fields.many2one('account.financial.report', 'Report Value'),
2740 'account_type_ids': fields.many2many('account.account.type', 'account_account_financial_report_type', 'report_id', 'account_type_id', 'Account Types'),
2747 account_financial_report()
2749 # ---------------------------------------------------------
2750 # Account generation from template wizards
2751 # ---------------------------------------------------------
2753 class wizard_multi_charts_accounts(osv.osv_memory):
2755 Create a new account chart for a company.
2758 * an account chart template
2759 * a number of digits for formatting code of non-view accounts
2760 * a list of bank accounts owned by the company
2762 * generates all accounts from the template and assigns them to the right company
2763 * generates all taxes and tax codes, changing account assignations
2764 * generates all accounting properties and assigns them correctly
2766 _name='wizard.multi.charts.accounts'
2767 _inherit = 'res.config'
2770 'company_id':fields.many2one('res.company', 'Company', required=True),
2771 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2772 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Cash and Banks', required=True),
2773 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2774 '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."),
2775 "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
2776 "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
2778 def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
2781 res['value']["sale_tax"] = False
2782 res['value']["purchase_tax"] = False
2783 if chart_template_id:
2784 # default tax is given by the lowesst 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
2785 sale_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2786 , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence, id desc")
2787 purchase_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2788 , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence, id desc")
2790 res['value']["sale_tax"] = sale_tax_ids and sale_tax_ids[0] or False
2791 res['value']["purchase_tax"] = purchase_tax_ids and purchase_tax_ids[0] or False
2794 def _get_purchase_tax(self, cr, uid, context=None):
2795 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2797 chart_template_id = ids[0]
2798 purchase_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2799 , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence")
2800 return purchase_tax_ids and purchase_tax_ids[0] or False
2803 def _get_sale_tax(self, cr, uid, context=None):
2804 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2806 chart_template_id = ids[0]
2807 sale_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2808 , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence")
2809 return sale_tax_ids and sale_tax_ids[0] or False
2812 def _get_chart(self, cr, uid, context=None):
2813 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2818 def _get_default_accounts(self, cr, uid, context=None):
2820 {'acc_name': _('Cash'),'account_type':'cash'}
2824 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, [uid], c)[0].company_id.id,
2825 'chart_template_id': _get_chart,
2826 'bank_accounts_id': _get_default_accounts,
2827 'sale_tax': _get_sale_tax,
2828 'purchase_tax': _get_purchase_tax,
2833 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2834 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)
2836 company_ids = self.pool.get('res.company').search(cr, uid, [], context=context)
2837 #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)
2838 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",))
2839 configured_cmp = [r[0] for r in cr.fetchall()]
2840 unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
2841 for field in res['fields']:
2842 if field == 'company_id':
2843 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
2844 res['fields'][field]['selection'] = [('', '')]
2845 if unconfigured_cmp:
2846 cmp_select = [(line.id, line.name) for line in self.pool.get('res.company').browse(cr, uid, unconfigured_cmp)]
2847 res['fields'][field]['selection'] = cmp_select
2850 def execute(self, cr, uid, ids, context=None):
2851 obj_multi = self.browse(cr, uid, ids[0])
2852 obj_acc = self.pool.get('account.account')
2853 obj_acc_tax = self.pool.get('account.tax')
2854 obj_journal = self.pool.get('account.journal')
2855 obj_acc_template = self.pool.get('account.account.template')
2856 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2857 obj_fiscal_position = self.pool.get('account.fiscal.position')
2858 obj_data = self.pool.get('ir.model.data')
2859 analytic_journal_obj = self.pool.get('account.analytic.journal')
2860 obj_tax_code = self.pool.get('account.tax.code')
2861 obj_tax_code_template = self.pool.get('account.tax.code.template')
2862 ir_values_obj = self.pool.get('ir.values')
2864 obj_acc_root = obj_multi.chart_template_id.account_root_id
2865 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2866 company_id = obj_multi.company_id.id
2869 acc_template_ref = {}
2870 tax_template_ref = {}
2871 tax_code_template_ref = {}
2874 #create all the tax code
2875 children_tax_code_template = obj_tax_code_template.search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2876 children_tax_code_template.sort()
2877 for tax_code_template in obj_tax_code_template.browse(cr, uid, children_tax_code_template, context=context):
2879 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2880 'code': tax_code_template.code,
2881 'info': tax_code_template.info,
2882 '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,
2883 'company_id': company_id,
2884 'sign': tax_code_template.sign,
2886 new_tax_code = obj_tax_code.create(cr, uid, vals)
2887 #recording the new tax code to do the mapping
2888 tax_code_template_ref[tax_code_template.id] = new_tax_code
2891 tax_template_to_tax = {}
2892 for tax in obj_multi.chart_template_id.tax_template_ids:
2896 'sequence': tax.sequence,
2897 'amount':tax.amount,
2899 'applicable_type': tax.applicable_type,
2900 'domain':tax.domain,
2901 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2902 'child_depend': tax.child_depend,
2903 'python_compute': tax.python_compute,
2904 'python_compute_inv': tax.python_compute_inv,
2905 'python_applicable': tax.python_applicable,
2906 '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,
2907 '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,
2908 'base_sign': tax.base_sign,
2909 'tax_sign': tax.tax_sign,
2910 '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,
2911 '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,
2912 'ref_base_sign': tax.ref_base_sign,
2913 'ref_tax_sign': tax.ref_tax_sign,
2914 'include_base_amount': tax.include_base_amount,
2915 'description':tax.description,
2916 'company_id': company_id,
2917 'type_tax_use': tax.type_tax_use,
2918 'price_include': tax.price_include
2920 new_tax = obj_acc_tax.create(cr, uid, vals_tax)
2921 tax_template_to_tax[tax.id] = new_tax
2922 #as the accounts have not been created yet, we have to wait before filling these fields
2923 todo_dict[new_tax] = {
2924 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2925 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2927 tax_template_ref[tax.id] = new_tax
2928 #deactivate the parent_store functionnality on account_account for rapidity purpose
2929 ctx = context and context.copy() or {}
2930 ctx['defer_parent_store_computation'] = True
2932 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2933 children_acc_template.sort()
2934 for account_template in obj_acc_template.browse(cr, uid, children_acc_template, context=context):
2936 for tax in account_template.tax_ids:
2937 tax_ids.append(tax_template_ref[tax.id])
2938 #create the account_account
2940 dig = obj_multi.code_digits
2941 code_main = account_template.code and len(account_template.code) or 0
2942 code_acc = account_template.code or ''
2943 if code_main>0 and code_main<=dig and account_template.type != 'view':
2944 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2946 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2947 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2949 'type': account_template.type,
2950 'user_type': account_template.user_type and account_template.user_type.id or False,
2951 'reconcile': account_template.reconcile,
2952 'shortcut': account_template.shortcut,
2953 'note': account_template.note,
2954 '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,
2955 'tax_ids': [(6,0,tax_ids)],
2956 'company_id': company_id,
2958 new_account = obj_acc.create(cr, uid, vals, context=ctx)
2959 acc_template_ref[account_template.id] = new_account
2962 #reactivate the parent_store functionnality on account_account
2963 obj_acc._parent_store_compute(cr)
2965 for key,value in todo_dict.items():
2966 if value['account_collected_id'] or value['account_paid_id']:
2967 obj_acc_tax.write(cr, uid, [key], {
2968 'account_collected_id': acc_template_ref.get(value['account_collected_id'], False),
2969 'account_paid_id': acc_template_ref.get(value['account_paid_id'], False),
2973 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_sp_journal_view')])
2974 data = obj_data.browse(cr, uid, data_id[0], context=context)
2975 view_id = data.res_id
2978 analytical_sale_ids = analytic_journal_obj.search(cr,uid,[('type','=','sale')])
2979 analytical_journal_sale = analytical_sale_ids and analytical_sale_ids[0] or False
2982 'name': _('Sales Journal'),
2986 'company_id': company_id,
2987 'analytic_journal_id': analytical_journal_sale,
2990 if obj_multi.chart_template_id.property_account_receivable:
2991 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2992 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2994 obj_journal.create(cr,uid,vals_journal)
2997 analytical_purchase_ids = analytic_journal_obj.search(cr,uid,[('type','=','purchase')])
2998 analytical_journal_purchase = analytical_purchase_ids and analytical_purchase_ids[0] or False
3001 'name': _('Purchase Journal'),
3005 'company_id': company_id,
3006 'analytic_journal_id': analytical_journal_purchase,
3009 if obj_multi.chart_template_id.property_account_payable:
3010 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
3011 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
3012 obj_journal.create(cr,uid,vals_journal)
3014 # Creating Journals Sales Refund and Purchase Refund
3015 data_id = obj_data.search(cr, uid, [('model', '=', 'account.journal.view'), ('name', '=', 'account_sp_refund_journal_view')], context=context)
3016 data = obj_data.browse(cr, uid, data_id[0], context=context)
3017 view_id = data.res_id
3019 #Sales Refund Journal
3021 'name': _('Sales Refund Journal'),
3022 'type': 'sale_refund',
3025 'analytic_journal_id': analytical_journal_sale,
3026 'company_id': company_id
3029 if obj_multi.chart_template_id.property_account_receivable:
3030 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
3031 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
3033 obj_journal.create(cr, uid, vals_journal, context=context)
3035 # Purchase Refund Journal
3037 'name': _('Purchase Refund Journal'),
3038 'type': 'purchase_refund',
3041 'analytic_journal_id': analytical_journal_purchase,
3042 'company_id': company_id
3045 if obj_multi.chart_template_id.property_account_payable:
3046 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
3047 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
3049 obj_journal.create(cr, uid, vals_journal, context=context)
3051 # Miscellaneous Journal
3052 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_view')])
3053 data = obj_data.browse(cr, uid, data_id[0], context=context)
3054 view_id = data.res_id
3056 analytical_miscellaneous_ids = analytic_journal_obj.search(cr, uid, [('type', '=', 'situation')], context=context)
3057 analytical_journal_miscellaneous = analytical_miscellaneous_ids and analytical_miscellaneous_ids[0] or False
3060 'name': _('Miscellaneous Journal'),
3064 'analytic_journal_id': analytical_journal_miscellaneous,
3065 'company_id': company_id
3068 obj_journal.create(cr, uid, vals_journal, context=context)
3070 # Opening Entries Journal
3071 if obj_multi.chart_template_id.property_account_income_opening and obj_multi.chart_template_id.property_account_expense_opening:
3073 'name': _('Opening Entries Journal'),
3074 'type': 'situation',
3077 'company_id': company_id,
3078 'centralisation': True,
3079 'default_credit_account_id': acc_template_ref[obj_multi.chart_template_id.property_account_income_opening.id],
3080 'default_debit_account_id': acc_template_ref[obj_multi.chart_template_id.property_account_expense_opening.id]
3082 obj_journal.create(cr, uid, vals_journal, context=context)
3085 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')])
3086 data = obj_data.browse(cr, uid, data_id[0], context=context)
3087 view_id_cash = data.res_id
3089 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view_multi')])
3090 data = obj_data.browse(cr, uid, data_id[0], context=context)
3091 view_id_cur = data.res_id
3092 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
3096 for line in obj_multi.bank_accounts_id:
3097 #create the account_account for this bank journal
3099 dig = obj_multi.code_digits
3100 if not ref_acc_bank.code:
3101 raise osv.except_osv(_('Configuration Error !'), _('The bank account defined on the selected chart of account hasn\'t a code.'))
3103 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)), '0')) + str(current_num)
3104 ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)])
3111 'currency_id': line.currency_id and line.currency_id.id or False,
3113 'type': 'liquidity',
3114 'user_type': account_template.user_type and account_template.user_type.id or False,
3116 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
3117 'company_id': company_id,
3119 acc_cash_id = obj_acc.create(cr,uid,vals)
3121 #create the bank journal
3123 'name': vals['name'],
3124 'code': _('BNK') + str(current_num),
3125 'type': line.account_type == 'cash' and 'cash' or 'bank',
3126 'company_id': company_id,
3127 'analytic_journal_id': False,
3128 'currency_id': False,
3130 if line.currency_id:
3131 vals_journal['view_id'] = view_id_cur
3132 vals_journal['currency'] = line.currency_id.id
3134 vals_journal['view_id'] = view_id_cash
3135 vals_journal['default_credit_account_id'] = acc_cash_id
3136 vals_journal['default_debit_account_id'] = acc_cash_id
3137 obj_journal.create(cr, uid, vals_journal)
3141 #create the properties
3142 property_obj = self.pool.get('ir.property')
3143 fields_obj = self.pool.get('ir.model.fields')
3146 ('property_account_receivable','res.partner','account.account'),
3147 ('property_account_payable','res.partner','account.account'),
3148 ('property_account_expense_categ','product.category','account.account'),
3149 ('property_account_income_categ','product.category','account.account'),
3150 ('property_account_expense','product.template','account.account'),
3151 ('property_account_income','product.template','account.account'),
3152 ('property_reserve_and_surplus_account','res.company','account.account')
3154 for record in todo_list:
3156 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
3157 account = getattr(obj_multi.chart_template_id, record[0])
3158 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
3161 'company_id': company_id,
3162 'fields_id': field[0],
3163 'value': account and 'account.account,' + str(acc_template_ref[account.id]) or False,
3167 #the property exist: modify it
3168 property_obj.write(cr, uid, r, vals)
3170 #create the property
3171 property_obj.create(cr, uid, vals)
3173 fp_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)])
3176 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
3177 obj_ac_fp = self.pool.get('account.fiscal.position.account')
3179 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids, context=context):
3182 'company_id': company_id,
3183 'name': position.name,
3185 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
3187 for tax in position.tax_ids:
3189 'tax_src_id': tax_template_ref[tax.tax_src_id.id],
3190 'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
3191 'position_id': new_fp,
3193 obj_tax_fp.create(cr, uid, vals_tax)
3195 for acc in position.account_ids:
3197 'account_src_id': acc_template_ref[acc.account_src_id.id],
3198 'account_dest_id': acc_template_ref[acc.account_dest_id.id],
3199 'position_id': new_fp,
3201 obj_ac_fp.create(cr, uid, vals_acc)
3203 if obj_multi.sale_tax:
3204 ir_values_obj.set(cr, uid, key='default', key2=False, name="taxes_id", company=obj_multi.company_id.id,
3205 models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.sale_tax.id]])
3206 if obj_multi.purchase_tax:
3207 ir_values_obj.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=obj_multi.company_id.id,
3208 models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.purchase_tax.id]])
3210 wizard_multi_charts_accounts()
3212 class account_bank_accounts_wizard(osv.osv_memory):
3213 _name='account.bank.accounts.wizard'
3216 'acc_name': fields.char('Account Name.', size=64, required=True),
3217 'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
3218 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
3219 'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
3222 account_bank_accounts_wizard()
3224 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: