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 openerp import SUPERUSER_ID
30 from openerp import tools
31 from openerp.osv import fields, osv, expression
32 from openerp.tools.translate import _
33 from openerp.tools.float_utils import float_round
35 import openerp.addons.decimal_precision as dp
37 _logger = logging.getLogger(__name__)
39 def check_cycle(self, cr, uid, ids, context=None):
40 """ climbs the ``self._table.parent_id`` chains for 100 levels or
41 until it can't find any more parent(s)
43 Returns true if it runs out of parents (no cycle), false if
44 it can recurse 100 times without ending all chains
48 cr.execute('SELECT DISTINCT parent_id '\
49 'FROM '+self._table+' '\
51 'AND parent_id IS NOT NULL',(tuple(ids),))
52 ids = map(itemgetter(0), cr.fetchall())
58 class account_payment_term(osv.osv):
59 _name = "account.payment.term"
60 _description = "Payment Term"
62 'name': fields.char('Payment Term', size=64, translate=True, required=True),
63 '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."),
64 'note': fields.text('Description', translate=True),
65 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
72 def compute(self, cr, uid, id, value, date_ref=False, context=None):
74 date_ref = datetime.now().strftime('%Y-%m-%d')
75 pt = self.browse(cr, uid, id, context=context)
78 obj_precision = self.pool.get('decimal.precision')
79 prec = obj_precision.precision_get(cr, uid, 'Account')
80 for line in pt.line_ids:
81 if line.value == 'fixed':
82 amt = round(line.value_amount, prec)
83 elif line.value == 'procent':
84 amt = round(value * line.value_amount, prec)
85 elif line.value == 'balance':
86 amt = round(amount, prec)
88 next_date = (datetime.strptime(date_ref, '%Y-%m-%d') + relativedelta(days=line.days))
90 next_first_date = next_date + relativedelta(day=1,months=1) #Getting 1st of next month
91 next_date = next_first_date + relativedelta(days=line.days2)
93 next_date += relativedelta(day=line.days2, months=1)
94 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
97 amount = reduce(lambda x,y: x+y[1], result, 0.0)
98 dist = round(value-amount, prec)
100 result.append( (time.strftime('%Y-%m-%d'), dist) )
103 class account_payment_term_line(osv.osv):
104 _name = "account.payment.term.line"
105 _description = "Payment Term Line"
107 'value': fields.selection([('procent', 'Percent'),
108 ('balance', 'Balance'),
109 ('fixed', 'Fixed Amount')], 'Computation',
110 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 treated."""),
112 'value_amount': fields.float('Amount To Pay', digits_compute=dp.get_precision('Payment Term'), help="For percent enter a ratio between 0-1."),
113 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
114 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
115 '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)."),
116 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True, ondelete='cascade'),
123 _order = "value desc,days"
125 def _check_percent(self, cr, uid, ids, context=None):
126 obj = self.browse(cr, uid, ids[0], context=context)
127 if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
132 (_check_percent, 'Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2%.', ['value_amount']),
136 class account_account_type(osv.osv):
137 _name = "account.account.type"
138 _description = "Account Type"
140 def _get_financial_report_ref(self, cr, uid, context=None):
141 obj_data = self.pool.get('ir.model.data')
142 obj_financial_report = self.pool.get('account.financial.report')
143 financial_report_ref = {}
144 for key, financial_report in [
145 ('asset','account_financial_report_assets0'),
146 ('liability','account_financial_report_liability0'),
147 ('income','account_financial_report_income0'),
148 ('expense','account_financial_report_expense0'),
151 financial_report_ref[key] = obj_financial_report.browse(cr, uid,
152 obj_data.get_object_reference(cr, uid, 'account', financial_report)[1],
156 return financial_report_ref
158 def _get_current_report_type(self, cr, uid, ids, name, arg, context=None):
160 financial_report_ref = self._get_financial_report_ref(cr, uid, context=context)
161 for record in self.browse(cr, uid, ids, context=context):
162 res[record.id] = 'none'
163 for key, financial_report in financial_report_ref.items():
164 list_ids = [x.id for x in financial_report.account_type_ids]
165 if record.id in list_ids:
169 def _save_report_type(self, cr, uid, account_type_id, field_name, field_value, arg, context=None):
170 field_value = field_value or 'none'
171 obj_financial_report = self.pool.get('account.financial.report')
172 #unlink if it exists somewhere in the financial reports related to BS or PL
173 financial_report_ref = self._get_financial_report_ref(cr, uid, context=context)
174 for key, financial_report in financial_report_ref.items():
175 list_ids = [x.id for x in financial_report.account_type_ids]
176 if account_type_id in list_ids:
177 obj_financial_report.write(cr, uid, [financial_report.id], {'account_type_ids': [(3, account_type_id)]})
178 #write it in the good place
179 if field_value != 'none':
180 return obj_financial_report.write(cr, uid, [financial_report_ref[field_value].id], {'account_type_ids': [(4, account_type_id)]})
183 'name': fields.char('Account Type', size=64, required=True, translate=True),
184 'code': fields.char('Code', size=32, required=True, select=True),
185 '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.
187 'None' means that nothing will be done.
188 'Balance' will generally be used for cash accounts.
189 'Detail' will copy each existing journal item of the previous year, even the reconciled ones.
190 'Unreconciled' will copy only the journal items that were unreconciled on the first day of the new fiscal year."""),
191 'report_type': fields.function(_get_current_report_type, fnct_inv=_save_report_type, type='selection', string='P&L / BS Category', store=True,
192 selection= [('none','/'),
193 ('income', _('Profit & Loss (Income account)')),
194 ('expense', _('Profit & Loss (Expense account)')),
195 ('asset', _('Balance Sheet (Asset account)')),
196 ('liability', _('Balance Sheet (Liability account)'))], help="This field is used to generate legal reports: profit and loss, balance sheet.", required=True),
197 'note': fields.text('Description'),
200 'close_method': 'none',
201 'report_type': 'none',
206 def _code_get(self, cr, uid, context=None):
207 acc_type_obj = self.pool.get('account.account.type')
208 ids = acc_type_obj.search(cr, uid, [])
209 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context=context)
210 return [(r['code'], r['name']) for r in res]
212 #----------------------------------------------------------
214 #----------------------------------------------------------
216 class account_tax(osv.osv):
217 _name = 'account.tax'
219 class account_account(osv.osv):
220 _order = "parent_left"
221 _parent_order = "code"
222 _name = "account.account"
223 _description = "Account"
226 def search(self, cr, uid, args, offset=0, limit=None, order=None,
227 context=None, count=False):
232 while pos < len(args):
234 if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
235 args[pos] = ('code', '=like', tools.ustr(args[pos][2].replace('%', ''))+'%')
236 if args[pos][0] == 'journal_id':
240 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2], context=context)
241 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
242 args[pos] = ('type','not in',('consolidation','view'))
244 ids3 = map(lambda x: x.id, jour.type_control_ids)
245 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
246 ids1 += map(lambda x: x.id, jour.account_control_ids)
247 args[pos] = ('id', 'in', ids1)
250 if context and context.has_key('consolidate_children'): #add consolidated children of accounts
251 ids = super(account_account, self).search(cr, uid, args, offset, limit,
252 order, context=context, count=count)
253 for consolidate_child in self.browse(cr, uid, context['account_id'], context=context).child_consol_ids:
254 ids.append(consolidate_child.id)
257 return super(account_account, self).search(cr, uid, args, offset, limit,
258 order, context=context, count=count)
260 def _get_children_and_consol(self, cr, uid, ids, context=None):
261 #this function search for all the children and all consolidated children (recursively) of the given account ids
262 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
264 for rec in self.browse(cr, uid, ids2, context=context):
265 for child in rec.child_consol_ids:
266 ids3.append(child.id)
268 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
271 def __compute(self, cr, uid, ids, field_names, arg=None, context=None,
272 query='', query_params=()):
273 """ compute the balance, debit and/or credit for the provided
277 `field_names`: the fields to compute (a list of any of
278 'balance', 'debit' and 'credit')
279 `arg`: unused fields.function stuff
280 `query`: additional query filter (as a string)
281 `query_params`: parameters for the provided query string
282 (__compute will handle their escaping) as a
286 'balance': "COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance",
287 'debit': "COALESCE(SUM(l.debit), 0) as debit",
288 'credit': "COALESCE(SUM(l.credit), 0) as credit",
289 # by convention, foreign_balance is 0 when the account has no secondary currency, because the amounts may be in different currencies
290 'foreign_balance': "(SELECT CASE WHEN currency_id IS NULL THEN 0 ELSE COALESCE(SUM(l.amount_currency), 0) END FROM account_account WHERE id IN (l.account_id)) as foreign_balance",
292 #get all the necessary accounts
293 children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
294 #compute for each account the balance/debit/credit from the move lines
297 null_result = dict((fn, 0.0) for fn in field_names)
298 if children_and_consolidated:
299 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
303 wheres.append(query.strip())
304 if aml_query.strip():
305 wheres.append(aml_query.strip())
306 filters = " AND ".join(wheres)
307 # IN might not work ideally in case there are too many
308 # children_and_consolidated, in that case join on a
310 # SELECT l.account_id as id FROM account_move_line l
311 # INNER JOIN (VALUES (id1), (id2), (id3), ...) AS tmp (id)
312 # ON l.account_id = tmp.id
313 # or make _get_children_and_consol return a query and join on that
314 request = ("SELECT l.account_id as id, " +\
315 ', '.join(mapping.values()) +
316 " FROM account_move_line l" \
317 " WHERE l.account_id IN %s " \
319 " GROUP BY l.account_id")
320 params = (tuple(children_and_consolidated),) + query_params
321 cr.execute(request, params)
323 for row in cr.dictfetchall():
324 accounts[row['id']] = row
326 # consolidate accounts with direct children
327 children_and_consolidated.reverse()
328 brs = list(self.browse(cr, uid, children_and_consolidated, context=context))
330 currency_obj = self.pool.get('res.currency')
334 # for child in current.child_id:
335 # if child.id not in sums:
336 # can_compute = False
338 # brs.insert(0, brs.pop(brs.index(child)))
340 # brs.insert(0, child)
342 for fn in field_names:
343 sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
344 for child in current.child_id:
345 if child.company_id.currency_id.id == current.company_id.currency_id.id:
346 sums[current.id][fn] += sums[child.id][fn]
348 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)
350 # as we have to relay on values computed before this is calculated separately than previous fields
351 if current.currency_id and current.exchange_rate and \
352 ('adjusted_balance' in field_names or 'unrealized_gain_loss' in field_names):
353 # Computing Adjusted Balance and Unrealized Gains and losses
354 # Adjusted Balance = Foreign Balance / Exchange Rate
355 # Unrealized Gains and losses = Adjusted Balance - Balance
356 adj_bal = sums[current.id].get('foreign_balance', 0.0) / current.exchange_rate
357 sums[current.id].update({'adjusted_balance': adj_bal, 'unrealized_gain_loss': adj_bal - sums[current.id].get('balance', 0.0)})
360 res[id] = sums.get(id, null_result)
363 res[id] = null_result
366 def _get_company_currency(self, cr, uid, ids, field_name, arg, context=None):
368 for rec in self.browse(cr, uid, ids, context=context):
369 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.symbol)
372 def _get_child_ids(self, cr, uid, ids, field_name, arg, context=None):
374 for record in self.browse(cr, uid, ids, context=context):
375 if record.child_parent_ids:
376 result[record.id] = [x.id for x in record.child_parent_ids]
378 result[record.id] = []
380 if record.child_consol_ids:
381 for acc in record.child_consol_ids:
382 if acc.id not in result[record.id]:
383 result[record.id].append(acc.id)
387 def _get_level(self, cr, uid, ids, field_name, arg, context=None):
389 for account in self.browse(cr, uid, ids, context=context):
390 #we may not know the level of the parent at the time of computation, so we
391 # can't simply do res[account.id] = account.parent_id.level + 1
393 parent = account.parent_id
396 parent = parent.parent_id
397 res[account.id] = level
400 def _set_credit_debit(self, cr, uid, account_id, name, value, arg, context=None):
401 if context.get('config_invisible', True):
404 account = self.browse(cr, uid, account_id, context=context)
405 diff = value - getattr(account,name)
409 journal_obj = self.pool.get('account.journal')
410 jids = journal_obj.search(cr, uid, [('type','=','situation'),('centralisation','=',1),('company_id','=',account.company_id.id)], context=context)
412 raise osv.except_osv(_('Error!'),_("You need an Opening journal with centralisation checked to set the initial balance."))
414 period_obj = self.pool.get('account.period')
415 pids = period_obj.search(cr, uid, [('special','=',True),('company_id','=',account.company_id.id)], context=context)
417 raise osv.except_osv(_('Error!'),_("There is no opening/closing period defined, please create one to set the initial balance."))
419 move_obj = self.pool.get('account.move.line')
420 move_id = move_obj.search(cr, uid, [
421 ('journal_id','=',jids[0]),
422 ('period_id','=',pids[0]),
423 ('account_id','=', account_id),
425 ('name','=', _('Opening Balance'))
428 move = move_obj.browse(cr, uid, move_id[0], context=context)
429 move_obj.write(cr, uid, move_id[0], {
430 name: diff+getattr(move,name)
434 raise osv.except_osv(_('Error!'),_("Unable to adapt the initial balance (negative value)."))
435 nameinv = (name=='credit' and 'debit') or 'credit'
436 move_id = move_obj.create(cr, uid, {
437 'name': _('Opening Balance'),
438 'account_id': account_id,
439 'journal_id': jids[0],
440 'period_id': pids[0],
447 'name': fields.char('Name', size=256, required=True, select=True),
448 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
449 'code': fields.char('Code', size=64, required=True, select=1),
450 'type': fields.selection([
452 ('other', 'Regular'),
453 ('receivable', 'Receivable'),
454 ('payable', 'Payable'),
455 ('liquidity','Liquidity'),
456 ('consolidation', 'Consolidation'),
457 ('closed', 'Closed'),
458 ], 'Internal Type', required=True, help="The 'Internal Type' is used for features available on "\
459 "different types of accounts: view can not have journal items, consolidation are accounts that "\
460 "can have children accounts for multi-company consolidations, payable/receivable are for "\
461 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
462 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
463 help="Account Type is used for information purpose, to generate "
464 "country-specific legal reports, and set the rules to close a fiscal year and generate opening entries."),
465 'financial_report_ids': fields.many2many('account.financial.report', 'account_account_financial_report', 'account_id', 'report_line_id', 'Financial Reports'),
466 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
467 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
468 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
469 'child_id': fields.function(_get_child_ids, type='many2many', relation="account.account", string="Child Accounts"),
470 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Balance', multi='balance'),
471 'credit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Credit', multi='balance'),
472 'debit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'),
473 'foreign_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Foreign Balance', multi='balance',
474 help="Total amount (in Secondary currency) for transactions held in secondary currency for this account."),
475 'adjusted_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Adjusted Balance', multi='balance',
476 help="Total amount (in Company currency) for transactions held in secondary currency for this account."),
477 'unrealized_gain_loss': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Unrealized Gain or Loss', multi='balance',
478 help="Value of Loss or Gain due to changes in exchange rate when doing multi-currency transactions."),
479 'reconcile': fields.boolean('Allow Reconciliation', help="Check this box if this account allows reconciliation of journal items."),
480 'exchange_rate': fields.related('currency_id', 'rate', type='float', string='Exchange Rate', digits=(12,6)),
481 'shortcut': fields.char('Shortcut', size=12),
482 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
483 'account_id', 'tax_id', 'Default Taxes'),
484 'note': fields.text('Internal Notes'),
485 'company_currency_id': fields.function(_get_company_currency, type='many2one', relation='res.currency', string='Company Currency'),
486 'company_id': fields.many2one('res.company', 'Company', required=True),
487 '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."),
489 'parent_left': fields.integer('Parent Left', select=1),
490 'parent_right': fields.integer('Parent Right', select=1),
491 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
493 'This will select how the current currency rate for outgoing transactions is computed. '\
494 'In most countries the legal method is "average" but only a few software systems are able to '\
495 'manage this. So if you import from another software system you may have to use the rate at date. ' \
496 'Incoming transactions always use the rate at date.', \
498 'level': fields.function(_get_level, string='Level', method=True, type='integer',
500 'account.account': (_get_children_and_consol, ['level', 'parent_id'], 10),
508 'currency_mode': 'current',
509 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
512 def _check_recursion(self, cr, uid, ids, context=None):
513 obj_self = self.browse(cr, uid, ids[0], context=context)
514 p_id = obj_self.parent_id and obj_self.parent_id.id
515 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
518 cr.execute('SELECT DISTINCT child_id '\
519 'FROM account_account_consol_rel '\
520 'WHERE parent_id IN %s', (tuple(ids),))
521 child_ids = map(itemgetter(0), cr.fetchall())
523 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
526 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
527 if p_id and (p_id in s_ids):
533 def _check_type(self, cr, uid, ids, context=None):
536 accounts = self.browse(cr, uid, ids, context=context)
537 for account in accounts:
538 if account.child_id and account.type not in ('view', 'consolidation'):
542 def _check_account_type(self, cr, uid, ids, context=None):
543 for account in self.browse(cr, uid, ids, context=context):
544 if account.type in ('receivable', 'payable') and account.user_type.close_method != 'unreconciled':
548 def _check_company_account(self, cr, uid, ids, context=None):
549 for account in self.browse(cr, uid, ids, context=context):
550 if account.parent_id:
551 if account.company_id != account.parent_id.company_id:
556 (_check_recursion, 'Error!\nYou cannot create recursive accounts.', ['parent_id']),
557 (_check_type, 'Configuration Error!\nYou cannot define children to an account with internal type different of "View".', ['type']),
558 (_check_account_type, 'Configuration Error!\nYou cannot select an account type with a deferral method different of "Unreconciled" for accounts with internal type "Payable/Receivable".', ['user_type','type']),
559 (_check_company_account, 'Error!\nYou cannot create an account which has parent account of different company.', ['parent_id']),
562 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
564 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
570 if name and str(name).startswith('partner:'):
571 part_id = int(name.split(':')[1])
572 part = self.pool.get('res.partner').browse(cr, user, part_id, context=context)
573 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
575 if name and str(name).startswith('type:'):
576 type = name.split(':')[1]
577 args += [('type', '=', type)]
582 if operator not in expression.NEGATIVE_TERM_OPERATORS:
583 ids = self.search(cr, user, ['|', ('code', '=like', name+"%"), '|', ('shortcut', '=', name), ('name', operator, name)]+args, limit=limit)
584 if not ids and len(name.split()) >= 2:
585 #Separating code and name of account for searching
586 operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
587 ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
589 ids = self.search(cr, user, ['&','!', ('code', '=like', name+"%"), ('name', operator, name)]+args, limit=limit)
590 # as negation want to restric, do if already have results
591 if ids and len(name.split()) >= 2:
592 operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
593 ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2), ('id', 'in', ids)]+ args, limit=limit)
595 ids = self.search(cr, user, args, context=context, limit=limit)
596 return self.name_get(cr, user, ids, context=context)
598 def name_get(self, cr, uid, ids, context=None):
601 if isinstance(ids, (int, long)):
603 reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
606 name = record['name']
608 name = record['code'] + ' ' + name
609 res.append((record['id'], name))
612 def copy(self, cr, uid, id, default=None, context=None, done_list=None, local=False):
613 default = {} if default is None else default.copy()
614 if done_list is None:
616 account = self.browse(cr, uid, id, context=context)
618 default.update(code=_("%s (copy)") % (account['code'] or ''))
621 if account.id in done_list:
623 done_list.append(account.id)
625 for child in account.child_id:
626 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
628 new_child_ids.append(child_ids)
629 default['child_parent_ids'] = [(6, 0, new_child_ids)]
631 default['child_parent_ids'] = False
632 return super(account_account, self).copy(cr, uid, id, default, context=context)
634 def _check_moves(self, cr, uid, ids, method, context=None):
635 line_obj = self.pool.get('account.move.line')
636 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
638 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
639 if method == 'write':
640 raise osv.except_osv(_('Error!'), _('You cannot deactivate an account that contains journal items.'))
641 elif method == 'unlink':
642 raise osv.except_osv(_('Error!'), _('You cannot remove an account that contains journal items.'))
643 #Checking whether the account is set as a property to any Partner or not
644 value = 'account.account,' + str(ids[0])
645 partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
647 raise osv.except_osv(_('Warning!'), _('You cannot remove/deactivate an account which is set on a customer or supplier.'))
650 def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
651 restricted_groups = ['consolidation','view']
652 line_obj = self.pool.get('account.move.line')
653 for account in self.browse(cr, uid, ids, context=context):
654 old_type = account.type
655 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
656 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
657 #Check for 'Closed' type
658 if old_type == 'closed' and new_type !='closed':
659 raise osv.except_osv(_('Warning!'), _("You cannot change the type of account from 'Closed' to any other type as it contains journal items!"))
660 # Forbid to change an account type for restricted_groups as it contains journal items (or if one of its children does)
661 if (new_type in restricted_groups):
662 raise osv.except_osv(_('Warning!'), _("You cannot change the type of account to '%s' type as it contains journal items!") % (new_type,))
666 # For legal reason (forbiden to modify journal entries which belongs to a closed fy or period), Forbid to modify
667 # the code of an account if journal entries have been already posted on this account. This cannot be simply
668 # 'configurable' since it can lead to a lack of confidence in OpenERP and this is what we want to change.
669 def _check_allow_code_change(self, cr, uid, ids, context=None):
670 line_obj = self.pool.get('account.move.line')
671 for account in self.browse(cr, uid, ids, context=context):
672 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])], context=context)
673 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)], context=context):
674 raise osv.except_osv(_('Warning !'), _("You cannot change the code of account which contains journal items!"))
677 def write(self, cr, uid, ids, vals, context=None):
682 if isinstance(ids, (int, long)):
685 # Dont allow changing the company_id when account_move_line already exist
686 if 'company_id' in vals:
687 move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
689 # Allow the write if the value is the same
690 for i in [i['company_id'][0] for i in self.read(cr,uid,ids,['company_id'])]:
691 if vals['company_id']!=i:
692 raise osv.except_osv(_('Warning!'), _('You cannot change the owner company of an account that already contains journal items.'))
693 if 'active' in vals and not vals['active']:
694 self._check_moves(cr, uid, ids, "write", context=context)
695 if 'type' in vals.keys():
696 self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
697 if 'code' in vals.keys():
698 self._check_allow_code_change(cr, uid, ids, context=context)
699 return super(account_account, self).write(cr, uid, ids, vals, context=context)
701 def unlink(self, cr, uid, ids, context=None):
702 self._check_moves(cr, uid, ids, "unlink", context=context)
703 return super(account_account, self).unlink(cr, uid, ids, context=context)
706 class account_journal(osv.osv):
707 _name = "account.journal"
708 _description = "Journal"
710 'with_last_closing_balance' : fields.boolean('Opening With Last Closing Balance'),
711 'name': fields.char('Journal Name', size=64, required=True),
712 'code': fields.char('Code', size=5, required=True, help="The code will be displayed on reports."),
713 'type': fields.selection([('sale', 'Sale'),('sale_refund','Sale Refund'), ('purchase', 'Purchase'), ('purchase_refund','Purchase Refund'), ('cash', 'Cash'), ('bank', 'Bank and Checks'), ('general', 'General'), ('situation', 'Opening/Closing Situation')], 'Type', size=32, required=True,
714 help="Select 'Sale' for customer invoices journals."\
715 " Select 'Purchase' for supplier invoices journals."\
716 " Select 'Cash' or 'Bank' for journals that are used in customer or supplier payments."\
717 " Select 'General' for miscellaneous operations journals."\
718 " Select 'Opening/Closing Situation' for entries generated for new fiscal years."),
719 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
720 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
721 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
722 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
723 'centralisation': fields.boolean('Centralized 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."),
724 '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"),
725 '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."),
726 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="This field contains the information related to the numbering of the journal entries of this journal.", required=True),
727 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
728 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
729 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
730 'entry_posted': fields.boolean('Autopost Created Moves', help='Check this box to automatically post entries of this journal. Note that legally, some entries may be automatically posted when the source document is validated (Invoices), whatever the status of this field.'),
731 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
732 'allow_date':fields.boolean('Check Date in Period', help= 'If checked, the entry won\'t be created if the entry date is not included into the selected period'),
733 'profit_account_id' : fields.many2one('account.account', 'Profit Account'),
734 'loss_account_id' : fields.many2one('account.account', 'Loss Account'),
735 'internal_account_id' : fields.many2one('account.account', 'Internal Transfers Account', select=1),
736 'cash_control' : fields.boolean('Cash Control', help='If you want the journal should be control at opening/closing, check this option'),
740 'cash_control' : False,
741 'with_last_closing_balance' : False,
742 'user_id': lambda self, cr, uid, context: uid,
743 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
746 ('code_company_uniq', 'unique (code, company_id)', 'The code of the journal must be unique per company !'),
747 ('name_company_uniq', 'unique (name, company_id)', 'The name of the journal must be unique per company !'),
752 def _check_currency(self, cr, uid, ids, context=None):
753 for journal in self.browse(cr, uid, ids, context=context):
755 if journal.default_credit_account_id and not journal.default_credit_account_id.currency_id.id == journal.currency.id:
757 if journal.default_debit_account_id and not journal.default_debit_account_id.currency_id.id == journal.currency.id:
762 (_check_currency, 'Configuration error!\nThe currency chosen should be shared by the default accounts too.', ['currency','default_debit_account_id','default_credit_account_id']),
765 def copy(self, cr, uid, id, default=None, context=None, done_list=None, local=False):
766 default = {} if default is None else default.copy()
767 if done_list is None:
769 journal = self.browse(cr, uid, id, context=context)
771 code=_("%s (copy)") % (journal['code'] or ''),
772 name=_("%s (copy)") % (journal['name'] or ''),
774 return super(account_journal, self).copy(cr, uid, id, default, context=context)
776 def write(self, cr, uid, ids, vals, context=None):
779 if isinstance(ids, (int, long)):
781 for journal in self.browse(cr, uid, ids, context=context):
782 if 'company_id' in vals and journal.company_id.id != vals['company_id']:
783 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
785 raise osv.except_osv(_('Warning!'), _('This journal already contains items, therefore you cannot modify its company field.'))
786 return super(account_journal, self).write(cr, uid, ids, vals, context=context)
788 def create_sequence(self, cr, uid, vals, context=None):
789 """ Create new no_gap entry sequence for every new Joural
791 # in account.journal code is actually the prefix of the sequence
792 # whereas ir.sequence code is a key to lookup global sequences.
793 prefix = vals['code'].upper()
796 'name': vals['name'],
797 'implementation':'no_gap',
798 'prefix': prefix + "/%(year)s/",
800 'number_increment': 1
802 if 'company_id' in vals:
803 seq['company_id'] = vals['company_id']
804 return self.pool.get('ir.sequence').create(cr, uid, seq)
806 def create(self, cr, uid, vals, context=None):
807 if not 'sequence_id' in vals or not vals['sequence_id']:
808 # if we have the right to create a journal, we should be able to
809 # create it's sequence.
810 vals.update({'sequence_id': self.create_sequence(cr, SUPERUSER_ID, vals, context)})
811 return super(account_journal, self).create(cr, uid, vals, context)
813 def name_get(self, cr, user, ids, context=None):
815 Returns a list of tupples containing id, name.
816 result format: {[(id, name), (id, name), ...]}
818 @param cr: A database cursor
819 @param user: ID of the user currently logged in
820 @param ids: list of ids for which name should be read
821 @param context: context arguments, like lang, time zone
823 @return: Returns a list of tupples containing id, name
827 if isinstance(ids, (int, long)):
829 result = self.browse(cr, user, ids, context=context)
833 currency = rs.currency
835 currency = rs.company_id.currency_id
836 name = "%s (%s)" % (rs.name, currency.name)
837 res += [(rs.id, name)]
840 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
846 if context.get('journal_type', False):
847 args += [('type','=',context.get('journal_type'))]
849 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
851 ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
853 return self.name_get(cr, user, ids, context=context)
856 class account_fiscalyear(osv.osv):
857 _name = "account.fiscalyear"
858 _description = "Fiscal Year"
860 'name': fields.char('Fiscal Year', size=64, required=True),
861 'code': fields.char('Code', size=6, required=True),
862 'company_id': fields.many2one('res.company', 'Company', required=True),
863 'date_start': fields.date('Start Date', required=True),
864 'date_stop': fields.date('End Date', required=True),
865 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
866 'state': fields.selection([('draft','Open'), ('done','Closed')], 'Status', readonly=True),
870 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
872 _order = "date_start, id"
875 def _check_duration(self, cr, uid, ids, context=None):
876 obj_fy = self.browse(cr, uid, ids[0], context=context)
877 if obj_fy.date_stop < obj_fy.date_start:
882 (_check_duration, 'Error!\nThe start date of a fiscal year must precede its end date.', ['date_start','date_stop'])
885 def create_period3(self, cr, uid, ids, context=None):
886 return self.create_period(cr, uid, ids, context, 3)
888 def create_period(self, cr, uid, ids, context=None, interval=1):
889 period_obj = self.pool.get('account.period')
890 for fy in self.browse(cr, uid, ids, context=context):
891 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
892 period_obj.create(cr, uid, {
893 'name': "%s %s" % (_('Opening Period'), ds.strftime('%Y')),
894 'code': ds.strftime('00/%Y'),
898 'fiscalyear_id': fy.id,
900 while ds.strftime('%Y-%m-%d') < fy.date_stop:
901 de = ds + relativedelta(months=interval, days=-1)
903 if de.strftime('%Y-%m-%d') > fy.date_stop:
904 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
906 period_obj.create(cr, uid, {
907 'name': ds.strftime('%m/%Y'),
908 'code': ds.strftime('%m/%Y'),
909 'date_start': ds.strftime('%Y-%m-%d'),
910 'date_stop': de.strftime('%Y-%m-%d'),
911 'fiscalyear_id': fy.id,
913 ds = ds + relativedelta(months=interval)
916 def find(self, cr, uid, dt=None, exception=True, context=None):
917 res = self.finds(cr, uid, dt, exception, context=context)
918 return res and res[0] or False
920 def finds(self, cr, uid, dt=None, exception=True, context=None):
921 if context is None: context = {}
923 dt = fields.date.context_today(self,cr,uid,context=context)
924 args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
925 if context.get('company_id', False):
926 company_id = context['company_id']
928 company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
929 args.append(('company_id', '=', company_id))
930 ids = self.search(cr, uid, args, context=context)
933 raise osv.except_osv(_('Error!'), _('There is no fiscal year defined for this date.\nPlease create one from the configuration of the accounting menu.'))
938 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
945 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
947 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
948 return self.name_get(cr, user, ids, context=context)
951 class account_period(osv.osv):
952 _name = "account.period"
953 _description = "Account period"
955 'name': fields.char('Period Name', size=64, required=True),
956 'code': fields.char('Code', size=12),
957 'special': fields.boolean('Opening/Closing Period', size=12,
958 help="These periods can overlap."),
959 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
960 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
961 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
962 'state': fields.selection([('draft','Open'), ('done','Closed')], 'Status', readonly=True,
963 help='When monthly periods are created. The status is \'Draft\'. At the end of monthly period it is in \'Done\' status.'),
964 'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
969 _order = "date_start, special desc"
971 ('name_company_uniq', 'unique(name, company_id)', 'The name of the period must be unique per company!'),
974 def _check_duration(self,cr,uid,ids,context=None):
975 obj_period = self.browse(cr, uid, ids[0], context=context)
976 if obj_period.date_stop < obj_period.date_start:
980 def _check_year_limit(self,cr,uid,ids,context=None):
981 for obj_period in self.browse(cr, uid, ids, context=context):
982 if obj_period.special:
985 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
986 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
987 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
988 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
991 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
992 for period in self.browse(cr, uid, pids):
993 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
998 (_check_duration, 'Error!\nThe duration of the Period(s) is/are invalid.', ['date_stop']),
999 (_check_year_limit, 'Error!\nThe period is invalid. Either some periods are overlapping or the period\'s dates are not matching the scope of the fiscal year.', ['date_stop'])
1002 def next(self, cr, uid, period, step, context=None):
1003 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
1008 def find(self, cr, uid, dt=None, context=None):
1009 if context is None: context = {}
1011 dt = fields.date.context_today(self, cr, uid, context=context)
1012 args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
1013 if context.get('company_id', False):
1014 args.append(('company_id', '=', context['company_id']))
1016 company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
1017 args.append(('company_id', '=', company_id))
1019 if context.get('account_period_prefer_normal', True):
1020 # look for non-special periods first, and fallback to all if no result is found
1021 result = self.search(cr, uid, args + [('special', '=', False)], context=context)
1023 result = self.search(cr, uid, args, context=context)
1025 model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_fiscalyear')
1026 msg = _('There is no period defined for this date: %s.\nPlease, go to Configuration/Periods and configure a fiscal year.') % dt
1027 raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel'))
1031 def action_draft(self, cr, uid, ids, *args):
1033 for period in self.browse(cr, uid, ids):
1034 if period.fiscalyear_id.state == 'done':
1035 raise osv.except_osv(_('Warning!'), _('You can not re-open a period which belongs to closed fiscal year'))
1036 cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),))
1037 cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),))
1040 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1047 ids = self.search(cr, user,
1048 [('code', 'ilike', name)] + args,
1052 ids = self.search(cr, user,
1053 [('name', operator, name)] + args,
1056 return self.name_get(cr, user, ids, context=context)
1058 def write(self, cr, uid, ids, vals, context=None):
1059 if 'company_id' in vals:
1060 move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
1062 raise osv.except_osv(_('Warning!'), _('This journal already contains items for this period, therefore you cannot modify its company field.'))
1063 return super(account_period, self).write(cr, uid, ids, vals, context=context)
1065 def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
1066 if period_from_id == period_to_id:
1067 return [period_from_id]
1068 period_from = self.browse(cr, uid, period_from_id)
1069 period_date_start = period_from.date_start
1070 company1_id = period_from.company_id.id
1071 period_to = self.browse(cr, uid, period_to_id)
1072 period_date_stop = period_to.date_stop
1073 company2_id = period_to.company_id.id
1074 if company1_id != company2_id:
1075 raise osv.except_osv(_('Error!'), _('You should choose the periods that belong to the same company.'))
1076 if period_date_start > period_date_stop:
1077 raise osv.except_osv(_('Error!'), _('Start period should precede then end period.'))
1079 # /!\ We do not include a criterion on the company_id field below, to allow producing consolidated reports
1080 # on multiple companies. It will only work when start/end periods are selected and no fiscal year is chosen.
1082 #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).
1083 if period_from.special:
1084 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop)])
1085 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('special', '=', False)])
1088 class account_journal_period(osv.osv):
1089 _name = "account.journal.period"
1090 _description = "Journal Period"
1092 def _icon_get(self, cr, uid, ids, field_name, arg=None, context=None):
1093 result = {}.fromkeys(ids, 'STOCK_NEW')
1094 for r in self.read(cr, uid, ids, ['state']):
1096 'draft': 'STOCK_NEW',
1097 'printed': 'STOCK_PRINT_PREVIEW',
1098 'done': 'STOCK_DIALOG_AUTHENTICATION',
1099 }.get(r['state'], 'STOCK_NEW')
1103 'name': fields.char('Journal-Period Name', size=64, required=True),
1104 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
1105 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
1106 'icon': fields.function(_icon_get, string='Icon', type='char', size=32),
1107 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the journal period without removing it."),
1108 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'Status', required=True, readonly=True,
1109 help='When journal period is created. The status is \'Draft\'. If a report is printed it comes to \'Printed\' status. When all transactions are done, it comes in \'Done\' status.'),
1110 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
1111 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
1114 def _check(self, cr, uid, ids, context=None):
1115 for obj in self.browse(cr, uid, ids, context=context):
1116 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))
1119 raise osv.except_osv(_('Error!'), _('You cannot modify/delete a journal with entries for this period.'))
1122 def write(self, cr, uid, ids, vals, context=None):
1123 self._check(cr, uid, ids, context=context)
1124 return super(account_journal_period, self).write(cr, uid, ids, vals, context=context)
1126 def create(self, cr, uid, vals, context=None):
1127 period_id = vals.get('period_id',False)
1129 period = self.pool.get('account.period').browse(cr, uid, period_id, context=context)
1130 vals['state']=period.state
1131 return super(account_journal_period, self).create(cr, uid, vals, context)
1133 def unlink(self, cr, uid, ids, context=None):
1134 self._check(cr, uid, ids, context=context)
1135 return super(account_journal_period, self).unlink(cr, uid, ids, context=context)
1141 _order = "period_id"
1144 class account_fiscalyear(osv.osv):
1145 _inherit = "account.fiscalyear"
1146 _description = "Fiscal Year"
1148 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1151 def copy(self, cr, uid, id, default=None, context=None):
1152 default = {} if default is None else default.copy()
1155 'end_journal_period_id': False
1157 return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1159 #----------------------------------------------------------
1161 #----------------------------------------------------------
1162 class account_move(osv.osv):
1163 _name = "account.move"
1164 _description = "Account Entry"
1167 def account_move_prepare(self, cr, uid, journal_id, date=False, ref='', company_id=False, context=None):
1169 Prepares and returns a dictionary of values, ready to be passed to create() based on the parameters received.
1172 date = fields.date.today()
1173 period_obj = self.pool.get('account.period')
1175 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1176 company_id = user.company_id.id
1179 #put the company in context to find the good period
1180 ctx = context.copy()
1181 ctx.update({'company_id': company_id})
1183 'journal_id': journal_id,
1185 'period_id': period_obj.find(cr, uid, date, context=ctx)[0],
1187 'company_id': company_id,
1190 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1192 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1193 result format: {[(id, name), (id, name), ...]}
1195 @param cr: A database cursor
1196 @param user: ID of the user currently logged in
1197 @param name: name to search
1198 @param args: other arguments
1199 @param operator: default operator is 'ilike', it can be changed
1200 @param context: context arguments, like lang, time zone
1201 @param limit: Returns first 'n' ids of complete result, default is 80.
1203 @return: Returns a list of tuples containing id and name
1210 ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1212 if not ids and name and type(name) == int:
1213 ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1216 ids += self.search(cr, user, args, limit=limit, context=context)
1218 return self.name_get(cr, user, ids, context=context)
1220 def name_get(self, cursor, user, ids, context=None):
1221 if isinstance(ids, (int, long)):
1226 data_move = self.pool.get('account.move').browse(cursor, user, ids, context=context)
1227 for move in data_move:
1228 if move.state=='draft':
1229 name = '*' + str(move.id)
1232 res.append((move.id, name))
1235 def _get_period(self, cr, uid, context=None):
1236 ctx = dict(context or {})
1237 period_ids = self.pool.get('account.period').find(cr, uid, context=ctx)
1238 return period_ids[0]
1240 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1241 if not ids: return {}
1242 cr.execute( 'SELECT move_id, SUM(debit) '\
1243 'FROM account_move_line '\
1244 'WHERE move_id IN %s '\
1245 'GROUP BY move_id', (tuple(ids),))
1246 result = dict(cr.fetchall())
1248 result.setdefault(id, 0.0)
1251 def _search_amount(self, cr, uid, obj, name, args, context):
1255 if isinstance(cond[2],(list,tuple)):
1256 if cond[1] in ['in','not in']:
1257 amount = tuple(cond[2])
1261 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1264 cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1265 res_ids = set(id[0] for id in cr.fetchall())
1266 ids = ids and (ids & res_ids) or res_ids
1268 return [('id', 'in', tuple(ids))]
1269 return [('id', '=', '0')]
1271 def _get_move_from_lines(self, cr, uid, ids, context=None):
1272 line_obj = self.pool.get('account.move.line')
1273 return [line.move_id.id for line in line_obj.browse(cr, uid, ids, context=context)]
1276 'name': fields.char('Number', size=64, required=True),
1277 'ref': fields.char('Reference', size=64),
1278 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1279 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1280 'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'Status', required=True, readonly=True,
1281 help='All manually created new journal entries are usually in the status \'Unposted\', but you can set the option to skip that status on the related journal. In that case, they will behave as journal entries automatically created by the system on document validation (invoices, bank statements...) and will be created in \'Posted\' status.'),
1282 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1283 '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.'),
1284 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store={
1285 _name: (lambda self, cr,uid,ids,c: ids, ['line_id'], 10),
1286 'account.move.line': (_get_move_from_lines, ['partner_id'],10)
1288 'amount': fields.function(_amount_compute, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1289 'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}, select=True),
1290 'narration':fields.text('Internal Note'),
1291 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1292 'balance': fields.float('balance', digits_compute=dp.get_precision('Account'), help="This is a field only used for internal purpose and shouldn't be displayed"),
1298 'period_id': _get_period,
1299 'date': fields.date.context_today,
1300 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1303 def _check_centralisation(self, cursor, user, ids, context=None):
1304 for move in self.browse(cursor, user, ids, context=context):
1305 if move.journal_id.centralisation:
1306 move_ids = self.search(cursor, user, [
1307 ('period_id', '=', move.period_id.id),
1308 ('journal_id', '=', move.journal_id.id),
1310 if len(move_ids) > 1:
1315 (_check_centralisation,
1316 'You cannot create more than one move per period on a centralized journal.',
1320 def post(self, cr, uid, ids, context=None):
1323 invoice = context.get('invoice', False)
1324 valid_moves = self.validate(cr, uid, ids, context)
1327 raise osv.except_osv(_('Error!'), _('You cannot validate a non-balanced entry.\nMake sure you have configured payment terms properly.\nThe latest payment term line should be of the "Balance" type.'))
1328 obj_sequence = self.pool.get('ir.sequence')
1329 for move in self.browse(cr, uid, valid_moves, context=context):
1332 journal = move.journal_id
1334 if invoice and invoice.internal_number:
1335 new_name = invoice.internal_number
1337 if journal.sequence_id:
1338 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1339 new_name = obj_sequence.next_by_id(cr, uid, journal.sequence_id.id, c)
1341 raise osv.except_osv(_('Error!'), _('Please define a sequence on the journal.'))
1344 self.write(cr, uid, [move.id], {'name':new_name})
1346 cr.execute('UPDATE account_move '\
1349 ('posted', tuple(valid_moves),))
1352 def button_validate(self, cursor, user, ids, context=None):
1353 for move in self.browse(cursor, user, ids, context=context):
1354 # check that all accounts have the same topmost ancestor
1356 for line in move.line_id:
1357 account = line.account_id
1358 top_account = account
1359 while top_account.parent_id:
1360 top_account = top_account.parent_id
1362 top_common = top_account
1363 elif top_account.id != top_common.id:
1364 raise osv.except_osv(_('Error!'),
1365 _('You cannot validate this journal entry because account "%s" does not belong to chart of accounts "%s".') % (account.name, top_common.name))
1366 return self.post(cursor, user, ids, context=context)
1368 def button_cancel(self, cr, uid, ids, context=None):
1369 for line in self.browse(cr, uid, ids, context=context):
1370 if not line.journal_id.update_posted:
1371 raise osv.except_osv(_('Error!'), _('You cannot modify a posted entry of this journal.\nFirst you should set the journal to allow cancelling entries.'))
1373 cr.execute('UPDATE account_move '\
1375 'WHERE id IN %s', ('draft', tuple(ids),))
1378 def write(self, cr, uid, ids, vals, context=None):
1382 c['novalidate'] = True
1383 result = super(account_move, self).write(cr, uid, ids, vals, c)
1384 self.validate(cr, uid, ids, context=context)
1388 # TODO: Check if period is closed !
1390 def create(self, cr, uid, vals, context=None):
1393 if 'line_id' in vals and context.get('copy'):
1394 for l in vals['line_id']:
1397 'reconcile_id':False,
1398 'reconcile_partial_id':False,
1399 'analytic_lines':False,
1403 'account_tax_id':False,
1404 'statement_id': False,
1407 if 'journal_id' in vals and vals.get('journal_id', False):
1408 for l in vals['line_id']:
1410 l[2]['journal_id'] = vals['journal_id']
1411 context['journal_id'] = vals['journal_id']
1412 if 'period_id' in vals:
1413 for l in vals['line_id']:
1415 l[2]['period_id'] = vals['period_id']
1416 context['period_id'] = vals['period_id']
1418 default_period = self._get_period(cr, uid, context)
1419 for l in vals['line_id']:
1421 l[2]['period_id'] = default_period
1422 context['period_id'] = default_period
1424 if vals.get('line_id', False):
1426 c['novalidate'] = True
1427 c['period_id'] = vals['period_id'] if 'period_id' in vals else self._get_period(cr, uid, context)
1428 c['journal_id'] = vals['journal_id']
1429 if 'date' in vals: c['date'] = vals['date']
1430 result = super(account_move, self).create(cr, uid, vals, c)
1431 tmp = self.validate(cr, uid, [result], context)
1432 journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'], context)
1433 if journal.entry_posted and tmp:
1434 self.button_validate(cr,uid, [result], context)
1436 result = super(account_move, self).create(cr, uid, vals, context)
1439 def copy(self, cr, uid, id, default=None, context=None):
1440 default = {} if default is None else default.copy()
1441 context = {} if context is None else context.copy()
1450 return super(account_move, self).copy(cr, uid, id, default, context)
1452 def unlink(self, cr, uid, ids, context=None, check=True):
1455 if isinstance(ids, (int, long)):
1458 obj_move_line = self.pool.get('account.move.line')
1459 for move in self.browse(cr, uid, ids, context=context):
1460 if move['state'] != 'draft':
1461 raise osv.except_osv(_('User Error!'),
1462 _('You cannot delete a posted journal entry "%s".') % \
1464 for line in move.line_id:
1466 raise osv.except_osv(_('User Error!'),
1467 _("Move cannot be deleted if linked to an invoice. (Invoice: %s - Move ID:%s)") % \
1468 (line.invoice.number,move.name))
1469 line_ids = map(lambda x: x.id, move.line_id)
1470 context['journal_id'] = move.journal_id.id
1471 context['period_id'] = move.period_id.id
1472 obj_move_line._update_check(cr, uid, line_ids, context)
1473 obj_move_line.unlink(cr, uid, line_ids, context=context)
1474 toremove.append(move.id)
1475 result = super(account_move, self).unlink(cr, uid, toremove, context)
1478 def _compute_balance(self, cr, uid, id, context=None):
1479 move = self.browse(cr, uid, id, context=context)
1481 for line in move.line_id:
1482 amount+= (line.debit - line.credit)
1485 def _centralise(self, cr, uid, move, mode, context=None):
1486 assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1487 currency_obj = self.pool.get('res.currency')
1492 account_id = move.journal_id.default_debit_account_id.id
1495 raise osv.except_osv(_('User Error!'),
1496 _('There is no default debit account defined \n' \
1497 'on journal "%s".') % move.journal_id.name)
1499 account_id = move.journal_id.default_credit_account_id.id
1502 raise osv.except_osv(_('User Error!'),
1503 _('There is no default credit account defined \n' \
1504 'on journal "%s".') % move.journal_id.name)
1506 # find the first line of this move with the current mode
1507 # or create it if it doesn't exist
1508 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1513 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1514 line_id = self.pool.get('account.move.line').create(cr, uid, {
1515 'name': _(mode.capitalize()+' Centralisation'),
1516 'centralisation': mode,
1517 'partner_id': False,
1518 'account_id': account_id,
1520 'journal_id': move.journal_id.id,
1521 'period_id': move.period_id.id,
1522 'date': move.period_id.date_stop,
1527 # find the first line of this move with the other mode
1528 # so that we can exclude it from our calculation
1529 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1536 cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1537 result = cr.fetchone()[0] or 0.0
1538 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1540 #adjust also the amount in currency if needed
1541 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,))
1542 for row in cr.dictfetchall():
1543 currency_id = currency_obj.browse(cr, uid, row['currency_id'], context=context)
1544 if not currency_obj.is_zero(cr, uid, currency_id, row['amount_currency']):
1545 amount_currency = row['amount_currency'] * -1
1546 account_id = amount_currency > 0 and move.journal_id.default_debit_account_id.id or move.journal_id.default_credit_account_id.id
1547 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']))
1550 cr.execute('update account_move_line set amount_currency=%s , account_id=%s where id=%s', (amount_currency, account_id, res[0]))
1552 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1553 line_id = self.pool.get('account.move.line').create(cr, uid, {
1554 'name': _('Currency Adjustment'),
1555 'centralisation': 'currency',
1556 'partner_id': False,
1557 'account_id': account_id,
1559 'journal_id': move.journal_id.id,
1560 'period_id': move.period_id.id,
1561 'date': move.period_id.date_stop,
1564 'currency_id': row['currency_id'],
1565 'amount_currency': amount_currency,
1571 # Validate a balanced move. If it is a centralised journal, create a move.
1573 def validate(self, cr, uid, ids, context=None):
1574 if context and ('__last_update' in context):
1575 del context['__last_update']
1577 valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1578 obj_analytic_line = self.pool.get('account.analytic.line')
1579 obj_move_line = self.pool.get('account.move.line')
1580 for move in self.browse(cr, uid, ids, context):
1581 journal = move.journal_id
1586 for line in move.line_id:
1587 amount += line.debit - line.credit
1588 line_ids.append(line.id)
1589 if line.state=='draft':
1590 line_draft_ids.append(line.id)
1593 company_id = line.account_id.company_id.id
1594 if not company_id == line.account_id.company_id.id:
1595 raise osv.except_osv(_('Error!'), _("Cannot create moves for different companies."))
1597 if line.account_id.currency_id and line.currency_id:
1598 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):
1599 raise osv.except_osv(_('Error!'), _("""Cannot create move with currency different from ..""") % (line.account_id.code, line.account_id.name))
1601 if abs(amount) < 10 ** -4:
1602 # If the move is balanced
1603 # Add to the list of valid moves
1604 # (analytic lines will be created later for valid moves)
1605 valid_moves.append(move)
1607 # Check whether the move lines are confirmed
1609 if not line_draft_ids:
1611 # Update the move lines (set them as valid)
1613 obj_move_line.write(cr, uid, line_draft_ids, {
1615 }, context, check=False)
1620 if journal.type in ('purchase','sale'):
1621 for line in move.line_id:
1623 key = (line.account_id.id, line.tax_code_id.id)
1625 code = account2[key][0]
1626 amount = account2[key][1] * (line.debit + line.credit)
1627 elif line.account_id.id in account:
1628 code = account[line.account_id.id][0]
1629 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1630 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1631 obj_move_line.write(cr, uid, [line.id], {
1632 'tax_code_id': code,
1633 'tax_amount': amount
1634 }, context, check=False)
1635 elif journal.centralisation:
1636 # If the move is not balanced, it must be centralised...
1638 # Add to the list of valid moves
1639 # (analytic lines will be created later for valid moves)
1640 valid_moves.append(move)
1643 # Update the move lines (set them as valid)
1645 self._centralise(cr, uid, move, 'debit', context=context)
1646 self._centralise(cr, uid, move, 'credit', context=context)
1647 obj_move_line.write(cr, uid, line_draft_ids, {
1649 }, context, check=False)
1651 # We can't validate it (it's unbalanced)
1652 # Setting the lines as draft
1653 not_draft_line_ids = list(set(line_ids) - set(line_draft_ids))
1654 if not_draft_line_ids:
1655 obj_move_line.write(cr, uid, not_draft_line_ids, {
1657 }, context, check=False)
1658 # Create analytic lines for the valid moves
1659 for record in valid_moves:
1660 obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1662 valid_moves = [move.id for move in valid_moves]
1663 return len(valid_moves) > 0 and valid_moves or False
1666 class account_move_reconcile(osv.osv):
1667 _name = "account.move.reconcile"
1668 _description = "Account Reconciliation"
1670 'name': fields.char('Name', size=64, required=True),
1671 'type': fields.char('Type', size=16, required=True),
1672 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1673 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1674 'create_date': fields.date('Creation date', readonly=True),
1675 'opening_reconciliation': fields.boolean('Opening Entries Reconciliation', help="Is this reconciliation produced by the opening of a new fiscal year ?."),
1678 'name': lambda self,cr,uid,ctx=None: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile', context=ctx) or '/',
1681 # You cannot unlink a reconciliation if it is a opening_reconciliation one,
1682 # you should use the generate opening entries wizard for that
1683 def unlink(self, cr, uid, ids, context=None):
1684 for move_rec in self.browse(cr, uid, ids, context=context):
1685 if move_rec.opening_reconciliation:
1686 raise osv.except_osv(_('Error!'), _('You cannot unreconcile journal items if they has been generated by the \
1687 opening/closing fiscal year process.'))
1688 return super(account_move_reconcile, self).unlink(cr, uid, ids, context=context)
1690 # Look in the line_id and line_partial_ids to ensure the partner is the same or empty
1691 # on all lines. We allow that only for opening/closing period
1692 def _check_same_partner(self, cr, uid, ids, context=None):
1693 for reconcile in self.browse(cr, uid, ids, context=context):
1695 if not reconcile.opening_reconciliation:
1696 if reconcile.line_id:
1697 first_partner = reconcile.line_id[0].partner_id.id
1698 move_lines = reconcile.line_id
1699 elif reconcile.line_partial_ids:
1700 first_partner = reconcile.line_partial_ids[0].partner_id.id
1701 move_lines = reconcile.line_partial_ids
1702 if any([(line.account_id.type in ('receivable', 'payable') and line.partner_id.id != first_partner) for line in move_lines]):
1707 (_check_same_partner, 'You can only reconcile journal items with the same partner.', ['line_id']),
1710 def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None):
1712 for rec in self.browse(cr, uid, ids, context=context):
1713 for line in rec.line_partial_ids:
1714 if line.account_id.currency_id:
1715 total += line.amount_currency
1717 total += (line.debit or 0.0) - (line.credit or 0.0)
1719 self.pool.get('account.move.line').write(cr, uid,
1720 map(lambda x: x.id, rec.line_partial_ids),
1721 {'reconcile_id': rec.id }
1725 def name_get(self, cr, uid, ids, context=None):
1729 for r in self.browse(cr, uid, ids, context=context):
1730 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1732 name = '%s (%.2f)' % (r.name, total)
1733 result.append((r.id,name))
1735 result.append((r.id,r.name))
1739 #----------------------------------------------------------
1741 #----------------------------------------------------------
1744 child_depend: la taxe depend des taxes filles
1746 class account_tax_code(osv.osv):
1748 A code for the tax object.
1750 This code is used for some tax declarations.
1752 def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1753 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1754 if context.get('based_on', 'invoices') == 'payments':
1755 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1756 FROM account_move_line AS line, \
1757 account_move AS move \
1758 LEFT JOIN account_invoice invoice ON \
1759 (invoice.move_id = move.id) \
1760 WHERE line.tax_code_id IN %s '+where+' \
1761 AND move.id = line.move_id \
1762 AND ((invoice.state = \'paid\') \
1763 OR (invoice.id IS NULL)) \
1764 GROUP BY line.tax_code_id',
1765 (parent_ids,) + where_params)
1767 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1768 FROM account_move_line AS line, \
1769 account_move AS move \
1770 WHERE line.tax_code_id IN %s '+where+' \
1771 AND move.id = line.move_id \
1772 GROUP BY line.tax_code_id',
1773 (parent_ids,) + where_params)
1774 res=dict(cr.fetchall())
1775 obj_precision = self.pool.get('decimal.precision')
1777 for record in self.browse(cr, uid, ids, context=context):
1778 def _rec_get(record):
1779 amount = res.get(record.id, 0.0)
1780 for rec in record.child_ids:
1781 amount += _rec_get(rec) * rec.sign
1783 res2[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1786 def _sum_year(self, cr, uid, ids, name, args, context=None):
1789 move_state = ('posted', )
1790 if context.get('state', 'all') == 'all':
1791 move_state = ('draft', 'posted', )
1792 if context.get('fiscalyear_id', False):
1793 fiscalyear_id = [context['fiscalyear_id']]
1795 fiscalyear_id = self.pool.get('account.fiscalyear').finds(cr, uid, exception=False)
1800 for fy in fiscalyear_id:
1801 pids += map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fy).period_ids)
1803 where = ' AND line.period_id IN %s AND move.state IN %s '
1804 where_params = (tuple(pids), move_state)
1805 return self._sum(cr, uid, ids, name, args, context,
1806 where=where, where_params=where_params)
1808 def _sum_period(self, cr, uid, ids, name, args, context):
1811 move_state = ('posted', )
1812 if context.get('state', False) == 'all':
1813 move_state = ('draft', 'posted', )
1814 if context.get('period_id', False):
1815 period_id = context['period_id']
1817 period_id = self.pool.get('account.period').find(cr, uid, context=context)
1819 return dict.fromkeys(ids, 0.0)
1820 period_id = period_id[0]
1821 return self._sum(cr, uid, ids, name, args, context,
1822 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1824 _name = 'account.tax.code'
1825 _description = 'Tax Code'
1828 'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1829 'code': fields.char('Case Code', size=64),
1830 'info': fields.text('Description'),
1831 'sum': fields.function(_sum_year, string="Year Sum"),
1832 'sum_period': fields.function(_sum_period, string="Period Sum"),
1833 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1834 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1835 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1836 'company_id': fields.many2one('res.company', 'Company', required=True),
1837 '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.'),
1838 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any tax related to this tax code to appear on invoices"),
1839 'sequence': fields.integer('Sequence', help="Determine the display order in the report 'Accounting \ Reporting \ Generic Reporting \ Taxes \ Taxes Report'"),
1842 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1847 ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1848 return self.name_get(cr, user, ids, context)
1850 def name_get(self, cr, uid, ids, context=None):
1851 if isinstance(ids, (int, long)):
1855 if isinstance(ids, (int, long)):
1857 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1858 return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1861 def _default_company(self, cr, uid, context=None):
1862 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1864 return user.company_id.id
1865 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1867 'company_id': _default_company,
1869 'notprintable': False,
1872 def copy(self, cr, uid, id, default=None, context=None):
1875 default = default.copy()
1876 default.update({'line_ids': []})
1877 return super(account_tax_code, self).copy(cr, uid, id, default, context)
1879 _check_recursion = check_cycle
1881 (_check_recursion, 'Error!\nYou cannot create recursive accounts.', ['parent_id'])
1886 def get_precision_tax():
1887 def change_digit_tax(cr):
1888 res = openerp.registry(cr.dbname)['decimal.precision'].precision_get(cr, SUPERUSER_ID, 'Account')
1890 return change_digit_tax
1892 class account_tax(osv.osv):
1896 Type: percent, fixed, none, code
1897 PERCENT: tax = price * amount
1898 FIXED: tax = price + amount
1900 CODE: execute python code. localcontext = {'price_unit':pu}
1901 return result in the context
1902 Ex: result=round(price_unit*0.21,4)
1904 def copy_data(self, cr, uid, id, default=None, context=None):
1907 name = self.read(cr, uid, id, ['name'], context=context)['name']
1908 default = default.copy()
1909 default.update({'name': name + _(' (Copy)')})
1910 return super(account_tax, self).copy_data(cr, uid, id, default=default, context=context)
1912 _name = 'account.tax'
1913 _description = 'Tax'
1915 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1916 '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."),
1917 'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For taxes of type percentage, enter % ratio between 0-1."),
1918 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the tax without removing it."),
1919 'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1920 help="The computation method for the tax amount."),
1921 'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1922 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1923 '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."),
1924 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account', help="Set the account that will be set by default on invoice tax lines for invoices. Leave empty to use the expense account."),
1925 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account', help="Set the account that will be set by default on invoice tax lines for refunds. Leave empty to use the expense account."),
1926 'account_analytic_collected_id':fields.many2one('account.analytic.account', 'Invoice Tax Analytic Account', help="Set the analytic account that will be used by default on the invoice tax lines for invoices. Leave empty if you don't want to use an analytic account on the invoice tax lines by default."),
1927 'account_analytic_paid_id':fields.many2one('account.analytic.account', 'Refund Tax Analytic Account', help="Set the analytic account that will be used by default on the invoice tax lines for refunds. Leave empty if you don't want to use an analytic account on the invoice tax lines by default."),
1928 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1929 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1930 '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."),
1931 'python_compute':fields.text('Python Code'),
1932 'python_compute_inv':fields.text('Python Code (reverse)'),
1933 'python_applicable':fields.text('Applicable Code'),
1936 # Fields used for the Tax declaration
1938 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the tax declaration."),
1939 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the tax declaration."),
1940 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1941 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1943 # Same fields for refund invoices
1945 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the tax declaration."),
1946 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the tax declaration."),
1947 'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1."),
1948 'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1."),
1949 '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"),
1950 'company_id': fields.many2one('res.company', 'Company', required=True),
1951 'description': fields.char('Tax Code'),
1952 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1953 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1956 _sql_constraints = [
1957 ('name_company_uniq', 'unique(name, company_id)', 'Tax Name must be unique per company!'),
1960 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1962 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1963 result format: {[(id, name), (id, name), ...]}
1965 @param cr: A database cursor
1966 @param user: ID of the user currently logged in
1967 @param name: name to search
1968 @param args: other arguments
1969 @param operator: default operator is 'ilike', it can be changed
1970 @param context: context arguments, like lang, time zone
1971 @param limit: Returns first 'n' ids of complete result, default is 80.
1973 @return: Returns a list of tupples containing id and name
1981 ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context)
1983 ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context)
1985 ids = self.search(cr, user, args, limit=limit, context=context or {})
1986 return self.name_get(cr, user, ids, context=context)
1988 def write(self, cr, uid, ids, vals, context=None):
1989 if vals.get('type', False) and vals['type'] in ('none', 'code'):
1990 vals.update({'amount': 0.0})
1991 return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1993 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1994 journal_pool = self.pool.get('account.journal')
1996 if context and context.has_key('type'):
1997 if context.get('type') in ('out_invoice','out_refund'):
1998 args += [('type_tax_use','in',['sale','all'])]
1999 elif context.get('type') in ('in_invoice','in_refund'):
2000 args += [('type_tax_use','in',['purchase','all'])]
2002 if context and context.has_key('journal_id'):
2003 journal = journal_pool.browse(cr, uid, context.get('journal_id'))
2004 if journal.type in ('sale', 'purchase'):
2005 args += [('type_tax_use','in',[journal.type,'all'])]
2007 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
2009 def name_get(self, cr, uid, ids, context=None):
2013 for record in self.read(cr, uid, ids, ['description','name'], context=context):
2014 name = record['description'] and record['description'] or record['name']
2015 res.append((record['id'],name ))
2018 def _default_company(self, cr, uid, context=None):
2019 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2021 return user.company_id.id
2022 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2025 'python_compute': '''# price_unit\n# or False\n# product: product.product object or None\n# partner: res.partner object or None\n\nresult = price_unit * 0.10''',
2026 'python_compute_inv': '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
2027 'applicable_type': 'true',
2032 'type_tax_use': 'all',
2038 'include_base_amount': False,
2039 'company_id': _default_company,
2043 def _applicable(self, cr, uid, taxes, price_unit, product=None, partner=None):
2046 if tax.applicable_type=='code':
2047 localdict = {'price_unit':price_unit, 'product':product, 'partner':partner}
2048 exec tax.python_applicable in localdict
2049 if localdict.get('result', False):
2055 def _unit_compute(self, cr, uid, taxes, price_unit, product=None, partner=None, quantity=0):
2056 taxes = self._applicable(cr, uid, taxes, price_unit ,product, partner)
2058 cur_price_unit=price_unit
2060 # we compute the amount for the current tax object and append it to the result
2061 data = {'id':tax.id,
2062 'name':tax.description and tax.description + " - " + tax.name or tax.name,
2063 'account_collected_id':tax.account_collected_id.id,
2064 'account_paid_id':tax.account_paid_id.id,
2065 'account_analytic_collected_id': tax.account_analytic_collected_id.id,
2066 'account_analytic_paid_id': tax.account_analytic_paid_id.id,
2067 'base_code_id': tax.base_code_id.id,
2068 'ref_base_code_id': tax.ref_base_code_id.id,
2069 'sequence': tax.sequence,
2070 'base_sign': tax.base_sign,
2071 'tax_sign': tax.tax_sign,
2072 'ref_base_sign': tax.ref_base_sign,
2073 'ref_tax_sign': tax.ref_tax_sign,
2074 'price_unit': cur_price_unit,
2075 'tax_code_id': tax.tax_code_id.id,
2076 'ref_tax_code_id': tax.ref_tax_code_id.id,
2079 if tax.type=='percent':
2080 amount = cur_price_unit * tax.amount
2081 data['amount'] = amount
2083 elif tax.type=='fixed':
2084 data['amount'] = tax.amount
2085 data['tax_amount']=quantity
2086 # data['amount'] = quantity
2087 elif tax.type=='code':
2088 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
2089 exec tax.python_compute in localdict
2090 amount = localdict['result']
2091 data['amount'] = amount
2092 elif tax.type=='balance':
2093 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2094 data['balance'] = cur_price_unit
2096 amount2 = data.get('amount', 0.0)
2098 if tax.child_depend:
2101 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, product, partner, quantity)
2102 res.extend(child_tax)
2103 if tax.child_depend:
2105 for name in ('base','ref_base'):
2106 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2107 r[name+'_code_id'] = latest[name+'_code_id']
2108 r[name+'_sign'] = latest[name+'_sign']
2109 r['price_unit'] = latest['price_unit']
2110 latest[name+'_code_id'] = False
2111 for name in ('tax','ref_tax'):
2112 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2113 r[name+'_code_id'] = latest[name+'_code_id']
2114 r[name+'_sign'] = latest[name+'_sign']
2115 r['amount'] = data['amount']
2116 latest[name+'_code_id'] = False
2117 if tax.include_base_amount:
2118 cur_price_unit+=amount2
2121 def compute_all(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, force_excluded=False):
2123 :param force_excluded: boolean used to say that we don't want to consider the value of field price_include of
2124 tax. It's used in encoding by line where you don't matter if you encoded a tax with that boolean to True or
2127 'total': 0.0, # Total without taxes
2128 'total_included: 0.0, # Total with taxes
2129 'taxes': [] # List of taxes, see compute for the format
2133 # By default, for each tax, tax amount will first be computed
2134 # and rounded at the 'Account' decimal precision for each
2135 # PO/SO/invoice line and then these rounded amounts will be
2136 # summed, leading to the total amount for that tax. But, if the
2137 # company has tax_calculation_rounding_method = round_globally,
2138 # we still follow the same method, but we use a much larger
2139 # precision when we round the tax amount for each line (we use
2140 # the 'Account' decimal precision + 5), and that way it's like
2141 # rounding after the sum of the tax amounts of each line
2142 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2143 tax_compute_precision = precision
2144 if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally':
2145 tax_compute_precision += 5
2146 totalin = totalex = float_round(price_unit * quantity, precision)
2150 if not tax.price_include or force_excluded:
2154 tin = self.compute_inv(cr, uid, tin, price_unit, quantity, product=product, partner=partner, precision=tax_compute_precision)
2156 totalex -= r.get('amount', 0.0)
2159 totlex_qty = totalex/quantity
2162 tex = self._compute(cr, uid, tex, totlex_qty, quantity, product=product, partner=partner, precision=tax_compute_precision)
2164 totalin += r.get('amount', 0.0)
2167 'total_included': totalin,
2171 def compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None):
2172 _logger.warning("Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included.")
2173 return self._compute(cr, uid, taxes, price_unit, quantity, product, partner)
2175 def _compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
2177 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2181 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2182 one tax for each tax id in IDS and their children
2185 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2186 res = self._unit_compute(cr, uid, taxes, price_unit, product, partner, quantity)
2189 if r.get('balance',False):
2190 r['amount'] = round(r.get('balance', 0.0) * quantity, precision) - total
2192 r['amount'] = round(r.get('amount', 0.0) * quantity, precision)
2193 total += r['amount']
2196 def _unit_compute_inv(self, cr, uid, taxes, price_unit, product=None, partner=None):
2197 taxes = self._applicable(cr, uid, taxes, price_unit, product, partner)
2200 cur_price_unit = price_unit
2202 tax_parent_tot = 0.0
2204 if (tax.type=='percent') and not tax.include_base_amount:
2205 tax_parent_tot += tax.amount
2208 if (tax.type=='fixed') and not tax.include_base_amount:
2209 cur_price_unit -= tax.amount
2212 if tax.type=='percent':
2213 if tax.include_base_amount:
2214 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
2216 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
2218 elif tax.type=='fixed':
2221 elif tax.type=='code':
2222 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
2223 exec tax.python_compute_inv in localdict
2224 amount = localdict['result']
2225 elif tax.type=='balance':
2226 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2228 if tax.include_base_amount:
2229 cur_price_unit -= amount
2238 'account_collected_id': tax.account_collected_id.id,
2239 'account_paid_id': tax.account_paid_id.id,
2240 'account_analytic_collected_id': tax.account_analytic_collected_id.id,
2241 'account_analytic_paid_id': tax.account_analytic_paid_id.id,
2242 'base_code_id': tax.base_code_id.id,
2243 'ref_base_code_id': tax.ref_base_code_id.id,
2244 'sequence': tax.sequence,
2245 'base_sign': tax.base_sign,
2246 'tax_sign': tax.tax_sign,
2247 'ref_base_sign': tax.ref_base_sign,
2248 'ref_tax_sign': tax.ref_tax_sign,
2249 'price_unit': cur_price_unit,
2250 'tax_code_id': tax.tax_code_id.id,
2251 'ref_tax_code_id': tax.ref_tax_code_id.id,
2254 if tax.child_depend:
2258 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, product, partner)
2259 res.extend(parent_tax)
2264 total += r['amount']
2266 r['price_unit'] -= total
2270 def compute_inv(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
2272 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2273 Price Unit is a Tax included price
2277 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2278 one tax for each tax id in IDS and their children
2281 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2282 res = self._unit_compute_inv(cr, uid, taxes, price_unit, product, partner=None)
2285 if r.get('balance',False):
2286 r['amount'] = round(r['balance'] * quantity, precision) - total
2288 r['amount'] = round(r['amount'] * quantity, precision)
2289 total += r['amount']
2293 # ---------------------------------------------------------
2294 # Account Entries Models
2295 # ---------------------------------------------------------
2297 class account_model(osv.osv):
2298 _name = "account.model"
2299 _description = "Account Model"
2301 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2302 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2303 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2304 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2305 'legend': fields.text('Legend', readonly=True, size=100),
2309 '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'),
2312 def generate(self, cr, uid, ids, data=None, context=None):
2317 account_move_obj = self.pool.get('account.move')
2318 account_move_line_obj = self.pool.get('account.move.line')
2319 pt_obj = self.pool.get('account.payment.term')
2320 period_obj = self.pool.get('account.period')
2325 if data.get('date', False):
2326 context.update({'date': data['date']})
2328 move_date = context.get('date', time.strftime('%Y-%m-%d'))
2329 move_date = datetime.strptime(move_date,"%Y-%m-%d")
2330 for model in self.browse(cr, uid, ids, context=context):
2331 ctx = context.copy()
2332 ctx.update({'company_id': model.company_id.id})
2333 period_ids = period_obj.find(cr, uid, dt=context.get('date', False), context=ctx)
2334 period_id = period_ids and period_ids[0] or False
2335 ctx.update({'journal_id': model.journal_id.id,'period_id': period_id})
2337 entry['name'] = model.name%{'year': move_date.strftime('%Y'), 'month': move_date.strftime('%m'), 'date': move_date.strftime('%Y-%m')}
2339 raise osv.except_osv(_('Wrong Model!'), _('You have a wrong expression "%(...)s" in your model!'))
2340 move_id = account_move_obj.create(cr, uid, {
2341 'ref': entry['name'],
2342 'period_id': period_id,
2343 'journal_id': model.journal_id.id,
2344 'date': context.get('date', fields.date.context_today(self,cr,uid,context=context))
2346 move_ids.append(move_id)
2347 for line in model.lines_id:
2348 analytic_account_id = False
2349 if line.analytic_account_id:
2350 if not model.journal_id.analytic_journal_id:
2351 raise osv.except_osv(_('No Analytic Journal!'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2352 analytic_account_id = line.analytic_account_id.id
2355 'journal_id': model.journal_id.id,
2356 'period_id': period_id,
2357 'analytic_account_id': analytic_account_id
2360 date_maturity = context.get('date',time.strftime('%Y-%m-%d'))
2361 if line.date_maturity == 'partner':
2362 if not line.partner_id:
2363 raise osv.except_osv(_('Error!'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2364 "\nPlease define partner on it!")%(line.name, model.name))
2366 payment_term_id = False
2367 if model.journal_id.type in ('purchase', 'purchase_refund') and line.partner_id.property_supplier_payment_term:
2368 payment_term_id = line.partner_id.property_supplier_payment_term.id
2369 elif line.partner_id.property_payment_term:
2370 payment_term_id = line.partner_id.property_payment_term.id
2372 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2374 pterm_list = [l[0] for l in pterm_list]
2376 date_maturity = pterm_list[-1]
2380 'quantity': line.quantity,
2381 'debit': line.debit,
2382 'credit': line.credit,
2383 'account_id': line.account_id.id,
2385 'partner_id': line.partner_id.id,
2386 'date': context.get('date', fields.date.context_today(self,cr,uid,context=context)),
2387 'date_maturity': date_maturity
2389 account_move_line_obj.create(cr, uid, val, context=ctx)
2393 def onchange_journal_id(self, cr, uid, ids, journal_id, context=None):
2397 journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
2398 if journal.company_id.id:
2399 company_id = journal.company_id.id
2401 return {'value': {'company_id': company_id}}
2404 class account_model_line(osv.osv):
2405 _name = "account.model.line"
2406 _description = "Account Model Entries"
2408 'name': fields.char('Name', size=64, required=True),
2409 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones."),
2410 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries."),
2411 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2412 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2413 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2414 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2415 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2416 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2417 'currency_id': fields.many2one('res.currency', 'Currency'),
2418 'partner_id': fields.many2one('res.partner', 'Partner'),
2419 '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."),
2422 _sql_constraints = [
2423 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model, they must be positive!'),
2424 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model, they must be positive!'),
2427 # ---------------------------------------------------------
2428 # Account Subscription
2429 # ---------------------------------------------------------
2432 class account_subscription(osv.osv):
2433 _name = "account.subscription"
2434 _description = "Account Subscription"
2436 'name': fields.char('Name', size=64, required=True),
2437 'ref': fields.char('Reference', size=16),
2438 'model_id': fields.many2one('account.model', 'Model', required=True),
2439 'date_start': fields.date('Start Date', required=True),
2440 'period_total': fields.integer('Number of Periods', required=True),
2441 'period_nbr': fields.integer('Period', required=True),
2442 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2443 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', required=True, readonly=True),
2444 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2447 'date_start': fields.date.context_today,
2448 'period_type': 'month',
2453 def state_draft(self, cr, uid, ids, context=None):
2454 self.write(cr, uid, ids, {'state':'draft'})
2457 def check(self, cr, uid, ids, context=None):
2459 for sub in self.browse(cr, uid, ids, context=context):
2461 for line in sub.lines_id:
2462 if not line.move_id.id:
2466 todone.append(sub.id)
2468 self.write(cr, uid, todone, {'state':'done'})
2471 def remove_line(self, cr, uid, ids, context=None):
2473 for sub in self.browse(cr, uid, ids, context=context):
2474 for line in sub.lines_id:
2475 if not line.move_id.id:
2476 toremove.append(line.id)
2478 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2479 self.write(cr, uid, ids, {'state':'draft'})
2482 def compute(self, cr, uid, ids, context=None):
2483 for sub in self.browse(cr, uid, ids, context=context):
2485 for i in range(sub.period_total):
2486 self.pool.get('account.subscription.line').create(cr, uid, {
2488 'subscription_id': sub.id,
2490 if sub.period_type=='day':
2491 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2492 if sub.period_type=='month':
2493 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2494 if sub.period_type=='year':
2495 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2496 self.write(cr, uid, ids, {'state':'running'})
2500 class account_subscription_line(osv.osv):
2501 _name = "account.subscription.line"
2502 _description = "Account Subscription Line"
2504 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2505 'date': fields.date('Date', required=True),
2506 'move_id': fields.many2one('account.move', 'Entry'),
2509 def move_create(self, cr, uid, ids, context=None):
2512 obj_model = self.pool.get('account.model')
2513 for line in self.browse(cr, uid, ids, context=context):
2517 move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], data, context)
2518 tocheck[line.subscription_id.id] = True
2519 self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2520 all_moves.extend(move_ids)
2522 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2528 # ---------------------------------------------------------------
2529 # Account Templates: Account, Tax, Tax Code and chart. + Wizard
2530 # ---------------------------------------------------------------
2532 class account_tax_template(osv.osv):
2533 _name = 'account.tax.template'
2535 class account_account_template(osv.osv):
2537 _name = "account.account.template"
2538 _description ='Templates for Accounts'
2541 'name': fields.char('Name', size=256, required=True, select=True),
2542 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2543 'code': fields.char('Code', size=64, required=True, select=1),
2544 'type': fields.selection([
2545 ('receivable','Receivable'),
2546 ('payable','Payable'),
2548 ('consolidation','Consolidation'),
2549 ('liquidity','Liquidity'),
2550 ('other','Regular'),
2551 ('closed','Closed'),
2552 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2553 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2554 "can have children accounts for multi-company consolidations, payable/receivable are for "\
2555 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2556 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2557 help="These types are defined according to your country. The type contains more information "\
2558 "about the account and its specificities."),
2559 'financial_report_ids': fields.many2many('account.financial.report', 'account_template_financial_report', 'account_template_id', 'report_line_id', 'Financial Reports'),
2560 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2561 'shortcut': fields.char('Shortcut', size=12),
2562 'note': fields.text('Note'),
2563 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade', domain=[('type','=','view')]),
2564 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2565 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2566 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2567 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', help="This optional field allow you to link an account template to a specific chart template that may differ from the one its root parent belongs to. This allow you to define chart templates that extend another and complete it with few new accounts (You don't need to define the whole structure that is common to both several times)."),
2576 _check_recursion = check_cycle
2578 (_check_recursion, 'Error!\nYou cannot create recursive account templates.', ['parent_id']),
2581 def name_get(self, cr, uid, ids, context=None):
2584 reads = self.read(cr, uid, ids, ['name','code'], context=context)
2586 for record in reads:
2587 name = record['name']
2589 name = record['code']+' '+name
2590 res.append((record['id'],name ))
2593 def generate_account(self, cr, uid, chart_template_id, tax_template_ref, acc_template_ref, code_digits, company_id, context=None):
2595 This method for generating accounts from templates.
2597 :param chart_template_id: id of the chart template chosen in the wizard
2598 :param tax_template_ref: Taxes templates reference for write taxes_id in account_account.
2599 :paramacc_template_ref: dictionary with the mappping between the account templates and the real accounts.
2600 :param code_digits: number of digits got from wizard.multi.charts.accounts, this is use for account code.
2601 :param company_id: company_id selected from wizard.multi.charts.accounts.
2602 :returns: return acc_template_ref for reference purpose.
2607 obj_acc = self.pool.get('account.account')
2608 company_name = self.pool.get('res.company').browse(cr, uid, company_id, context=context).name
2609 template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
2610 #deactivate the parent_store functionnality on account_account for rapidity purpose
2611 ctx = context.copy()
2612 ctx.update({'defer_parent_store_computation': True})
2614 children_acc_criteria = [('chart_template_id','=', chart_template_id)]
2615 if template.account_root_id.id:
2616 children_acc_criteria = ['|'] + children_acc_criteria + ['&',('parent_id','child_of', [template.account_root_id.id]),('chart_template_id','=', False)]
2617 children_acc_template = self.search(cr, uid, [('nocreate','!=',True)] + children_acc_criteria, order='id')
2618 for account_template in self.browse(cr, uid, children_acc_template, context=context):
2619 # skip the root of COA if it's not the main one
2620 if (template.account_root_id.id == account_template.id) and template.parent_id:
2623 for tax in account_template.tax_ids:
2624 tax_ids.append(tax_template_ref[tax.id])
2626 code_main = account_template.code and len(account_template.code) or 0
2627 code_acc = account_template.code or ''
2628 if code_main > 0 and code_main <= code_digits and account_template.type != 'view':
2629 code_acc = str(code_acc) + (str('0'*(code_digits-code_main)))
2630 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
2631 #the level as to be given as well at the creation time, because of the defer_parent_store_computation in
2632 #context. Indeed because of this, the parent_left and parent_right are not computed and thus the child_of
2633 #operator does not return the expected values, with result of having the level field not computed at all.
2635 level = parent_id in level_ref and level_ref[parent_id] + 1 or obj_acc._get_level(cr, uid, [parent_id], 'level', None, context=context)[parent_id] + 1
2639 'name': (template.account_root_id.id == account_template.id) and company_name or account_template.name,
2640 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2642 'type': account_template.type,
2643 'user_type': account_template.user_type and account_template.user_type.id or False,
2644 'reconcile': account_template.reconcile,
2645 'shortcut': account_template.shortcut,
2646 'note': account_template.note,
2647 'financial_report_ids': account_template.financial_report_ids and [(6,0,[x.id for x in account_template.financial_report_ids])] or False,
2648 'parent_id': parent_id,
2649 'tax_ids': [(6,0,tax_ids)],
2650 'company_id': company_id,
2653 new_account = obj_acc.create(cr, uid, vals, context=ctx)
2654 acc_template_ref[account_template.id] = new_account
2655 level_ref[new_account] = level
2657 #reactivate the parent_store functionnality on account_account
2658 obj_acc._parent_store_compute(cr)
2659 return acc_template_ref
2662 class account_add_tmpl_wizard(osv.osv_memory):
2663 """Add one more account from the template.
2665 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2666 _name = 'account.addtmpl.wizard'
2668 def _get_def_cparent(self, cr, uid, context=None):
2669 acc_obj = self.pool.get('account.account')
2670 tmpl_obj = self.pool.get('account.account.template')
2671 tids = tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2672 if not tids or not tids[0]['parent_id']:
2674 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2676 if not ptids or not ptids[0]['code']:
2677 raise osv.except_osv(_('Error!'), _('There is no parent code for the template account.'))
2678 res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2679 return res and res[0] or False
2682 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2685 'cparent_id': _get_def_cparent,
2688 def action_create(self,cr,uid,ids,context=None):
2691 acc_obj = self.pool.get('account.account')
2692 tmpl_obj = self.pool.get('account.account.template')
2693 data = self.read(cr, uid, ids)[0]
2694 company_id = acc_obj.read(cr, uid, [data['cparent_id'][0]], ['company_id'])[0]['company_id'][0]
2695 account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2697 'name': account_template.name,
2698 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2699 'code': account_template.code,
2700 'type': account_template.type,
2701 'user_type': account_template.user_type and account_template.user_type.id or False,
2702 'reconcile': account_template.reconcile,
2703 'shortcut': account_template.shortcut,
2704 'note': account_template.note,
2705 'parent_id': data['cparent_id'][0],
2706 'company_id': company_id,
2708 acc_obj.create(cr, uid, vals)
2709 return {'type':'state', 'state': 'end' }
2711 def action_cancel(self, cr, uid, ids, context=None):
2712 return { 'type': 'state', 'state': 'end' }
2715 class account_tax_code_template(osv.osv):
2717 _name = 'account.tax.code.template'
2718 _description = 'Tax Code Template'
2722 'name': fields.char('Tax Case Name', size=64, required=True),
2723 'code': fields.char('Case Code', size=64),
2724 'info': fields.text('Description'),
2725 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2726 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2727 'sign': fields.float('Sign For Parent', required=True),
2728 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any tax related to this tax Code to appear on invoices."),
2733 'notprintable': False,
2736 def generate_tax_code(self, cr, uid, tax_code_root_id, company_id, context=None):
2738 This function generates the tax codes from the templates of tax code that are children of the given one passed
2739 in argument. Then it returns a dictionary with the mappping between the templates and the real objects.
2741 :param tax_code_root_id: id of the root of all the tax code templates to process
2742 :param company_id: id of the company the wizard is running for
2743 :returns: dictionary with the mappping between the templates and the real objects.
2746 obj_tax_code_template = self.pool.get('account.tax.code.template')
2747 obj_tax_code = self.pool.get('account.tax.code')
2748 tax_code_template_ref = {}
2749 company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
2751 #find all the children of the tax_code_root_id
2752 children_tax_code_template = tax_code_root_id and obj_tax_code_template.search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id') or []
2753 for tax_code_template in obj_tax_code_template.browse(cr, uid, children_tax_code_template, context=context):
2755 'name': (tax_code_root_id == tax_code_template.id) and company.name or tax_code_template.name,
2756 'code': tax_code_template.code,
2757 'info': tax_code_template.info,
2758 '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,
2759 'company_id': company_id,
2760 'sign': tax_code_template.sign,
2762 #check if this tax code already exists
2763 rec_list = obj_tax_code.search(cr, uid, [('name', '=', vals['name']),('code', '=', vals['code']),('company_id', '=', vals['company_id'])], context=context)
2765 #if not yet, create it
2766 new_tax_code = obj_tax_code.create(cr, uid, vals)
2767 #recording the new tax code to do the mapping
2768 tax_code_template_ref[tax_code_template.id] = new_tax_code
2769 return tax_code_template_ref
2771 def name_get(self, cr, uid, ids, context=None):
2774 if isinstance(ids, (int, long)):
2776 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2777 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2780 _check_recursion = check_cycle
2782 (_check_recursion, 'Error!\nYou cannot create recursive Tax Codes.', ['parent_id'])
2784 _order = 'code,name'
2787 class account_chart_template(osv.osv):
2788 _name="account.chart.template"
2789 _description= "Templates for Account Chart"
2792 'name': fields.char('Name', size=64, required=True),
2793 'parent_id': fields.many2one('account.chart.template', 'Parent Chart Template'),
2794 'code_digits': fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2795 'visible': fields.boolean('Can be Visible?', help="Set this to False if you don't want this template to be used actively in the wizard that generate Chart of Accounts from templates, this is useful when you want to generate accounts of this template only when loading its child template."),
2796 'currency_id': fields.many2one('res.currency', 'Currency'),
2797 'complete_tax_set': fields.boolean('Complete Set of Taxes', help='This boolean helps you to choose if you want to propose to the user to encode the sale and purchase rates or choose from list of taxes. This last choice assumes that the set of tax defined on this template is complete'),
2798 'account_root_id': fields.many2one('account.account.template', 'Root Account', domain=[('parent_id','=',False)]),
2799 'tax_code_root_id': fields.many2one('account.tax.code.template', 'Root Tax Code', domain=[('parent_id','=',False)]),
2800 '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'),
2801 'bank_account_view_id': fields.many2one('account.account.template', 'Bank Account'),
2802 'property_account_receivable': fields.many2one('account.account.template', 'Receivable Account'),
2803 'property_account_payable': fields.many2one('account.account.template', 'Payable Account'),
2804 'property_account_expense_categ': fields.many2one('account.account.template', 'Expense Category Account'),
2805 'property_account_income_categ': fields.many2one('account.account.template', 'Income Category Account'),
2806 'property_account_expense': fields.many2one('account.account.template', 'Expense Account on Product Template'),
2807 'property_account_income': fields.many2one('account.account.template', 'Income Account on Product Template'),
2808 'property_account_income_opening': fields.many2one('account.account.template', 'Opening Entries Income Account'),
2809 'property_account_expense_opening': fields.many2one('account.account.template', 'Opening Entries Expense Account'),
2815 'complete_tax_set': True,
2819 class account_tax_template(osv.osv):
2821 _name = 'account.tax.template'
2822 _description = 'Templates for Taxes'
2825 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2826 'name': fields.char('Tax Name', size=64, required=True),
2827 '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."),
2828 'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For Tax Type percent enter % ratio between 0-1."),
2829 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True),
2830 '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."),
2831 '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."),
2832 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2833 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2834 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2835 '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."),
2836 'python_compute':fields.text('Python Code'),
2837 'python_compute_inv':fields.text('Python Code (reverse)'),
2838 'python_applicable':fields.text('Applicable Code'),
2841 # Fields used for the Tax declaration
2843 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the tax declaration."),
2844 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the tax declaration."),
2845 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2846 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2848 # Same fields for refund invoices
2850 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the tax declaration."),
2851 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the tax declaration."),
2852 'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1."),
2853 'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1."),
2854 '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."),
2855 'description': fields.char('Internal Name'),
2856 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,),
2857 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
2860 def name_get(self, cr, uid, ids, context=None):
2864 for record in self.read(cr, uid, ids, ['description','name'], context=context):
2865 name = record['description'] and record['description'] or record['name']
2866 res.append((record['id'],name ))
2869 def _default_company(self, cr, uid, context=None):
2870 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2872 return user.company_id.id
2873 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2876 'python_compute': lambda *a: '''# price_unit\n# product: product.product object or None\n# partner: res.partner object or None\n\nresult = price_unit * 0.10''',
2877 'python_compute_inv': lambda *a: '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
2878 'applicable_type': 'true',
2886 'include_base_amount': False,
2887 'type_tax_use': 'all',
2892 def _generate_tax(self, cr, uid, tax_templates, tax_code_template_ref, company_id, context=None):
2894 This method generate taxes from templates.
2896 :param tax_templates: list of browse record of the tax templates to process
2897 :param tax_code_template_ref: Taxcode templates reference.
2898 :param company_id: id of the company the wizard is running for
2901 'tax_template_to_tax': mapping between tax template and the newly generated taxes corresponding,
2902 'account_dict': dictionary containing a to-do list with all the accounts to assign on new taxes
2909 tax_template_to_tax = {}
2910 for tax in tax_templates:
2913 'sequence': tax.sequence,
2914 'amount': tax.amount,
2916 'applicable_type': tax.applicable_type,
2917 'domain': tax.domain,
2918 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_to_tax) and tax_template_to_tax[tax.parent_id.id]) or False,
2919 'child_depend': tax.child_depend,
2920 'python_compute': tax.python_compute,
2921 'python_compute_inv': tax.python_compute_inv,
2922 'python_applicable': tax.python_applicable,
2923 '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,
2924 '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,
2925 'base_sign': tax.base_sign,
2926 'tax_sign': tax.tax_sign,
2927 '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,
2928 '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,
2929 'ref_base_sign': tax.ref_base_sign,
2930 'ref_tax_sign': tax.ref_tax_sign,
2931 'include_base_amount': tax.include_base_amount,
2932 'description': tax.description,
2933 'company_id': company_id,
2934 'type_tax_use': tax.type_tax_use,
2935 'price_include': tax.price_include
2937 new_tax = self.pool.get('account.tax').create(cr, uid, vals_tax)
2938 tax_template_to_tax[tax.id] = new_tax
2939 #as the accounts have not been created yet, we have to wait before filling these fields
2940 todo_dict[new_tax] = {
2941 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2942 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2944 res.update({'tax_template_to_tax': tax_template_to_tax, 'account_dict': todo_dict})
2948 # Fiscal Position Templates
2950 class account_fiscal_position_template(osv.osv):
2951 _name = 'account.fiscal.position.template'
2952 _description = 'Template for Fiscal Position'
2955 'name': fields.char('Fiscal Position Template', size=64, required=True),
2956 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2957 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2958 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping'),
2959 'note': fields.text('Notes', translate=True),
2962 def generate_fiscal_position(self, cr, uid, chart_temp_id, tax_template_ref, acc_template_ref, company_id, context=None):
2964 This method generate Fiscal Position, Fiscal Position Accounts and Fiscal Position Taxes from templates.
2966 :param chart_temp_id: Chart Template Id.
2967 :param taxes_ids: Taxes templates reference for generating account.fiscal.position.tax.
2968 :param acc_template_ref: Account templates reference for generating account.fiscal.position.account.
2969 :param company_id: company_id selected from wizard.multi.charts.accounts.
2974 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2975 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2976 obj_fiscal_position = self.pool.get('account.fiscal.position')
2977 fp_ids = self.search(cr, uid, [('chart_template_id', '=', chart_temp_id)])
2978 for position in self.browse(cr, uid, fp_ids, context=context):
2979 new_fp = obj_fiscal_position.create(cr, uid, {'company_id': company_id, 'name': position.name, 'note': position.note})
2980 for tax in position.tax_ids:
2981 obj_tax_fp.create(cr, uid, {
2982 'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2983 'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2984 'position_id': new_fp
2986 for acc in position.account_ids:
2987 obj_ac_fp.create(cr, uid, {
2988 'account_src_id': acc_template_ref[acc.account_src_id.id],
2989 'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2990 'position_id': new_fp
2995 class account_fiscal_position_tax_template(osv.osv):
2996 _name = 'account.fiscal.position.tax.template'
2997 _description = 'Template Tax Fiscal Position'
2998 _rec_name = 'position_id'
3001 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
3002 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
3003 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
3007 class account_fiscal_position_account_template(osv.osv):
3008 _name = 'account.fiscal.position.account.template'
3009 _description = 'Template Account Fiscal Mapping'
3010 _rec_name = 'position_id'
3012 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
3013 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
3014 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
3018 # ---------------------------------------------------------
3019 # Account generation from template wizards
3020 # ---------------------------------------------------------
3022 class wizard_multi_charts_accounts(osv.osv_memory):
3024 Create a new account chart for a company.
3027 * an account chart template
3028 * a number of digits for formatting code of non-view accounts
3029 * a list of bank accounts owned by the company
3031 * generates all accounts from the template and assigns them to the right company
3032 * generates all taxes and tax codes, changing account assignations
3033 * generates all accounting properties and assigns them correctly
3035 _name='wizard.multi.charts.accounts'
3036 _inherit = 'res.config'
3039 'company_id':fields.many2one('res.company', 'Company', required=True),
3040 'currency_id': fields.many2one('res.currency', 'Currency', help="Currency as per company's country."),
3041 'only_one_chart_template': fields.boolean('Only One Chart Template Available'),
3042 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
3043 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Cash and Banks', required=True),
3044 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
3045 "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
3046 "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
3047 'sale_tax_rate': fields.float('Sales Tax(%)'),
3048 'purchase_tax_rate': fields.float('Purchase Tax(%)'),
3049 'complete_tax_set': fields.boolean('Complete Set of Taxes', help='This boolean helps you to choose if you want to propose to the user to encode the sales and purchase rates or use the usual m2o fields. This last choice assumes that the set of tax defined for the chosen template is complete'),
3053 def _get_chart_parent_ids(self, cr, uid, chart_template, context=None):
3054 """ Returns the IDs of all ancestor charts, including the chart itself.
3055 (inverse of child_of operator)
3057 :param browse_record chart_template: the account.chart.template record
3058 :return: the IDS of all ancestor charts, including the chart itself.
3060 result = [chart_template.id]
3061 while chart_template.parent_id:
3062 chart_template = chart_template.parent_id
3063 result.append(chart_template.id)
3066 def onchange_tax_rate(self, cr, uid, ids, rate=False, context=None):
3067 return {'value': {'purchase_tax_rate': rate or False}}
3069 def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
3071 tax_templ_obj = self.pool.get('account.tax.template')
3072 res['value'] = {'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False}
3073 if chart_template_id:
3074 data = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3075 currency_id = data.currency_id and data.currency_id.id or self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
3076 res['value'].update({'complete_tax_set': data.complete_tax_set, 'currency_id': currency_id})
3077 if data.complete_tax_set:
3078 # default tax is given by the lowest sequence. For same sequence we will take the latest created as it will be the case for tax created while isntalling the generic chart of account
3079 chart_ids = self._get_chart_parent_ids(cr, uid, data, context=context)
3080 base_tax_domain = [("chart_template_id", "in", chart_ids), ('parent_id', '=', False)]
3081 sale_tax_domain = base_tax_domain + [('type_tax_use', 'in', ('sale','all'))]
3082 purchase_tax_domain = base_tax_domain + [('type_tax_use', 'in', ('purchase','all'))]
3083 sale_tax_ids = tax_templ_obj.search(cr, uid, sale_tax_domain, order="sequence, id desc")
3084 purchase_tax_ids = tax_templ_obj.search(cr, uid, purchase_tax_domain, order="sequence, id desc")
3085 res['value'].update({'sale_tax': sale_tax_ids and sale_tax_ids[0] or False,
3086 'purchase_tax': purchase_tax_ids and purchase_tax_ids[0] or False})
3087 res.setdefault('domain', {})
3088 res['domain']['sale_tax'] = repr(sale_tax_domain)
3089 res['domain']['purchase_tax'] = repr(purchase_tax_domain)
3090 if data.code_digits:
3091 res['value'].update({'code_digits': data.code_digits})
3094 def default_get(self, cr, uid, fields, context=None):
3095 res = super(wizard_multi_charts_accounts, self).default_get(cr, uid, fields, context=context)
3096 tax_templ_obj = self.pool.get('account.tax.template')
3097 account_chart_template = self.pool['account.chart.template']
3099 if 'bank_accounts_id' in fields:
3100 res.update({'bank_accounts_id': [{'acc_name': _('Cash'), 'account_type': 'cash'},{'acc_name': _('Bank'), 'account_type': 'bank'}]})
3101 if 'company_id' in fields:
3102 res.update({'company_id': self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0].company_id.id})
3103 if 'currency_id' in fields:
3104 company_id = res.get('company_id') or False
3106 company_obj = self.pool.get('res.company')
3107 country_id = company_obj.browse(cr, uid, company_id, context=context).country_id.id
3108 currency_id = company_obj.on_change_country(cr, uid, company_id, country_id, context=context)['value']['currency_id']
3109 res.update({'currency_id': currency_id})
3111 ids = account_chart_template.search(cr, uid, [('visible', '=', True)], context=context)
3113 #in order to set default chart which was last created set max of ids.
3115 if context.get("default_charts"):
3116 model_data = self.pool.get('ir.model.data').search_read(cr, uid, [('model','=','account.chart.template'),('module','=',context.get("default_charts"))], ['res_id'], context=context)
3118 chart_id = model_data[0]['res_id']
3119 chart = account_chart_template.browse(cr, uid, chart_id, context=context)
3120 chart_hierarchy_ids = self._get_chart_parent_ids(cr, uid, chart, context=context)
3121 if 'chart_template_id' in fields:
3122 res.update({'only_one_chart_template': len(ids) == 1,
3123 'chart_template_id': chart_id})
3124 if 'sale_tax' in fields:
3125 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id", "in", chart_hierarchy_ids),
3126 ('type_tax_use', 'in', ('sale','all'))],
3128 res.update({'sale_tax': sale_tax_ids and sale_tax_ids[0] or False})
3129 if 'purchase_tax' in fields:
3130 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id", "in", chart_hierarchy_ids),
3131 ('type_tax_use', 'in', ('purchase','all'))],
3133 res.update({'purchase_tax': purchase_tax_ids and purchase_tax_ids[0] or False})
3135 'purchase_tax_rate': 15.0,
3136 'sale_tax_rate': 15.0,
3140 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
3141 if context is None:context = {}
3142 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)
3144 acc_template_obj = self.pool.get('account.chart.template')
3145 company_obj = self.pool.get('res.company')
3147 company_ids = company_obj.search(cr, uid, [], context=context)
3148 #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)
3149 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",))
3150 configured_cmp = [r[0] for r in cr.fetchall()]
3151 unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
3152 for field in res['fields']:
3153 if field == 'company_id':
3154 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
3155 res['fields'][field]['selection'] = [('', '')]
3156 if unconfigured_cmp:
3157 cmp_select = [(line.id, line.name) for line in company_obj.browse(cr, uid, unconfigured_cmp)]
3158 res['fields'][field]['selection'] = cmp_select
3161 def check_created_journals(self, cr, uid, vals_journal, company_id, context=None):
3163 This method used for checking journals already created or not. If not then create new journal.
3165 obj_journal = self.pool.get('account.journal')
3166 rec_list = obj_journal.search(cr, uid, [('name','=', vals_journal['name']),('company_id', '=', company_id)], context=context)
3168 obj_journal.create(cr, uid, vals_journal, context=context)
3171 def generate_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3173 This method is used for creating journals.
3175 :param chart_temp_id: Chart Template Id.
3176 :param acc_template_ref: Account templates reference.
3177 :param company_id: company_id selected from wizard.multi.charts.accounts.
3180 journal_data = self._prepare_all_journals(cr, uid, chart_template_id, acc_template_ref, company_id, context=context)
3181 for vals_journal in journal_data:
3182 self.check_created_journals(cr, uid, vals_journal, company_id, context=context)
3185 def _prepare_all_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3186 def _get_analytic_journal(journal_type):
3187 # Get the analytic journal
3190 if journal_type in ('sale', 'sale_refund'):
3191 data = obj_data.get_object_reference(cr, uid, 'account', 'analytic_journal_sale')
3192 elif journal_type in ('purchase', 'purchase_refund'):
3193 data = obj_data.get_object_reference(cr, uid, 'account', 'exp')
3194 elif journal_type == 'general':
3198 return data and data[1] or False
3200 def _get_default_account(journal_type, type='debit'):
3201 # Get the default accounts
3202 default_account = False
3203 if journal_type in ('sale', 'sale_refund'):
3204 default_account = acc_template_ref.get(template.property_account_income_categ.id)
3205 elif journal_type in ('purchase', 'purchase_refund'):
3206 default_account = acc_template_ref.get(template.property_account_expense_categ.id)
3207 elif journal_type == 'situation':
3209 default_account = acc_template_ref.get(template.property_account_expense_opening.id)
3211 default_account = acc_template_ref.get(template.property_account_income_opening.id)
3212 return default_account
3215 'sale': _('Sales Journal'),
3216 'purchase': _('Purchase Journal'),
3217 'sale_refund': _('Sales Refund Journal'),
3218 'purchase_refund': _('Purchase Refund Journal'),
3219 'general': _('Miscellaneous Journal'),
3220 'situation': _('Opening Entries Journal'),
3224 'purchase': _('EXJ'),
3225 'sale_refund': _('SCNJ'),
3226 'purchase_refund': _('ECNJ'),
3227 'general': _('MISC'),
3228 'situation': _('OPEJ'),
3231 obj_data = self.pool.get('ir.model.data')
3232 analytic_journal_obj = self.pool.get('account.analytic.journal')
3233 template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3236 for journal_type in ['sale', 'purchase', 'sale_refund', 'purchase_refund', 'general', 'situation']:
3238 'type': journal_type,
3239 'name': journal_names[journal_type],
3240 'code': journal_codes[journal_type],
3241 'company_id': company_id,
3242 'centralisation': journal_type == 'situation',
3243 'analytic_journal_id': _get_analytic_journal(journal_type),
3244 'default_credit_account_id': _get_default_account(journal_type, 'credit'),
3245 'default_debit_account_id': _get_default_account(journal_type, 'debit'),
3247 journal_data.append(vals)
3250 def generate_properties(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3252 This method used for creating properties.
3254 :param chart_template_id: id of the current chart template for which we need to create properties
3255 :param acc_template_ref: Mapping between ids of account templates and real accounts created from them
3256 :param company_id: company_id selected from wizard.multi.charts.accounts.
3259 property_obj = self.pool.get('ir.property')
3260 field_obj = self.pool.get('ir.model.fields')
3262 ('property_account_receivable','res.partner','account.account'),
3263 ('property_account_payable','res.partner','account.account'),
3264 ('property_account_expense_categ','product.category','account.account'),
3265 ('property_account_income_categ','product.category','account.account'),
3266 ('property_account_expense','product.template','account.account'),
3267 ('property_account_income','product.template','account.account'),
3269 template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3270 for record in todo_list:
3271 account = getattr(template, record[0])
3272 value = account and 'account.account,' + str(acc_template_ref[account.id]) or False
3274 field = field_obj.search(cr, uid, [('name', '=', record[0]),('model', '=', record[1]),('relation', '=', record[2])], context=context)
3277 'company_id': company_id,
3278 'fields_id': field[0],
3281 property_ids = property_obj.search(cr, uid, [('name','=', record[0]),('company_id', '=', company_id)], context=context)
3283 #the property exist: modify it
3284 property_obj.write(cr, uid, property_ids, vals, context=context)
3286 #create the property
3287 property_obj.create(cr, uid, vals, context=context)
3290 def _install_template(self, cr, uid, template_id, company_id, code_digits=None, obj_wizard=None, acc_ref=None, taxes_ref=None, tax_code_ref=None, context=None):
3292 This function recursively loads the template objects and create the real objects from them.
3294 :param template_id: id of the chart template to load
3295 :param company_id: id of the company the wizard is running for
3296 :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3297 :param obj_wizard: the current wizard for generating the COA from the templates
3298 :param acc_ref: Mapping between ids of account templates and real accounts created from them
3299 :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3300 :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3301 :returns: return a tuple with a dictionary containing
3302 * the mapping between the account template ids and the ids of the real accounts that have been generated
3303 from them, as first item,
3304 * a similar dictionary for mapping the tax templates and taxes, as second item,
3305 * a last identical containing the mapping of tax code templates and tax codes
3306 :rtype: tuple(dict, dict, dict)
3310 if taxes_ref is None:
3312 if tax_code_ref is None:
3314 template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3315 if template.parent_id:
3316 tmp1, tmp2, tmp3 = self._install_template(cr, uid, template.parent_id.id, company_id, code_digits=code_digits, acc_ref=acc_ref, taxes_ref=taxes_ref, tax_code_ref=tax_code_ref, context=context)
3317 acc_ref.update(tmp1)
3318 taxes_ref.update(tmp2)
3319 tax_code_ref.update(tmp3)
3320 tmp1, tmp2, tmp3 = self._load_template(cr, uid, template_id, company_id, code_digits=code_digits, obj_wizard=obj_wizard, account_ref=acc_ref, taxes_ref=taxes_ref, tax_code_ref=tax_code_ref, context=context)
3321 acc_ref.update(tmp1)
3322 taxes_ref.update(tmp2)
3323 tax_code_ref.update(tmp3)
3324 return acc_ref, taxes_ref, tax_code_ref
3326 def _load_template(self, cr, uid, template_id, company_id, code_digits=None, obj_wizard=None, account_ref=None, taxes_ref=None, tax_code_ref=None, context=None):
3328 This function generates all the objects from the templates
3330 :param template_id: id of the chart template to load
3331 :param company_id: id of the company the wizard is running for
3332 :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3333 :param obj_wizard: the current wizard for generating the COA from the templates
3334 :param acc_ref: Mapping between ids of account templates and real accounts created from them
3335 :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3336 :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3337 :returns: return a tuple with a dictionary containing
3338 * the mapping between the account template ids and the ids of the real accounts that have been generated
3339 from them, as first item,
3340 * a similar dictionary for mapping the tax templates and taxes, as second item,
3341 * a last identical containing the mapping of tax code templates and tax codes
3342 :rtype: tuple(dict, dict, dict)
3344 if account_ref is None:
3346 if taxes_ref is None:
3348 if tax_code_ref is None:
3350 template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3351 obj_tax_code_template = self.pool.get('account.tax.code.template')
3352 obj_acc_tax = self.pool.get('account.tax')
3353 obj_tax_temp = self.pool.get('account.tax.template')
3354 obj_acc_template = self.pool.get('account.account.template')
3355 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
3357 # create all the tax code.
3358 tax_code_ref.update(obj_tax_code_template.generate_tax_code(cr, uid, template.tax_code_root_id.id, company_id, context=context))
3360 # Generate taxes from templates.
3361 tax_templates = [x for x in template.tax_template_ids]
3362 generated_tax_res = obj_tax_temp._generate_tax(cr, uid, tax_templates, tax_code_ref, company_id, context=context)
3363 taxes_ref.update(generated_tax_res['tax_template_to_tax'])
3365 # Generating Accounts from templates.
3366 account_template_ref = obj_acc_template.generate_account(cr, uid, template_id, taxes_ref, account_ref, code_digits, company_id, context=context)
3367 account_ref.update(account_template_ref)
3369 # writing account values on tax after creation of accounts
3370 for key,value in generated_tax_res['account_dict'].items():
3371 if value['account_collected_id'] or value['account_paid_id']:
3372 obj_acc_tax.write(cr, uid, [key], {
3373 'account_collected_id': account_ref.get(value['account_collected_id'], False),
3374 'account_paid_id': account_ref.get(value['account_paid_id'], False),
3378 self.generate_journals(cr, uid, template_id, account_ref, company_id, context=context)
3380 # generate properties function
3381 self.generate_properties(cr, uid, template_id, account_ref, company_id, context=context)
3383 # Generate Fiscal Position , Fiscal Position Accounts and Fiscal Position Taxes from templates
3384 obj_fiscal_position_template.generate_fiscal_position(cr, uid, template_id, taxes_ref, account_ref, company_id, context=context)
3386 return account_ref, taxes_ref, tax_code_ref
3388 def _create_tax_templates_from_rates(self, cr, uid, obj_wizard, company_id, context=None):
3390 This function checks if the chosen chart template is configured as containing a full set of taxes, and if
3391 it's not the case, it creates the templates for account.tax.code and for account.account.tax objects accordingly
3392 to the provided sale/purchase rates. Then it saves the new tax templates as default taxes to use for this chart
3395 :param obj_wizard: browse record of wizard to generate COA from templates
3396 :param company_id: id of the company for wich the wizard is running
3399 obj_tax_code_template = self.pool.get('account.tax.code.template')
3400 obj_tax_temp = self.pool.get('account.tax.template')
3401 chart_template = obj_wizard.chart_template_id
3403 all_parents = self._get_chart_parent_ids(cr, uid, chart_template, context=context)
3404 # create tax templates and tax code templates from purchase_tax_rate and sale_tax_rate fields
3405 if not chart_template.complete_tax_set:
3406 value = obj_wizard.sale_tax_rate
3407 ref_tax_ids = obj_tax_temp.search(cr, uid, [('type_tax_use','in', ('sale','all')), ('chart_template_id', 'in', all_parents)], context=context, order="sequence, id desc", limit=1)
3408 obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Tax %.2f%%') % value})
3409 value = obj_wizard.purchase_tax_rate
3410 ref_tax_ids = obj_tax_temp.search(cr, uid, [('type_tax_use','in', ('purchase','all')), ('chart_template_id', 'in', all_parents)], context=context, order="sequence, id desc", limit=1)
3411 obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Purchase Tax %.2f%%') % value})
3414 def execute(self, cr, uid, ids, context=None):
3416 This function is called at the confirmation of the wizard to generate the COA from the templates. It will read
3417 all the provided information to create the accounts, the banks, the journals, the taxes, the tax codes, the
3418 accounting properties... accordingly for the chosen company.
3420 if uid != SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager'):
3421 raise openerp.exceptions.AccessError(_("Only administrators can change the settings"))
3422 obj_data = self.pool.get('ir.model.data')
3423 ir_values_obj = self.pool.get('ir.values')
3424 obj_wizard = self.browse(cr, uid, ids[0])
3425 company_id = obj_wizard.company_id.id
3427 self.pool.get('res.company').write(cr, uid, [company_id], {'currency_id': obj_wizard.currency_id.id})
3429 # When we install the CoA of first company, set the currency to price types and pricelists
3431 for ref in (('product','list_price'),('product','standard_price'),('product','list0'),('purchase','list0')):
3433 tmp2 = obj_data.get_object_reference(cr, uid, *ref)
3435 self.pool[tmp2[0]].write(cr, uid, tmp2[1], {
3436 'currency_id': obj_wizard.currency_id.id
3441 # If the floats for sale/purchase rates have been filled, create templates from them
3442 self._create_tax_templates_from_rates(cr, uid, obj_wizard, company_id, context=context)
3444 # Install all the templates objects and generate the real objects
3445 acc_template_ref, taxes_ref, tax_code_ref = self._install_template(cr, uid, obj_wizard.chart_template_id.id, company_id, code_digits=obj_wizard.code_digits, obj_wizard=obj_wizard, context=context)
3447 # write values of default taxes for product as super user
3448 if obj_wizard.sale_tax and taxes_ref:
3449 ir_values_obj.set_default(cr, SUPERUSER_ID, 'product.product', "taxes_id", [taxes_ref[obj_wizard.sale_tax.id]], for_all_users=True, company_id=company_id)
3450 if obj_wizard.purchase_tax and taxes_ref:
3451 ir_values_obj.set_default(cr, SUPERUSER_ID, 'product.product', "supplier_taxes_id", [taxes_ref[obj_wizard.purchase_tax.id]], for_all_users=True, company_id=company_id)
3453 # Create Bank journals
3454 self._create_bank_journals_from_o2m(cr, uid, obj_wizard, company_id, acc_template_ref, context=context)
3457 def _prepare_bank_journal(self, cr, uid, line, current_num, default_account_id, company_id, context=None):
3459 This function prepares the value to use for the creation of a bank journal created through the wizard of
3460 generating COA from templates.
3462 :param line: dictionary containing the values encoded by the user related to his bank account
3463 :param current_num: integer corresponding to a counter of the already created bank journals through this wizard.
3464 :param default_account_id: id of the default debit.credit account created before for this journal.
3465 :param company_id: id of the company for which the wizard is running
3466 :return: mapping of field names and values
3469 obj_data = self.pool.get('ir.model.data')
3470 obj_journal = self.pool.get('account.journal')
3473 # we need to loop again to find next number for journal code
3474 # because we can't rely on the value current_num as,
3475 # its possible that we already have bank journals created (e.g. by the creation of res.partner.bank)
3476 # and the next number for account code might have been already used before for journal
3477 for num in xrange(current_num, 100):
3478 # journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
3479 journal_code = _('BNK')[:3] + str(num)
3480 ids = obj_journal.search(cr, uid, [('code', '=', journal_code), ('company_id', '=', company_id)], context=context)
3484 raise osv.except_osv(_('Error!'), _('Cannot generate an unused journal code.'))
3487 'name': line['acc_name'],
3488 'code': journal_code,
3489 'type': line['account_type'] == 'cash' and 'cash' or 'bank',
3490 'company_id': company_id,
3491 'analytic_journal_id': False,
3493 'default_credit_account_id': default_account_id,
3494 'default_debit_account_id': default_account_id,
3496 if line['currency_id']:
3497 vals['currency'] = line['currency_id']
3501 def _prepare_bank_account(self, cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=None):
3503 This function prepares the value to use for the creation of the default debit and credit accounts of a
3504 bank journal created through the wizard of generating COA from templates.
3506 :param line: dictionary containing the values encoded by the user related to his bank account
3507 :param new_code: integer corresponding to the next available number to use as account code
3508 :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3509 of the accounts that have been generated from them.
3510 :param ref_acc_bank: browse record of the account template set as root of all bank accounts for the chosen
3512 :param company_id: id of the company for which the wizard is running
3513 :return: mapping of field names and values
3516 obj_data = self.pool.get('ir.model.data')
3518 # Get the id of the user types fr-or cash and bank
3519 tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_cash')
3520 cash_type = tmp and tmp[1] or False
3521 tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_bank')
3522 bank_type = tmp and tmp[1] or False
3524 'name': line['acc_name'],
3525 'currency_id': line['currency_id'],
3527 'type': 'liquidity',
3528 'user_type': line['account_type'] == 'cash' and cash_type or bank_type,
3529 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
3530 'company_id': company_id,
3533 def _create_bank_journals_from_o2m(self, cr, uid, obj_wizard, company_id, acc_template_ref, context=None):
3535 This function creates bank journals and its accounts for each line encoded in the field bank_accounts_id of the
3538 :param obj_wizard: the current wizard that generates the COA from the templates.
3539 :param company_id: the id of the company for which the wizard is running.
3540 :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3541 of the accounts that have been generated from them.
3544 obj_acc = self.pool.get('account.account')
3545 obj_journal = self.pool.get('account.journal')
3546 code_digits = obj_wizard.code_digits
3548 # Build a list with all the data to process
3550 if obj_wizard.bank_accounts_id:
3551 for acc in obj_wizard.bank_accounts_id:
3553 'acc_name': acc.acc_name,
3554 'account_type': acc.account_type,
3555 'currency_id': acc.currency_id.id,
3557 journal_data.append(vals)
3558 ref_acc_bank = obj_wizard.chart_template_id.bank_account_view_id
3559 if journal_data and not ref_acc_bank.code:
3560 raise osv.except_osv(_('Configuration Error!'), _('You have to set a code for the bank account defined on the selected chart of accounts.'))
3563 for line in journal_data:
3564 # Seek the next available number for the account code
3566 new_code = str(ref_acc_bank.code.ljust(code_digits-len(str(current_num)), '0')) + str(current_num)
3567 ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)])
3572 # Create the default debit/credit accounts for this bank journal
3573 vals = self._prepare_bank_account(cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=context)
3574 default_account_id = obj_acc.create(cr, uid, vals, context=context)
3576 #create the bank journal
3577 vals_journal = self._prepare_bank_journal(cr, uid, line, current_num, default_account_id, company_id, context=context)
3578 obj_journal.create(cr, uid, vals_journal)
3583 class account_bank_accounts_wizard(osv.osv_memory):
3584 _name='account.bank.accounts.wizard'
3587 'acc_name': fields.char('Account Name.', size=64, required=True),
3588 'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True, ondelete='cascade'),
3589 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
3590 'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
3594 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: