[MERGE] forward port of branch 8.0 up to c92e70b
[odoo/odoo.git] / addons / account / account.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 import logging
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
25 from operator import itemgetter
26 import time
27
28 import openerp
29 from openerp import SUPERUSER_ID, api
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 as round
34
35 import openerp.addons.decimal_precision as dp
36
37 _logger = logging.getLogger(__name__)
38
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)
42
43     Returns true if it runs out of parents (no cycle), false if
44     it can recurse 100 times without ending all chains
45     """
46     level = 100
47     while len(ids):
48         cr.execute('SELECT DISTINCT parent_id '\
49                     'FROM '+self._table+' '\
50                     'WHERE id IN %s '\
51                     'AND parent_id IS NOT NULL',(tuple(ids),))
52         ids = map(itemgetter(0), cr.fetchall())
53         if not level:
54             return False
55         level -= 1
56     return True
57
58 class res_company(osv.osv):
59     _inherit = "res.company"
60     _columns = {
61         'income_currency_exchange_account_id': fields.many2one(
62             'account.account',
63             string="Gain Exchange Rate Account",
64             domain="[('type', '=', 'other')]",),
65         'expense_currency_exchange_account_id': fields.many2one(
66             'account.account',
67             string="Loss Exchange Rate Account",
68             domain="[('type', '=', 'other')]",),
69     }
70
71 class account_payment_term(osv.osv):
72     _name = "account.payment.term"
73     _description = "Payment Term"
74     _columns = {
75         'name': fields.char('Payment Term', translate=True, required=True),
76         '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."),
77         'note': fields.text('Description', translate=True),
78         'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms', copy=True),
79     }
80     _defaults = {
81         'active': 1,
82     }
83     _order = "name"
84
85     def compute(self, cr, uid, id, value, date_ref=False, context=None):
86         if not date_ref:
87             date_ref = datetime.now().strftime('%Y-%m-%d')
88         pt = self.browse(cr, uid, id, context=context)
89         amount = value
90         result = []
91         obj_precision = self.pool.get('decimal.precision')
92         prec = obj_precision.precision_get(cr, uid, 'Account')
93         for line in pt.line_ids:
94             if line.value == 'fixed':
95                 amt = round(line.value_amount, prec)
96             elif line.value == 'procent':
97                 amt = round(value * (line.value_amount/100.0), prec)
98             elif line.value == 'balance':
99                 amt = round(amount, prec)
100             if amt:
101                 next_date = (datetime.strptime(date_ref, '%Y-%m-%d') + relativedelta(days=line.days))
102                 if line.days2 < 0:
103                     next_first_date = next_date + relativedelta(day=1,months=1) #Getting 1st of next month
104                     next_date = next_first_date + relativedelta(days=line.days2)
105                 if line.days2 > 0:
106                     next_date += relativedelta(day=line.days2, months=1)
107                 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
108                 amount -= amt
109
110         amount = reduce(lambda x,y: x+y[1], result, 0.0)
111         dist = round(value-amount, prec)
112         if dist:
113             result.append( (time.strftime('%Y-%m-%d'), dist) )
114         return result
115
116 class account_payment_term_line(osv.osv):
117     _name = "account.payment.term.line"
118     _description = "Payment Term Line"
119     _columns = {
120         'value': fields.selection([('procent', 'Percent'),
121                                    ('balance', 'Balance'),
122                                    ('fixed', 'Fixed Amount')], 'Computation',
123                                    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."""),
124
125         'value_amount': fields.float('Amount To Pay', digits_compute=dp.get_precision('Payment Term'), help="For percent enter a ratio between 0-100%."),
126         'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
127             "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
128         '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)."),
129         'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True, ondelete='cascade'),
130     }
131     _defaults = {
132         'value': 'balance',
133         'days': 30,
134         'days2': 0,
135     }
136     _order = "value desc,days"
137
138     def _check_percent(self, cr, uid, ids, context=None):
139         obj = self.browse(cr, uid, ids[0], context=context)
140         if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 100.0):
141             return False
142         return True
143
144     _constraints = [
145         (_check_percent, 'Percentages for Payment Term Line must be between 0 and 100.', ['value_amount']),
146     ]
147
148
149 class account_account_type(osv.osv):
150     _name = "account.account.type"
151     _description = "Account Type"
152
153     def _get_financial_report_ref(self, cr, uid, context=None):
154         obj_data = self.pool.get('ir.model.data')
155         obj_financial_report = self.pool.get('account.financial.report')
156         financial_report_ref = {}
157         for key, financial_report in [
158                     ('asset','account_financial_report_assets0'),
159                     ('liability','account_financial_report_liability0'),
160                     ('income','account_financial_report_income0'),
161                     ('expense','account_financial_report_expense0'),
162                 ]:
163             try:
164                 financial_report_ref[key] = obj_financial_report.browse(cr, uid,
165                     obj_data.get_object_reference(cr, uid, 'account', financial_report)[1],
166                     context=context)
167             except ValueError:
168                 pass
169         return financial_report_ref
170
171     def _get_current_report_type(self, cr, uid, ids, name, arg, context=None):
172         res = {}
173         financial_report_ref = self._get_financial_report_ref(cr, uid, context=context)
174         for record in self.browse(cr, uid, ids, context=context):
175             res[record.id] = 'none'
176             for key, financial_report in financial_report_ref.items():
177                 list_ids = [x.id for x in financial_report.account_type_ids]
178                 if record.id in list_ids:
179                     res[record.id] = key
180         return res
181
182     def _save_report_type(self, cr, uid, account_type_id, field_name, field_value, arg, context=None):
183         field_value = field_value or 'none'
184         obj_financial_report = self.pool.get('account.financial.report')
185         #unlink if it exists somewhere in the financial reports related to BS or PL
186         financial_report_ref = self._get_financial_report_ref(cr, uid, context=context)
187         for key, financial_report in financial_report_ref.items():
188             list_ids = [x.id for x in financial_report.account_type_ids]
189             if account_type_id in list_ids:
190                 obj_financial_report.write(cr, uid, [financial_report.id], {'account_type_ids': [(3, account_type_id)]})
191         #write it in the good place
192         if field_value != 'none':
193             return obj_financial_report.write(cr, uid, [financial_report_ref[field_value].id], {'account_type_ids': [(4, account_type_id)]})
194
195     _columns = {
196         'name': fields.char('Account Type', required=True, translate=True),
197         'code': fields.char('Code', size=32, required=True, select=True),
198         '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.
199
200  'None' means that nothing will be done.
201  'Balance' will generally be used for cash accounts.
202  'Detail' will copy each existing journal item of the previous year, even the reconciled ones.
203  'Unreconciled' will copy only the journal items that were unreconciled on the first day of the new fiscal year."""),
204         'report_type': fields.function(_get_current_report_type, fnct_inv=_save_report_type, type='selection', string='P&L / BS Category', store=True,
205             selection= [('none','/'),
206                         ('income', _('Profit & Loss (Income account)')),
207                         ('expense', _('Profit & Loss (Expense account)')),
208                         ('asset', _('Balance Sheet (Asset account)')),
209                         ('liability', _('Balance Sheet (Liability account)'))], help="This field is used to generate legal reports: profit and loss, balance sheet.", required=True),
210         'note': fields.text('Description'),
211     }
212     _defaults = {
213         'close_method': 'none',
214         'report_type': 'none',
215     }
216     _order = "code"
217
218
219 def _code_get(self, cr, uid, context=None):
220     acc_type_obj = self.pool.get('account.account.type')
221     ids = acc_type_obj.search(cr, uid, [])
222     res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context=context)
223     return [(r['code'], r['name']) for r in res]
224
225 #----------------------------------------------------------
226 # Accounts
227 #----------------------------------------------------------
228
229 class account_account(osv.osv):
230     _order = "parent_left"
231     _parent_order = "code"
232     _name = "account.account"
233     _description = "Account"
234     _parent_store = True
235
236     def search(self, cr, uid, args, offset=0, limit=None, order=None,
237             context=None, count=False):
238         if context is None:
239             context = {}
240         pos = 0
241
242         while pos < len(args):
243
244             if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
245                 args[pos] = ('code', '=like', tools.ustr(args[pos][2].replace('%', ''))+'%')
246             if args[pos][0] == 'journal_id':
247                 if not args[pos][2]:
248                     del args[pos]
249                     continue
250                 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2], context=context)
251                 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
252                     args[pos] = ('type','not in',('consolidation','view'))
253                     continue
254                 ids3 = map(lambda x: x.id, jour.type_control_ids)
255                 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
256                 ids1 += map(lambda x: x.id, jour.account_control_ids)
257                 args[pos] = ('id', 'in', ids1)
258             pos += 1
259
260         if context and context.has_key('consolidate_children'): #add consolidated children of accounts
261             ids = super(account_account, self).search(cr, uid, args, offset, limit,
262                 order, context=context, count=count)
263             for consolidate_child in self.browse(cr, uid, context['account_id'], context=context).child_consol_ids:
264                 ids.append(consolidate_child.id)
265             return ids
266
267         return super(account_account, self).search(cr, uid, args, offset, limit,
268                 order, context=context, count=count)
269
270     def _get_children_and_consol(self, cr, uid, ids, context=None):
271         #this function search for all the children and all consolidated children (recursively) of the given account ids
272         ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
273         ids3 = []
274         for rec in self.browse(cr, uid, ids2, context=context):
275             for child in rec.child_consol_ids:
276                 ids3.append(child.id)
277         if ids3:
278             ids3 = self._get_children_and_consol(cr, uid, ids3, context)
279         return ids2 + ids3
280
281     def __compute(self, cr, uid, ids, field_names, arg=None, context=None,
282                   query='', query_params=()):
283         """ compute the balance, debit and/or credit for the provided
284         account ids
285         Arguments:
286         `ids`: account ids
287         `field_names`: the fields to compute (a list of any of
288                        'balance', 'debit' and 'credit')
289         `arg`: unused fields.function stuff
290         `query`: additional query filter (as a string)
291         `query_params`: parameters for the provided query string
292                         (__compute will handle their escaping) as a
293                         tuple
294         """
295         mapping = {
296             'balance': "COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance",
297             'debit': "COALESCE(SUM(l.debit), 0) as debit",
298             'credit': "COALESCE(SUM(l.credit), 0) as credit",
299             # by convention, foreign_balance is 0 when the account has no secondary currency, because the amounts may be in different currencies
300             '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",
301         }
302         #get all the necessary accounts
303         children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
304         #compute for each account the balance/debit/credit from the move lines
305         accounts = {}
306         res = {}
307         null_result = dict((fn, 0.0) for fn in field_names)
308         if children_and_consolidated:
309             aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
310
311             wheres = [""]
312             if query.strip():
313                 wheres.append(query.strip())
314             if aml_query.strip():
315                 wheres.append(aml_query.strip())
316             filters = " AND ".join(wheres)
317             # IN might not work ideally in case there are too many
318             # children_and_consolidated, in that case join on a
319             # values() e.g.:
320             # SELECT l.account_id as id FROM account_move_line l
321             # INNER JOIN (VALUES (id1), (id2), (id3), ...) AS tmp (id)
322             # ON l.account_id = tmp.id
323             # or make _get_children_and_consol return a query and join on that
324             request = ("SELECT l.account_id as id, " +\
325                        ', '.join(mapping.values()) +
326                        " FROM account_move_line l" \
327                        " WHERE l.account_id IN %s " \
328                             + filters +
329                        " GROUP BY l.account_id")
330             params = (tuple(children_and_consolidated),) + query_params
331             cr.execute(request, params)
332
333             for row in cr.dictfetchall():
334                 accounts[row['id']] = row
335
336             # consolidate accounts with direct children
337             children_and_consolidated.reverse()
338             brs = list(self.browse(cr, uid, children_and_consolidated, context=context))
339             sums = {}
340             currency_obj = self.pool.get('res.currency')
341             while brs:
342                 current = brs.pop(0)
343 #                can_compute = True
344 #                for child in current.child_id:
345 #                    if child.id not in sums:
346 #                        can_compute = False
347 #                        try:
348 #                            brs.insert(0, brs.pop(brs.index(child)))
349 #                        except ValueError:
350 #                            brs.insert(0, child)
351 #                if can_compute:
352                 for fn in field_names:
353                     sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
354                     for child in current.child_id:
355                         if child.company_id.currency_id.id == current.company_id.currency_id.id:
356                             sums[current.id][fn] += sums[child.id][fn]
357                         else:
358                             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)
359
360                 # as we have to relay on values computed before this is calculated separately than previous fields
361                 if current.currency_id and current.exchange_rate and \
362                             ('adjusted_balance' in field_names or 'unrealized_gain_loss' in field_names):
363                     # Computing Adjusted Balance and Unrealized Gains and losses
364                     # Adjusted Balance = Foreign Balance / Exchange Rate
365                     # Unrealized Gains and losses = Adjusted Balance - Balance
366                     adj_bal = sums[current.id].get('foreign_balance', 0.0) / current.exchange_rate
367                     sums[current.id].update({'adjusted_balance': adj_bal, 'unrealized_gain_loss': adj_bal - sums[current.id].get('balance', 0.0)})
368
369             for id in ids:
370                 res[id] = sums.get(id, null_result)
371         else:
372             for id in ids:
373                 res[id] = null_result
374         return res
375
376     def _get_company_currency(self, cr, uid, ids, field_name, arg, context=None):
377         result = {}
378         for rec in self.browse(cr, uid, ids, context=context):
379             result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.symbol)
380         return result
381
382     def _get_child_ids(self, cr, uid, ids, field_name, arg, context=None):
383         result = {}
384         for record in self.browse(cr, uid, ids, context=context):
385             if record.child_parent_ids:
386                 result[record.id] = [x.id for x in record.child_parent_ids]
387             else:
388                 result[record.id] = []
389
390             if record.child_consol_ids:
391                 for acc in record.child_consol_ids:
392                     if acc.id not in result[record.id]:
393                         result[record.id].append(acc.id)
394
395         return result
396
397     def _get_level(self, cr, uid, ids, field_name, arg, context=None):
398         res = {}
399         for account in self.browse(cr, uid, ids, context=context):
400             #we may not know the level of the parent at the time of computation, so we
401             # can't simply do res[account.id] = account.parent_id.level + 1
402             level = 0
403             parent = account.parent_id
404             while parent:
405                 level += 1
406                 parent = parent.parent_id
407             res[account.id] = level
408         return res
409
410     def _set_credit_debit(self, cr, uid, account_id, name, value, arg, context=None):
411         if context.get('config_invisible', True):
412             return True
413
414         account = self.browse(cr, uid, account_id, context=context)
415         diff = value - getattr(account,name)
416         if not diff:
417             return True
418
419         journal_obj = self.pool.get('account.journal')
420         jids = journal_obj.search(cr, uid, [('type','=','situation'),('centralisation','=',1),('company_id','=',account.company_id.id)], context=context)
421         if not jids:
422             raise osv.except_osv(_('Error!'),_("You need an Opening journal with centralisation checked to set the initial balance."))
423
424         period_obj = self.pool.get('account.period')
425         pids = period_obj.search(cr, uid, [('special','=',True),('company_id','=',account.company_id.id)], context=context)
426         if not pids:
427             raise osv.except_osv(_('Error!'),_("There is no opening/closing period defined, please create one to set the initial balance."))
428
429         move_obj = self.pool.get('account.move.line')
430         move_id = move_obj.search(cr, uid, [
431             ('journal_id','=',jids[0]),
432             ('period_id','=',pids[0]),
433             ('account_id','=', account_id),
434             (name,'>', 0.0),
435             ('name','=', _('Opening Balance'))
436         ], context=context)
437         if move_id:
438             move = move_obj.browse(cr, uid, move_id[0], context=context)
439             move_obj.write(cr, uid, move_id[0], {
440                 name: diff+getattr(move,name)
441             }, context=context)
442         else:
443             if diff<0.0:
444                 raise osv.except_osv(_('Error!'),_("Unable to adapt the initial balance (negative value)."))
445             nameinv = (name=='credit' and 'debit') or 'credit'
446             move_id = move_obj.create(cr, uid, {
447                 'name': _('Opening Balance'),
448                 'account_id': account_id,
449                 'journal_id': jids[0],
450                 'period_id': pids[0],
451                 name: diff,
452                 nameinv: 0.0
453             }, context=context)
454         return True
455
456     _columns = {
457         'name': fields.char('Name', required=True, select=True),
458         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
459         'code': fields.char('Code', size=64, required=True, select=1),
460         'type': fields.selection([
461             ('view', 'View'),
462             ('other', 'Regular'),
463             ('receivable', 'Receivable'),
464             ('payable', 'Payable'),
465             ('liquidity','Liquidity'),
466             ('consolidation', 'Consolidation'),
467             ('closed', 'Closed'),
468         ], 'Internal Type', required=True, help="The 'Internal Type' is used for features available on "\
469             "different types of accounts: view can not have journal items, consolidation are accounts that "\
470             "can have children accounts for multi-company consolidations, payable/receivable are for "\
471             "partners accounts (for debit/credit computations), closed for depreciated accounts."),
472         'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
473             help="Account Type is used for information purpose, to generate "
474               "country-specific legal reports, and set the rules to close a fiscal year and generate opening entries."),
475         'financial_report_ids': fields.many2many('account.financial.report', 'account_account_financial_report', 'account_id', 'report_line_id', 'Financial Reports'),
476         'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
477         'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
478         'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
479         'child_id': fields.function(_get_child_ids, type='many2many', relation="account.account", string="Child Accounts"),
480         'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Balance', multi='balance'),
481         'credit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Credit', multi='balance'),
482         'debit': fields.function(__compute, fnct_inv=_set_credit_debit, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'),
483         'foreign_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Foreign Balance', multi='balance',
484                                            help="Total amount (in Secondary currency) for transactions held in secondary currency for this account."),
485         'adjusted_balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Adjusted Balance', multi='balance',
486                                             help="Total amount (in Company currency) for transactions held in secondary currency for this account."),
487         'unrealized_gain_loss': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Unrealized Gain or Loss', multi='balance',
488                                                 help="Value of Loss or Gain due to changes in exchange rate when doing multi-currency transactions."),
489         'reconcile': fields.boolean('Allow Reconciliation', help="Check this box if this account allows reconciliation of journal items."),
490         'exchange_rate': fields.related('currency_id', 'rate', type='float', string='Exchange Rate', digits=(12,6)),
491         'shortcut': fields.char('Shortcut', size=12),
492         'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
493             'account_id', 'tax_id', 'Default Taxes'),
494         'note': fields.text('Internal Notes'),
495         'company_currency_id': fields.function(_get_company_currency, type='many2one', relation='res.currency', string='Company Currency'),
496         'company_id': fields.many2one('res.company', 'Company', required=True),
497         '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."),
498
499         'parent_left': fields.integer('Parent Left', select=1),
500         'parent_right': fields.integer('Parent Right', select=1),
501         'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
502             help=
503             'This will select how the current currency rate for outgoing transactions is computed. '\
504             'In most countries the legal method is "average" but only a few software systems are able to '\
505             'manage this. So if you import from another software system you may have to use the rate at date. ' \
506             'Incoming transactions always use the rate at date.', \
507             required=True),
508         'level': fields.function(_get_level, string='Level', method=True, type='integer',
509              store={
510                     'account.account': (_get_children_and_consol, ['level', 'parent_id'], 10),
511                    }),
512     }
513
514     _defaults = {
515         'type': 'other',
516         'reconcile': False,
517         'active': True,
518         'currency_mode': 'current',
519         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
520     }
521
522     def _check_recursion(self, cr, uid, ids, context=None):
523         obj_self = self.browse(cr, uid, ids[0], context=context)
524         p_id = obj_self.parent_id and obj_self.parent_id.id
525         if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
526             return False
527         while(ids):
528             cr.execute('SELECT DISTINCT child_id '\
529                        'FROM account_account_consol_rel '\
530                        'WHERE parent_id IN %s', (tuple(ids),))
531             child_ids = map(itemgetter(0), cr.fetchall())
532             c_ids = child_ids
533             if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
534                 return False
535             while len(c_ids):
536                 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
537                 if p_id and (p_id in s_ids):
538                     return False
539                 c_ids = s_ids
540             ids = child_ids
541         return True
542
543     def _check_type(self, cr, uid, ids, context=None):
544         if context is None:
545             context = {}
546         accounts = self.browse(cr, uid, ids, context=context)
547         for account in accounts:
548             if account.child_id and account.type not in ('view', 'consolidation'):
549                 return False
550         return True
551
552     def _check_account_type(self, cr, uid, ids, context=None):
553         for account in self.browse(cr, uid, ids, context=context):
554             if account.type in ('receivable', 'payable') and account.user_type.close_method != 'unreconciled':
555                 return False
556         return True
557
558     def _check_company_account(self, cr, uid, ids, context=None):
559         for account in self.browse(cr, uid, ids, context=context):
560             if account.parent_id:
561                 if account.company_id != account.parent_id.company_id:
562                     return False
563         return True
564
565     _constraints = [
566         (_check_recursion, 'Error!\nYou cannot create recursive accounts.', ['parent_id']),
567         (_check_type, 'Configuration Error!\nYou cannot define children to an account with internal type different of "View".', ['type']),
568         (_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']),
569         (_check_company_account, 'Error!\nYou cannot create an account which has parent account of different company.', ['parent_id']),
570     ]
571     _sql_constraints = [
572         ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
573     ]
574     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
575         if not args:
576             args = []
577         args = args[:]
578         ids = []
579         try:
580             if name and str(name).startswith('partner:'):
581                 part_id = int(name.split(':')[1])
582                 part = self.pool.get('res.partner').browse(cr, user, part_id, context=context)
583                 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
584                 name = False
585             if name and str(name).startswith('type:'):
586                 type = name.split(':')[1]
587                 args += [('type', '=', type)]
588                 name = False
589         except:
590             pass
591         if name:
592             if operator not in expression.NEGATIVE_TERM_OPERATORS:
593                 plus_percent = lambda n: n+'%'
594                 code_op, code_conv = {
595                     'ilike': ('=ilike', plus_percent),
596                     'like': ('=like', plus_percent),
597                 }.get(operator, (operator, lambda n: n))
598
599                 ids = self.search(cr, user, ['|', ('code', code_op, code_conv(name)), '|', ('shortcut', '=', name), ('name', operator, name)]+args, limit=limit)
600
601                 if not ids and len(name.split()) >= 2:
602                     #Separating code and name of account for searching
603                     operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
604                     ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
605             else:
606                 ids = self.search(cr, user, ['&','!', ('code', '=like', name+"%"), ('name', operator, name)]+args, limit=limit)
607                 # as negation want to restric, do if already have results
608                 if ids and len(name.split()) >= 2:
609                     operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
610                     ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2), ('id', 'in', ids)]+ args, limit=limit)
611         else:
612             ids = self.search(cr, user, args, context=context, limit=limit)
613         return self.name_get(cr, user, ids, context=context)
614
615     def name_get(self, cr, uid, ids, context=None):
616         if not ids:
617             return []
618         if isinstance(ids, (int, long)):
619                     ids = [ids]
620         reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
621         res = []
622         for record in reads:
623             name = record['name']
624             if record['code']:
625                 name = record['code'] + ' ' + name
626             res.append((record['id'], name))
627         return res
628
629     def copy(self, cr, uid, id, default=None, context=None, done_list=None, local=False):
630         default = {} if default is None else default.copy()
631         if done_list is None:
632             done_list = []
633         account = self.browse(cr, uid, id, context=context)
634         new_child_ids = []
635         default.update(code=_("%s (copy)") % (account['code'] or ''))
636         if not local:
637             done_list = []
638         if account.id in done_list:
639             return False
640         done_list.append(account.id)
641         if account:
642             for child in account.child_id:
643                 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
644                 if child_ids:
645                     new_child_ids.append(child_ids)
646             default['child_parent_ids'] = [(6, 0, new_child_ids)]
647         else:
648             default['child_parent_ids'] = False
649         return super(account_account, self).copy(cr, uid, id, default, context=context)
650
651     def _check_moves(self, cr, uid, ids, method, context=None):
652         line_obj = self.pool.get('account.move.line')
653         account_ids = self.search(cr, uid, [('id', 'child_of', ids)], context=context)
654
655         if line_obj.search(cr, uid, [('account_id', 'in', account_ids)], context=context):
656             if method == 'write':
657                 raise osv.except_osv(_('Error!'), _('You cannot deactivate an account that contains journal items.'))
658             elif method == 'unlink':
659                 raise osv.except_osv(_('Error!'), _('You cannot remove an account that contains journal items.'))
660         #Checking whether the account is set as a property to any Partner or not
661         values = ['account.account,%s' % (account_id,) for account_id in ids]
662         partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','in', values)], context=context)
663         if partner_prop_acc:
664             raise osv.except_osv(_('Warning!'), _('You cannot remove/deactivate an account which is set on a customer or supplier.'))
665         return True
666
667     def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
668         restricted_groups = ['consolidation','view']
669         line_obj = self.pool.get('account.move.line')
670         for account in self.browse(cr, uid, ids, context=context):
671             old_type = account.type
672             account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
673             if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
674                 #Check for 'Closed' type
675                 if old_type == 'closed' and new_type !='closed':
676                     raise osv.except_osv(_('Warning!'), _("You cannot change the type of account from 'Closed' to any other type as it contains journal items!"))
677                 # Forbid to change an account type for restricted_groups as it contains journal items (or if one of its children does)
678                 if (new_type in restricted_groups):
679                     raise osv.except_osv(_('Warning!'), _("You cannot change the type of account to '%s' type as it contains journal items!") % (new_type,))
680
681         return True
682
683     # For legal reason (forbiden to modify journal entries which belongs to a closed fy or period), Forbid to modify
684     # the code of an account if journal entries have been already posted on this account. This cannot be simply 
685     # 'configurable' since it can lead to a lack of confidence in Odoo and this is what we want to change.
686     def _check_allow_code_change(self, cr, uid, ids, context=None):
687         line_obj = self.pool.get('account.move.line')
688         for account in self.browse(cr, uid, ids, context=context):
689             account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])], context=context)
690             if line_obj.search(cr, uid, [('account_id', 'in', account_ids)], context=context):
691                 raise osv.except_osv(_('Warning !'), _("You cannot change the code of account which contains journal items!"))
692         return True
693
694     def write(self, cr, uid, ids, vals, context=None):
695         if context is None:
696             context = {}
697         if not ids:
698             return True
699         if isinstance(ids, (int, long)):
700             ids = [ids]
701
702         # Dont allow changing the company_id when account_move_line already exist
703         if 'company_id' in vals:
704             move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)], context=context)
705             if move_lines:
706                 # Allow the write if the value is the same
707                 for i in [i['company_id'][0] for i in self.read(cr,uid,ids,['company_id'], context=context)]:
708                     if vals['company_id']!=i:
709                         raise osv.except_osv(_('Warning!'), _('You cannot change the owner company of an account that already contains journal items.'))
710         if 'active' in vals and not vals['active']:
711             self._check_moves(cr, uid, ids, "write", context=context)
712         if 'type' in vals.keys():
713             self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
714         if 'code' in vals.keys():
715             self._check_allow_code_change(cr, uid, ids, context=context)
716         return super(account_account, self).write(cr, uid, ids, vals, context=context)
717
718     def unlink(self, cr, uid, ids, context=None):
719         self._check_moves(cr, uid, ids, "unlink", context=context)
720         return super(account_account, self).unlink(cr, uid, ids, context=context)
721
722
723 class account_journal(osv.osv):
724     _name = "account.journal"
725     _description = "Journal"
726     _columns = {
727         'with_last_closing_balance': fields.boolean('Opening With Last Closing Balance', help="For cash or bank journal, this option should be unchecked when the starting balance should always set to 0 for new documents."),
728         'name': fields.char('Journal Name', required=True),
729         'code': fields.char('Code', size=5, required=True, help="The code will be displayed on reports."),
730         '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,
731                                  help="Select 'Sale' for customer invoices journals."\
732                                  " Select 'Purchase' for supplier invoices journals."\
733                                  " Select 'Cash' or 'Bank' for journals that are used in customer or supplier payments."\
734                                  " Select 'General' for miscellaneous operations journals."\
735                                  " Select 'Opening/Closing Situation' for entries generated for new fiscal years."),
736         'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
737         'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
738         'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
739         'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
740         '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."),
741         '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"),
742         '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."),
743         '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, copy=False),
744         'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
745         'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
746         'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
747         '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.'),
748         'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
749         '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'),
750         'profit_account_id' : fields.many2one('account.account', 'Profit Account'),
751         'loss_account_id' : fields.many2one('account.account', 'Loss Account'),
752         'internal_account_id' : fields.many2one('account.account', 'Internal Transfers Account', select=1),
753         'cash_control' : fields.boolean('Cash Control', help='If you want the journal should be control at opening/closing, check this option'),
754         'analytic_journal_id':fields.many2one('account.analytic.journal','Analytic Journal', help="Journal for analytic entries"),
755     }
756
757     _defaults = {
758         'cash_control' : False,
759         'with_last_closing_balance' : True,
760         'user_id': lambda self, cr, uid, context: uid,
761         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
762     }
763     _sql_constraints = [
764         ('code_company_uniq', 'unique (code, company_id)', 'The code of the journal must be unique per company !'),
765         ('name_company_uniq', 'unique (name, company_id)', 'The name of the journal must be unique per company !'),
766     ]
767
768     _order = 'code'
769
770     def _check_currency(self, cr, uid, ids, context=None):
771         for journal in self.browse(cr, uid, ids, context=context):
772             if journal.currency:
773                 if journal.default_credit_account_id and not journal.default_credit_account_id.currency_id.id == journal.currency.id:
774                     return False
775                 if journal.default_debit_account_id and not journal.default_debit_account_id.currency_id.id == journal.currency.id:
776                     return False
777         return True
778
779     _constraints = [
780         (_check_currency, 'Configuration error!\nThe currency chosen should be shared by the default accounts too.', ['currency','default_debit_account_id','default_credit_account_id']),
781     ]
782
783     def copy(self, cr, uid, id, default=None, context=None):
784         default = dict(context or {})
785         journal = self.browse(cr, uid, id, context=context)
786         default.update(
787             code=_("%s (copy)") % (journal['code'] or ''),
788             name=_("%s (copy)") % (journal['name'] or ''))
789         return super(account_journal, self).copy(cr, uid, id, default, context=context)
790
791     def write(self, cr, uid, ids, vals, context=None):
792         if context is None:
793             context = {}
794         if isinstance(ids, (int, long)):
795             ids = [ids]
796         for journal in self.browse(cr, uid, ids, context=context):
797             if 'company_id' in vals and journal.company_id.id != vals['company_id']:
798                 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
799                 if move_lines:
800                     raise osv.except_osv(_('Warning!'), _('This journal already contains items, therefore you cannot modify its company field.'))
801         return super(account_journal, self).write(cr, uid, ids, vals, context=context)
802
803     def create_sequence(self, cr, uid, vals, context=None):
804         """ Create new no_gap entry sequence for every new Joural
805         """
806         # in account.journal code is actually the prefix of the sequence
807         # whereas ir.sequence code is a key to lookup global sequences.
808         prefix = vals['code'].upper()
809
810         seq = {
811             'name': vals['name'],
812             'implementation':'no_gap',
813             'prefix': prefix + "/%(year)s/",
814             'padding': 4,
815             'number_increment': 1
816         }
817         if 'company_id' in vals:
818             seq['company_id'] = vals['company_id']
819         return self.pool.get('ir.sequence').create(cr, uid, seq)
820
821     def create(self, cr, uid, vals, context=None):
822         if not 'sequence_id' in vals or not vals['sequence_id']:
823             # if we have the right to create a journal, we should be able to
824             # create it's sequence.
825             vals.update({'sequence_id': self.create_sequence(cr, SUPERUSER_ID, vals, context)})
826         return super(account_journal, self).create(cr, uid, vals, context)
827
828     def name_get(self, cr, user, ids, context=None):
829         """
830         Returns a list of tupples containing id, name.
831         result format: {[(id, name), (id, name), ...]}
832
833         @param cr: A database cursor
834         @param user: ID of the user currently logged in
835         @param ids: list of ids for which name should be read
836         @param context: context arguments, like lang, time zone
837
838         @return: Returns a list of tupples containing id, name
839         """
840         if not ids:
841             return []
842         if isinstance(ids, (int, long)):
843             ids = [ids]
844         result = self.browse(cr, user, ids, context=context)
845         res = []
846         for rs in result:
847             if rs.currency:
848                 currency = rs.currency
849             else:
850                 currency = rs.company_id.currency_id
851             name = "%s (%s)" % (rs.name, currency.name)
852             res += [(rs.id, name)]
853         return res
854
855     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
856         if not args:
857             args = []
858         if operator in expression.NEGATIVE_TERM_OPERATORS:
859             domain = [('code', operator, name), ('name', operator, name)]
860         else:
861             domain = ['|', ('code', operator, name), ('name', operator, name)]
862         ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
863         return self.name_get(cr, user, ids, context=context)
864
865
866 class account_fiscalyear(osv.osv):
867     _name = "account.fiscalyear"
868     _description = "Fiscal Year"
869     _columns = {
870         'name': fields.char('Fiscal Year', required=True),
871         'code': fields.char('Code', size=6, required=True),
872         'company_id': fields.many2one('res.company', 'Company', required=True),
873         'date_start': fields.date('Start Date', required=True),
874         'date_stop': fields.date('End Date', required=True),
875         'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
876         'state': fields.selection([('draft','Open'), ('done','Closed')], 'Status', readonly=True, copy=False),
877         'end_journal_period_id': fields.many2one(
878              'account.journal.period', 'End of Year Entries Journal',
879              readonly=True, copy=False),
880     }
881     _defaults = {
882         'state': 'draft',
883         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
884     }
885     _order = "date_start, id"
886
887
888     def _check_duration(self, cr, uid, ids, context=None):
889         obj_fy = self.browse(cr, uid, ids[0], context=context)
890         if obj_fy.date_stop < obj_fy.date_start:
891             return False
892         return True
893
894     _constraints = [
895         (_check_duration, 'Error!\nThe start date of a fiscal year must precede its end date.', ['date_start','date_stop'])
896     ]
897
898     def create_period3(self, cr, uid, ids, context=None):
899         return self.create_period(cr, uid, ids, context, 3)
900
901     def create_period(self, cr, uid, ids, context=None, interval=1):
902         period_obj = self.pool.get('account.period')
903         for fy in self.browse(cr, uid, ids, context=context):
904             ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
905             period_obj.create(cr, uid, {
906                     'name':  "%s %s" % (_('Opening Period'), ds.strftime('%Y')),
907                     'code': ds.strftime('00/%Y'),
908                     'date_start': ds,
909                     'date_stop': ds,
910                     'special': True,
911                     'fiscalyear_id': fy.id,
912                 })
913             while ds.strftime('%Y-%m-%d') < fy.date_stop:
914                 de = ds + relativedelta(months=interval, days=-1)
915
916                 if de.strftime('%Y-%m-%d') > fy.date_stop:
917                     de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
918
919                 period_obj.create(cr, uid, {
920                     'name': ds.strftime('%m/%Y'),
921                     'code': ds.strftime('%m/%Y'),
922                     'date_start': ds.strftime('%Y-%m-%d'),
923                     'date_stop': de.strftime('%Y-%m-%d'),
924                     'fiscalyear_id': fy.id,
925                 })
926                 ds = ds + relativedelta(months=interval)
927         return True
928
929     def find(self, cr, uid, dt=None, exception=True, context=None):
930         res = self.finds(cr, uid, dt, exception, context=context)
931         return res and res[0] or False
932
933     def finds(self, cr, uid, dt=None, exception=True, context=None):
934         if context is None: context = {}
935         if not dt:
936             dt = fields.date.context_today(self,cr,uid,context=context)
937         args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
938         if context.get('company_id', False):
939             company_id = context['company_id']
940         else:
941             company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
942         args.append(('company_id', '=', company_id))
943         ids = self.search(cr, uid, args, context=context)
944         if not ids:
945             if exception:
946                 model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_fiscalyear')
947                 msg = _('No accounting period is covering this date: %s.') % dt
948                 raise openerp.exceptions.RedirectWarning(msg, action_id, _(' Configure Fiscal Year Now'))
949             else:
950                 return []
951         return ids
952
953     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
954         if args is None:
955             args = []
956         if operator in expression.NEGATIVE_TERM_OPERATORS:
957             domain = [('code', operator, name), ('name', operator, name)]
958         else:
959             domain = ['|', ('code', operator, name), ('name', operator, name)]
960         ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
961         return self.name_get(cr, user, ids, context=context)
962
963
964 class account_period(osv.osv):
965     _name = "account.period"
966     _description = "Account period"
967     _columns = {
968         'name': fields.char('Period Name', required=True),
969         'code': fields.char('Code', size=12),
970         'special': fields.boolean('Opening/Closing Period',help="These periods can overlap."),
971         'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
972         'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
973         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
974         'state': fields.selection([('draft','Open'), ('done','Closed')], 'Status', readonly=True, copy=False,
975                                   help='When monthly periods are created. The status is \'Draft\'. At the end of monthly period it is in \'Done\' status.'),
976         'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
977     }
978     _defaults = {
979         'state': 'draft',
980     }
981     _order = "date_start, special desc"
982     _sql_constraints = [
983         ('name_company_uniq', 'unique(name, company_id)', 'The name of the period must be unique per company!'),
984     ]
985
986     def _check_duration(self,cr,uid,ids,context=None):
987         obj_period = self.browse(cr, uid, ids[0], context=context)
988         if obj_period.date_stop < obj_period.date_start:
989             return False
990         return True
991
992     def _check_year_limit(self,cr,uid,ids,context=None):
993         for obj_period in self.browse(cr, uid, ids, context=context):
994             if obj_period.special:
995                 continue
996
997             if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
998                obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
999                obj_period.fiscalyear_id.date_start > obj_period.date_start or \
1000                obj_period.fiscalyear_id.date_start > obj_period.date_stop:
1001                 return False
1002
1003             pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
1004             for period in self.browse(cr, uid, pids):
1005                 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
1006                     return False
1007         return True
1008
1009     _constraints = [
1010         (_check_duration, 'Error!\nThe duration of the Period(s) is/are invalid.', ['date_stop']),
1011         (_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'])
1012     ]
1013
1014     @api.returns('self')
1015     def next(self, cr, uid, period, step, context=None):
1016         ids = self.search(cr, uid, [('date_start','>',period.date_start)])
1017         if len(ids)>=step:
1018             return ids[step-1]
1019         return False
1020
1021     @api.returns('self')
1022     def find(self, cr, uid, dt=None, context=None):
1023         if context is None: context = {}
1024         if not dt:
1025             dt = fields.date.context_today(self, cr, uid, context=context)
1026         args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
1027         if context.get('company_id', False):
1028             args.append(('company_id', '=', context['company_id']))
1029         else:
1030             company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
1031             args.append(('company_id', '=', company_id))
1032         result = []
1033         if context.get('account_period_prefer_normal', True):
1034             # look for non-special periods first, and fallback to all if no result is found
1035             result = self.search(cr, uid, args + [('special', '=', False)], context=context)
1036         if not result:
1037             result = self.search(cr, uid, args, context=context)
1038         if not result:
1039             model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_period')
1040             msg = _('No accounting period is covering this date: %s.') % dt
1041             raise openerp.exceptions.RedirectWarning(msg, action_id, _('Configure Periods Now'))
1042         return result
1043
1044     def action_draft(self, cr, uid, ids, context=None):
1045         mode = 'draft'
1046         for period in self.browse(cr, uid, ids):
1047             if period.fiscalyear_id.state == 'done':
1048                 raise osv.except_osv(_('Warning!'), _('You can not re-open a period which belongs to closed fiscal year'))
1049         cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),))
1050         cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),))
1051         self.invalidate_cache(cr, uid, context=context)
1052         return True
1053
1054     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1055         if args is None:
1056             args = []
1057         if operator in expression.NEGATIVE_TERM_OPERATORS:
1058             domain = [('code', operator, name), ('name', operator, name)]
1059         else:
1060             domain = ['|', ('code', operator, name), ('name', operator, name)]
1061         ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
1062         return self.name_get(cr, user, ids, context=context)
1063
1064     def write(self, cr, uid, ids, vals, context=None):
1065         if 'company_id' in vals:
1066             move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
1067             if move_lines:
1068                 raise osv.except_osv(_('Warning!'), _('This journal already contains items for this period, therefore you cannot modify its company field.'))
1069         return super(account_period, self).write(cr, uid, ids, vals, context=context)
1070
1071     def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
1072         if period_from_id == period_to_id:
1073             return [period_from_id]
1074         period_from = self.browse(cr, uid, period_from_id)
1075         period_date_start = period_from.date_start
1076         company1_id = period_from.company_id.id
1077         period_to = self.browse(cr, uid, period_to_id)
1078         period_date_stop = period_to.date_stop
1079         company2_id = period_to.company_id.id
1080         if company1_id != company2_id:
1081             raise osv.except_osv(_('Error!'), _('You should choose the periods that belong to the same company.'))
1082         if period_date_start > period_date_stop:
1083             raise osv.except_osv(_('Error!'), _('Start period should precede then end period.'))
1084
1085         # /!\ We do not include a criterion on the company_id field below, to allow producing consolidated reports
1086         # on multiple companies. It will only work when start/end periods are selected and no fiscal year is chosen.
1087
1088         #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).
1089         if period_from.special:
1090             return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop)])
1091         return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('special', '=', False)])
1092
1093
1094 class account_journal_period(osv.osv):
1095     _name = "account.journal.period"
1096     _description = "Journal Period"
1097
1098     def _icon_get(self, cr, uid, ids, field_name, arg=None, context=None):
1099         result = {}.fromkeys(ids, 'STOCK_NEW')
1100         for r in self.read(cr, uid, ids, ['state']):
1101             result[r['id']] = {
1102                 'draft': 'STOCK_NEW',
1103                 'printed': 'STOCK_PRINT_PREVIEW',
1104                 'done': 'STOCK_DIALOG_AUTHENTICATION',
1105             }.get(r['state'], 'STOCK_NEW')
1106         return result
1107
1108     _columns = {
1109         'name': fields.char('Journal-Period Name', required=True),
1110         'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
1111         'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
1112         'icon': fields.function(_icon_get, string='Icon', type='char'),
1113         '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."),
1114         'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'Status', required=True, readonly=True,
1115                                   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.'),
1116         'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
1117         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
1118     }
1119
1120     def _check(self, cr, uid, ids, context=None):
1121         for obj in self.browse(cr, uid, ids, context=context):
1122             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))
1123             res = cr.fetchall()
1124             if res:
1125                 raise osv.except_osv(_('Error!'), _('You cannot modify/delete a journal with entries for this period.'))
1126         return True
1127
1128     def write(self, cr, uid, ids, vals, context=None):
1129         self._check(cr, uid, ids, context=context)
1130         return super(account_journal_period, self).write(cr, uid, ids, vals, context=context)
1131
1132     def create(self, cr, uid, vals, context=None):
1133         period_id = vals.get('period_id',False)
1134         if period_id:
1135             period = self.pool.get('account.period').browse(cr, uid, period_id, context=context)
1136             vals['state']=period.state
1137         return super(account_journal_period, self).create(cr, uid, vals, context)
1138
1139     def unlink(self, cr, uid, ids, context=None):
1140         self._check(cr, uid, ids, context=context)
1141         return super(account_journal_period, self).unlink(cr, uid, ids, context=context)
1142
1143     _defaults = {
1144         'state': 'draft',
1145         'active': True,
1146     }
1147     _order = "period_id"
1148
1149 #----------------------------------------------------------
1150 # Entries
1151 #----------------------------------------------------------
1152 class account_move(osv.osv):
1153     _name = "account.move"
1154     _description = "Account Entry"
1155     _order = 'id desc'
1156
1157     def account_assert_balanced(self, cr, uid, context=None):
1158         cr.execute("""\
1159             SELECT      move_id
1160             FROM        account_move_line
1161             WHERE       state = 'valid'
1162             GROUP BY    move_id
1163             HAVING      abs(sum(debit) - sum(credit)) > 0.00001
1164             """)
1165         assert len(cr.fetchall()) == 0, \
1166             "For all Journal Items, the state is valid implies that the sum " \
1167             "of credits equals the sum of debits"
1168         return True
1169
1170     def account_move_prepare(self, cr, uid, journal_id, date=False, ref='', company_id=False, context=None):
1171         '''
1172         Prepares and returns a dictionary of values, ready to be passed to create() based on the parameters received.
1173         '''
1174         if not date:
1175             date = fields.date.today()
1176         period_obj = self.pool.get('account.period')
1177         if not company_id:
1178             user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1179             company_id = user.company_id.id
1180         if context is None:
1181             context = {}
1182         #put the company in context to find the good period
1183         ctx = context.copy()
1184         ctx.update({'company_id': company_id})
1185         return {
1186             'journal_id': journal_id,
1187             'date': date,
1188             'period_id': period_obj.find(cr, uid, date, context=ctx)[0],
1189             'ref': ref,
1190             'company_id': company_id,
1191         }
1192
1193     def name_get(self, cursor, user, ids, context=None):
1194         if isinstance(ids, (int, long)):
1195             ids = [ids]
1196         if not ids:
1197             return []
1198         res = []
1199         data_move = self.pool.get('account.move').browse(cursor, user, ids, context=context)
1200         for move in data_move:
1201             if move.state=='draft':
1202                 name = '*' + str(move.id)
1203             else:
1204                 name = move.name
1205             res.append((move.id, name))
1206         return res
1207
1208     def _get_period(self, cr, uid, context=None):
1209         ctx = dict(context or {})
1210         period_ids = self.pool.get('account.period').find(cr, uid, context=ctx)
1211         return period_ids[0]
1212
1213     def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1214         if not ids: return {}
1215         cr.execute( 'SELECT move_id, SUM(debit) '\
1216                     'FROM account_move_line '\
1217                     'WHERE move_id IN %s '\
1218                     'GROUP BY move_id', (tuple(ids),))
1219         result = dict(cr.fetchall())
1220         for id in ids:
1221             result.setdefault(id, 0.0)
1222         return result
1223
1224     def _search_amount(self, cr, uid, obj, name, args, context):
1225         ids = set()
1226         for cond in args:
1227             amount = cond[2]
1228             if isinstance(cond[2],(list,tuple)):
1229                 if cond[1] in ['in','not in']:
1230                     amount = tuple(cond[2])
1231                 else:
1232                     continue
1233             else:
1234                 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1235                     continue
1236
1237             cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1238             res_ids = set(id[0] for id in cr.fetchall())
1239             ids = ids and (ids & res_ids) or res_ids
1240         if ids:
1241             return [('id', 'in', tuple(ids))]
1242         return [('id', '=', '0')]
1243
1244     def _get_move_from_lines(self, cr, uid, ids, context=None):
1245         line_obj = self.pool.get('account.move.line')
1246         return [line.move_id.id for line in line_obj.browse(cr, uid, ids, context=context)]
1247
1248     _columns = {
1249         'name': fields.char('Number', required=True, copy=False),
1250         'ref': fields.char('Reference', copy=False),
1251         'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1252         'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1253         'state': fields.selection(
1254               [('draft','Unposted'), ('posted','Posted')], 'Status',
1255               required=True, readonly=True, copy=False,
1256               help='All manually created new journal entries are usually in the status \'Unposted\', '
1257                    'but you can set the option to skip that status on the related journal. '
1258                    'In that case, they will behave as journal entries automatically created by the '
1259                    'system on document validation (invoices, bank statements...) and will be created '
1260                    'in \'Posted\' status.'),
1261         'line_id': fields.one2many('account.move.line', 'move_id', 'Entries',
1262                                    states={'posted':[('readonly',True)]},
1263                                    copy=True),
1264         '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.'),
1265         'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store={
1266             _name: (lambda self, cr,uid,ids,c: ids, ['line_id'], 10),
1267             'account.move.line': (_get_move_from_lines, ['partner_id'],10)
1268             }),
1269         'amount': fields.function(_amount_compute, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1270         'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}, select=True),
1271         'narration':fields.text('Internal Note'),
1272         'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1273         '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"),
1274     }
1275
1276     _defaults = {
1277         'name': '/',
1278         'state': 'draft',
1279         'period_id': _get_period,
1280         'date': fields.date.context_today,
1281         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1282     }
1283
1284     def _check_centralisation(self, cursor, user, ids, context=None):
1285         for move in self.browse(cursor, user, ids, context=context):
1286             if move.journal_id.centralisation:
1287                 move_ids = self.search(cursor, user, [
1288                     ('period_id', '=', move.period_id.id),
1289                     ('journal_id', '=', move.journal_id.id),
1290                     ])
1291                 if len(move_ids) > 1:
1292                     return False
1293         return True
1294
1295     _constraints = [
1296         (_check_centralisation,
1297             'You cannot create more than one move per period on a centralized journal.',
1298             ['journal_id']),
1299     ]
1300
1301     def post(self, cr, uid, ids, context=None):
1302         if context is None:
1303             context = {}
1304         invoice = context.get('invoice', False)
1305         valid_moves = self.validate(cr, uid, ids, context)
1306
1307         if not valid_moves:
1308             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.'))
1309         obj_sequence = self.pool.get('ir.sequence')
1310         for move in self.browse(cr, uid, valid_moves, context=context):
1311             if move.name =='/':
1312                 new_name = False
1313                 journal = move.journal_id
1314
1315                 if invoice and invoice.internal_number:
1316                     new_name = invoice.internal_number
1317                 else:
1318                     if journal.sequence_id:
1319                         c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1320                         new_name = obj_sequence.next_by_id(cr, uid, journal.sequence_id.id, c)
1321                     else:
1322                         raise osv.except_osv(_('Error!'), _('Please define a sequence on the journal.'))
1323
1324                 if new_name:
1325                     self.write(cr, uid, [move.id], {'name':new_name})
1326
1327         cr.execute('UPDATE account_move '\
1328                    'SET state=%s '\
1329                    'WHERE id IN %s',
1330                    ('posted', tuple(valid_moves),))
1331         self.invalidate_cache(cr, uid, context=context)
1332         return True
1333
1334     def button_validate(self, cursor, user, ids, context=None):
1335         for move in self.browse(cursor, user, ids, context=context):
1336             # check that all accounts have the same topmost ancestor
1337             top_common = None
1338             for line in move.line_id:
1339                 account = line.account_id
1340                 top_account = account
1341                 while top_account.parent_id:
1342                     top_account = top_account.parent_id
1343                 if not top_common:
1344                     top_common = top_account
1345                 elif top_account.id != top_common.id:
1346                     raise osv.except_osv(_('Error!'),
1347                                          _('You cannot validate this journal entry because account "%s" does not belong to chart of accounts "%s".') % (account.name, top_common.name))
1348         return self.post(cursor, user, ids, context=context)
1349
1350     def button_cancel(self, cr, uid, ids, context=None):
1351         for line in self.browse(cr, uid, ids, context=context):
1352             if not line.journal_id.update_posted:
1353                 raise osv.except_osv(_('Error!'), _('You cannot modify a posted entry of this journal.\nFirst you should set the journal to allow cancelling entries.'))
1354         if ids:
1355             cr.execute('UPDATE account_move '\
1356                        'SET state=%s '\
1357                        'WHERE id IN %s', ('draft', tuple(ids),))
1358             self.invalidate_cache(cr, uid, context=context)
1359         return True
1360
1361     def write(self, cr, uid, ids, vals, context=None):
1362         if context is None:
1363             context = {}
1364         c = context.copy()
1365         c['novalidate'] = True
1366         result = super(account_move, self).write(cr, uid, ids, vals, c)
1367         self.validate(cr, uid, ids, context=context)
1368         return result
1369
1370     #
1371     # TODO: Check if period is closed !
1372     #
1373     def create(self, cr, uid, vals, context=None):
1374         context = dict(context or {})
1375         if vals.get('line_id'):
1376             if vals.get('journal_id'):
1377                 for l in vals['line_id']:
1378                     if not l[0]:
1379                         l[2]['journal_id'] = vals['journal_id']
1380                 context['journal_id'] = vals['journal_id']
1381             if 'period_id' in vals:
1382                 for l in vals['line_id']:
1383                     if not l[0]:
1384                         l[2]['period_id'] = vals['period_id']
1385                 context['period_id'] = vals['period_id']
1386             else:
1387                 default_period = self._get_period(cr, uid, context)
1388                 for l in vals['line_id']:
1389                     if not l[0]:
1390                         l[2]['period_id'] = default_period
1391                 context['period_id'] = default_period
1392
1393             c = context.copy()
1394             c['novalidate'] = True
1395             c['period_id'] = vals['period_id'] if 'period_id' in vals else self._get_period(cr, uid, context)
1396             c['journal_id'] = vals['journal_id']
1397             if 'date' in vals: c['date'] = vals['date']
1398             result = super(account_move, self).create(cr, uid, vals, c)
1399             tmp = self.validate(cr, uid, [result], context)
1400             journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'], context)
1401             if journal.entry_posted and tmp:
1402                 self.button_validate(cr,uid, [result], context)
1403         else:
1404             result = super(account_move, self).create(cr, uid, vals, context)
1405         return result
1406
1407     def unlink(self, cr, uid, ids, context=None, check=True):
1408         context = dict(context or {})
1409         if isinstance(ids, (int, long)):
1410             ids = [ids]
1411         toremove = []
1412         obj_move_line = self.pool.get('account.move.line')
1413         for move in self.browse(cr, uid, ids, context=context):
1414             if move['state'] != 'draft':
1415                 raise osv.except_osv(_('User Error!'),
1416                         _('You cannot delete a posted journal entry "%s".') % \
1417                                 move['name'])
1418             for line in move.line_id:
1419                 if line.invoice:
1420                     raise osv.except_osv(_('User Error!'),
1421                             _("Move cannot be deleted if linked to an invoice. (Invoice: %s - Move ID:%s)") % \
1422                                     (line.invoice.number,move.name))
1423             line_ids = map(lambda x: x.id, move.line_id)
1424             context['journal_id'] = move.journal_id.id
1425             context['period_id'] = move.period_id.id
1426             obj_move_line._update_check(cr, uid, line_ids, context)
1427             obj_move_line.unlink(cr, uid, line_ids, context=context)
1428             toremove.append(move.id)
1429         result = super(account_move, self).unlink(cr, uid, toremove, context)
1430         return result
1431
1432     def _compute_balance(self, cr, uid, id, context=None):
1433         move = self.browse(cr, uid, id, context=context)
1434         amount = 0
1435         for line in move.line_id:
1436             amount+= (line.debit - line.credit)
1437         return amount
1438
1439     def _centralise(self, cr, uid, move, mode, context=None):
1440         assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1441         currency_obj = self.pool.get('res.currency')
1442         account_move_line_obj = self.pool.get('account.move.line')
1443         context = dict(context or {})
1444
1445         if mode=='credit':
1446             account_id = move.journal_id.default_debit_account_id.id
1447             mode2 = 'debit'
1448             if not account_id:
1449                 raise osv.except_osv(_('User Error!'),
1450                         _('There is no default debit account defined \n' \
1451                                 'on journal "%s".') % move.journal_id.name)
1452         else:
1453             account_id = move.journal_id.default_credit_account_id.id
1454             mode2 = 'credit'
1455             if not account_id:
1456                 raise osv.except_osv(_('User Error!'),
1457                         _('There is no default credit account defined \n' \
1458                                 'on journal "%s".') % move.journal_id.name)
1459
1460         # find the first line of this move with the current mode
1461         # or create it if it doesn't exist
1462         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1463         res = cr.fetchone()
1464         if res:
1465             line_id = res[0]
1466         else:
1467             context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1468             line_id = account_move_line_obj.create(cr, uid, {
1469                 'name': _(mode.capitalize()+' Centralisation'),
1470                 'centralisation': mode,
1471                 'partner_id': False,
1472                 'account_id': account_id,
1473                 'move_id': move.id,
1474                 'journal_id': move.journal_id.id,
1475                 'period_id': move.period_id.id,
1476                 'date': move.period_id.date_stop,
1477                 'debit': 0.0,
1478                 'credit': 0.0,
1479             }, context)
1480
1481         # find the first line of this move with the other mode
1482         # so that we can exclude it from our calculation
1483         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1484         res = cr.fetchone()
1485         if res:
1486             line_id2 = res[0]
1487         else:
1488             line_id2 = 0
1489
1490         cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1491         result = cr.fetchone()[0] or 0.0
1492         cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1493         account_move_line_obj.invalidate_cache(cr, uid, [mode2], [line_id], context=context)
1494
1495         #adjust also the amount in currency if needed
1496         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,))
1497         for row in cr.dictfetchall():
1498             currency_id = currency_obj.browse(cr, uid, row['currency_id'], context=context)
1499             if not currency_obj.is_zero(cr, uid, currency_id, row['amount_currency']):
1500                 amount_currency = row['amount_currency'] * -1
1501                 account_id = amount_currency > 0 and move.journal_id.default_debit_account_id.id or move.journal_id.default_credit_account_id.id
1502                 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']))
1503                 res = cr.fetchone()
1504                 if res:
1505                     cr.execute('update account_move_line set amount_currency=%s , account_id=%s where id=%s', (amount_currency, account_id, res[0]))
1506                     account_move_line_obj.invalidate_cache(cr, uid, ['amount_currency', 'account_id'], [res[0]], context=context)
1507                 else:
1508                     context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1509                     line_id = account_move_line_obj.create(cr, uid, {
1510                         'name': _('Currency Adjustment'),
1511                         'centralisation': 'currency',
1512                         'partner_id': False,
1513                         'account_id': account_id,
1514                         'move_id': move.id,
1515                         'journal_id': move.journal_id.id,
1516                         'period_id': move.period_id.id,
1517                         'date': move.period_id.date_stop,
1518                         'debit': 0.0,
1519                         'credit': 0.0,
1520                         'currency_id': row['currency_id'],
1521                         'amount_currency': amount_currency,
1522                     }, context)
1523
1524         return True
1525
1526     #
1527     # Validate a balanced move. If it is a centralised journal, create a move.
1528     #
1529     def validate(self, cr, uid, ids, context=None):
1530         if context and ('__last_update' in context):
1531             del context['__last_update']
1532
1533         valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1534         obj_analytic_line = self.pool.get('account.analytic.line')
1535         obj_move_line = self.pool.get('account.move.line')
1536         for move in self.browse(cr, uid, ids, context):
1537             journal = move.journal_id
1538             amount = 0
1539             line_ids = []
1540             line_draft_ids = []
1541             company_id = None
1542             for line in move.line_id:
1543                 amount += line.debit - line.credit
1544                 line_ids.append(line.id)
1545                 if line.state=='draft':
1546                     line_draft_ids.append(line.id)
1547
1548                 if not company_id:
1549                     company_id = line.account_id.company_id.id
1550                 if not company_id == line.account_id.company_id.id:
1551                     raise osv.except_osv(_('Error!'), _("Cannot create moves for different companies."))
1552
1553                 if line.account_id.currency_id and line.currency_id:
1554                     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):
1555                         raise osv.except_osv(_('Error!'), _("""Cannot create move with currency different from ..""") % (line.account_id.code, line.account_id.name))
1556
1557             if abs(amount) < 10 ** -4:
1558                 # If the move is balanced
1559                 # Add to the list of valid moves
1560                 # (analytic lines will be created later for valid moves)
1561                 valid_moves.append(move)
1562
1563                 # Check whether the move lines are confirmed
1564
1565                 if not line_draft_ids:
1566                     continue
1567                 # Update the move lines (set them as valid)
1568
1569                 obj_move_line.write(cr, uid, line_draft_ids, {
1570                     'state': 'valid'
1571                 }, context, check=False)
1572
1573                 account = {}
1574                 account2 = {}
1575
1576                 if journal.type in ('purchase','sale'):
1577                     for line in move.line_id:
1578                         code = amount = 0
1579                         key = (line.account_id.id, line.tax_code_id.id)
1580                         if key in account2:
1581                             code = account2[key][0]
1582                             amount = account2[key][1] * (line.debit + line.credit)
1583                         elif line.account_id.id in account:
1584                             code = account[line.account_id.id][0]
1585                             amount = account[line.account_id.id][1] * (line.debit + line.credit)
1586                         if (code or amount) and not (line.tax_code_id or line.tax_amount):
1587                             obj_move_line.write(cr, uid, [line.id], {
1588                                 'tax_code_id': code,
1589                                 'tax_amount': amount
1590                             }, context, check=False)
1591             elif journal.centralisation:
1592                 # If the move is not balanced, it must be centralised...
1593
1594                 # Add to the list of valid moves
1595                 # (analytic lines will be created later for valid moves)
1596                 valid_moves.append(move)
1597
1598                 #
1599                 # Update the move lines (set them as valid)
1600                 #
1601                 self._centralise(cr, uid, move, 'debit', context=context)
1602                 self._centralise(cr, uid, move, 'credit', context=context)
1603                 obj_move_line.write(cr, uid, line_draft_ids, {
1604                     'state': 'valid'
1605                 }, context, check=False)
1606             else:
1607                 # We can't validate it (it's unbalanced)
1608                 # Setting the lines as draft
1609                 not_draft_line_ids = list(set(line_ids) - set(line_draft_ids))
1610                 if not_draft_line_ids:
1611                     obj_move_line.write(cr, uid, not_draft_line_ids, {
1612                         'state': 'draft'
1613                     }, context, check=False)
1614         # Create analytic lines for the valid moves
1615         for record in valid_moves:
1616             obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1617
1618         valid_moves = [move.id for move in valid_moves]
1619         return len(valid_moves) > 0 and valid_moves or False
1620
1621
1622 class account_move_reconcile(osv.osv):
1623     _name = "account.move.reconcile"
1624     _description = "Account Reconciliation"
1625     _columns = {
1626         'name': fields.char('Name', required=True),
1627         'type': fields.char('Type', required=True),
1628         'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1629         'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1630         'create_date': fields.date('Creation date', readonly=True),
1631         'opening_reconciliation': fields.boolean('Opening Entries Reconciliation', help="Is this reconciliation produced by the opening of a new fiscal year ?."),
1632     }
1633     _defaults = {
1634         'name': lambda self,cr,uid,ctx=None: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile', context=ctx) or '/',
1635     }
1636     
1637     # You cannot unlink a reconciliation if it is a opening_reconciliation one,
1638     # you should use the generate opening entries wizard for that
1639     def unlink(self, cr, uid, ids, context=None):
1640         for move_rec in self.browse(cr, uid, ids, context=context):
1641             if move_rec.opening_reconciliation:
1642                 raise osv.except_osv(_('Error!'), _('You cannot unreconcile journal items if they has been generated by the \
1643                                                         opening/closing fiscal year process.'))
1644         return super(account_move_reconcile, self).unlink(cr, uid, ids, context=context)
1645     
1646     # Look in the line_id and line_partial_ids to ensure the partner is the same or empty
1647     # on all lines. We allow that only for opening/closing period
1648     def _check_same_partner(self, cr, uid, ids, context=None):
1649         for reconcile in self.browse(cr, uid, ids, context=context):
1650             move_lines = []
1651             if not reconcile.opening_reconciliation:
1652                 if reconcile.line_id:
1653                     first_partner = reconcile.line_id[0].partner_id.id
1654                     move_lines = reconcile.line_id
1655                 elif reconcile.line_partial_ids:
1656                     first_partner = reconcile.line_partial_ids[0].partner_id.id
1657                     move_lines = reconcile.line_partial_ids
1658                 if any([(line.account_id.type in ('receivable', 'payable') and line.partner_id.id != first_partner) for line in move_lines]):
1659                     return False
1660         return True
1661
1662     _constraints = [
1663         (_check_same_partner, 'You can only reconcile journal items with the same partner.', ['line_id', 'line_partial_ids']),
1664     ]
1665     
1666     def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None):
1667         total = 0.0
1668         for rec in self.browse(cr, uid, ids, context=context):
1669             for line in rec.line_partial_ids:
1670                 if line.account_id.currency_id:
1671                     total += line.amount_currency
1672                 else:
1673                     total += (line.debit or 0.0) - (line.credit or 0.0)
1674         if not total:
1675             self.pool.get('account.move.line').write(cr, uid,
1676                 map(lambda x: x.id, rec.line_partial_ids),
1677                 {'reconcile_id': rec.id },
1678                 context=context
1679             )
1680         return True
1681
1682     def name_get(self, cr, uid, ids, context=None):
1683         if not ids:
1684             return []
1685         result = []
1686         for r in self.browse(cr, uid, ids, context=context):
1687             total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1688             if total:
1689                 name = '%s (%.2f)' % (r.name, total)
1690                 result.append((r.id,name))
1691             else:
1692                 result.append((r.id,r.name))
1693         return result
1694
1695
1696 #----------------------------------------------------------
1697 # Tax
1698 #----------------------------------------------------------
1699 """
1700 a documenter
1701 child_depend: la taxe depend des taxes filles
1702 """
1703 class account_tax_code(osv.osv):
1704     """
1705     A code for the tax object.
1706
1707     This code is used for some tax declarations.
1708     """
1709     def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1710         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1711         if context.get('based_on', 'invoices') == 'payments':
1712             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1713                     FROM account_move_line AS line, \
1714                         account_move AS move \
1715                         LEFT JOIN account_invoice invoice ON \
1716                             (invoice.move_id = move.id) \
1717                     WHERE line.tax_code_id IN %s '+where+' \
1718                         AND move.id = line.move_id \
1719                         AND ((invoice.state = \'paid\') \
1720                             OR (invoice.id IS NULL)) \
1721                             GROUP BY line.tax_code_id',
1722                                 (parent_ids,) + where_params)
1723         else:
1724             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1725                     FROM account_move_line AS line, \
1726                     account_move AS move \
1727                     WHERE line.tax_code_id IN %s '+where+' \
1728                     AND move.id = line.move_id \
1729                     GROUP BY line.tax_code_id',
1730                        (parent_ids,) + where_params)
1731         res=dict(cr.fetchall())
1732         obj_precision = self.pool.get('decimal.precision')
1733         res2 = {}
1734         for record in self.browse(cr, uid, ids, context=context):
1735             def _rec_get(record):
1736                 amount = res.get(record.id, 0.0)
1737                 for rec in record.child_ids:
1738                     amount += _rec_get(rec) * rec.sign
1739                 return amount
1740             res2[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1741         return res2
1742
1743     def _sum_year(self, cr, uid, ids, name, args, context=None):
1744         if context is None:
1745             context = {}
1746         move_state = ('posted', )
1747         if context.get('state', 'all') == 'all':
1748             move_state = ('draft', 'posted', )
1749         if context.get('fiscalyear_id', False):
1750             fiscalyear_id = [context['fiscalyear_id']]
1751         else:
1752             fiscalyear_id = self.pool.get('account.fiscalyear').finds(cr, uid, exception=False)
1753         where = ''
1754         where_params = ()
1755         if fiscalyear_id:
1756             pids = []
1757             for fy in fiscalyear_id:
1758                 pids += map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fy).period_ids)
1759             if pids:
1760                 where = ' AND line.period_id IN %s AND move.state IN %s '
1761                 where_params = (tuple(pids), move_state)
1762         return self._sum(cr, uid, ids, name, args, context,
1763                 where=where, where_params=where_params)
1764
1765     def _sum_period(self, cr, uid, ids, name, args, context):
1766         if context is None:
1767             context = {}
1768         move_state = ('posted', )
1769         if context.get('state', False) == 'all':
1770             move_state = ('draft', 'posted', )
1771         if context.get('period_id', False):
1772             period_id = context['period_id']
1773         else:
1774             period_id = self.pool.get('account.period').find(cr, uid, context=context)
1775             if not period_id:
1776                 return dict.fromkeys(ids, 0.0)
1777             period_id = period_id[0]
1778         return self._sum(cr, uid, ids, name, args, context,
1779                 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1780
1781     _name = 'account.tax.code'
1782     _description = 'Tax Code'
1783     _rec_name = 'code'
1784     _order = 'sequence, code'
1785     _columns = {
1786         'name': fields.char('Tax Case Name', required=True, translate=True),
1787         'code': fields.char('Case Code', size=64),
1788         'info': fields.text('Description'),
1789         'sum': fields.function(_sum_year, string="Year Sum"),
1790         'sum_period': fields.function(_sum_period, string="Period Sum"),
1791         'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1792         'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1793         'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1794         'company_id': fields.many2one('res.company', 'Company', required=True),
1795         '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.'),
1796         '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"),
1797         'sequence': fields.integer('Sequence', help="Determine the display order in the report 'Accounting \ Reporting \ Generic Reporting \ Taxes \ Taxes Report'"),
1798     }
1799
1800     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1801         if not args:
1802             args = []
1803         if operator in expression.NEGATIVE_TERM_OPERATORS:
1804             domain = [('code', operator, name), ('name', operator, name)]
1805         else:
1806             domain = ['|', ('code', operator, name), ('name', operator, name)]
1807         ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
1808         return self.name_get(cr, user, ids, context=context)
1809
1810     def name_get(self, cr, uid, ids, context=None):
1811         if isinstance(ids, (int, long)):
1812             ids = [ids]
1813         if not ids:
1814             return []
1815         if isinstance(ids, (int, long)):
1816             ids = [ids]
1817         reads = self.read(cr, uid, ids, ['name','code'], context=context, load='_classic_write')
1818         return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1819                 for x in reads]
1820
1821     def _default_company(self, cr, uid, context=None):
1822         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1823         if user.company_id:
1824             return user.company_id.id
1825         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1826
1827     _defaults = {
1828         'company_id': _default_company,
1829         'sign': 1.0,
1830         'notprintable': False,
1831     }
1832
1833     _check_recursion = check_cycle
1834     _constraints = [
1835         (_check_recursion, 'Error!\nYou cannot create recursive accounts.', ['parent_id'])
1836     ]
1837     _order = 'code'
1838
1839
1840 def get_precision_tax():
1841     def change_digit_tax(cr):
1842         res = openerp.registry(cr.dbname)['decimal.precision'].precision_get(cr, SUPERUSER_ID, 'Account')
1843         return (16, res+3)
1844     return change_digit_tax
1845
1846 class account_tax(osv.osv):
1847     """
1848     A tax object.
1849
1850     Type: percent, fixed, none, code
1851         PERCENT: tax = price * amount
1852         FIXED: tax = price + amount
1853         NONE: no tax line
1854         CODE: execute python code. localcontext = {'price_unit':pu}
1855             return result in the context
1856             Ex: result=round(price_unit*0.21,4)
1857     """
1858     def copy_data(self, cr, uid, id, default=None, context=None):
1859         if default is None:
1860             default = {}
1861         this = self.browse(cr, uid, id, context=context)
1862         tmp_default = dict(default, name=_("%s (Copy)") % this.name)
1863         return super(account_tax, self).copy_data(cr, uid, id, default=tmp_default, context=context)
1864
1865     _name = 'account.tax'
1866     _description = 'Tax'
1867     _columns = {
1868         'name': fields.char('Tax Name', required=True, translate=True, help="This name will be displayed on reports"),
1869         '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."),
1870         'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For taxes of type percentage, enter % ratio between 0-1."),
1871         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the tax without removing it."),
1872         'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1873             help="The computation method for the tax amount."),
1874         'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1875             help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1876         'domain':fields.char('Domain', help="This field is only used if you develop your own module allowing developers to create specific taxes in a custom domain."),
1877         '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."),
1878         '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."),
1879         '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."),
1880         '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."),
1881         'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1882         'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1883         '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."),
1884         'python_compute':fields.text('Python Code'),
1885         'python_compute_inv':fields.text('Python Code (reverse)'),
1886         'python_applicable':fields.text('Applicable Code'),
1887
1888         #
1889         # Fields used for the Tax declaration
1890         #
1891         'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the tax declaration."),
1892         'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the tax declaration."),
1893         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
1894         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
1895
1896         # Same fields for refund invoices
1897
1898         'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the tax declaration."),
1899         'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the tax declaration."),
1900         'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
1901         'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()),
1902         '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"),
1903         'company_id': fields.many2one('res.company', 'Company', required=True),
1904         'description': fields.char('Tax Code'),
1905         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1906         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1907
1908     }
1909     _sql_constraints = [
1910         ('name_company_uniq', 'unique(name, company_id)', 'Tax Name must be unique per company!'),
1911     ]
1912
1913     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1914         """
1915         Returns a list of tupples containing id, name, as internally it is called {def name_get}
1916         result format: {[(id, name), (id, name), ...]}
1917
1918         @param cr: A database cursor
1919         @param user: ID of the user currently logged in
1920         @param name: name to search
1921         @param args: other arguments
1922         @param operator: default operator is 'ilike', it can be changed
1923         @param context: context arguments, like lang, time zone
1924         @param limit: Returns first 'n' ids of complete result, default is 80.
1925
1926         @return: Returns a list of tupples containing id and name
1927         """
1928         if not args:
1929             args = []
1930         if operator in expression.NEGATIVE_TERM_OPERATORS:
1931             domain = [('description', operator, name), ('name', operator, name)]
1932         else:
1933             domain = ['|', ('description', operator, name), ('name', operator, name)]
1934         ids = self.search(cr, user, expression.AND([domain, args]), limit=limit, context=context)
1935         return self.name_get(cr, user, ids, context=context)
1936
1937     def write(self, cr, uid, ids, vals, context=None):
1938         if vals.get('type', False) and vals['type'] in ('none', 'code'):
1939             vals.update({'amount': 0.0})
1940         return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1941
1942     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1943         if context is None:
1944             context = {}
1945         journal_pool = self.pool.get('account.journal')
1946
1947         if context.get('type'):
1948             if context.get('type') in ('out_invoice','out_refund'):
1949                 args += [('type_tax_use','in',['sale','all'])]
1950             elif context.get('type') in ('in_invoice','in_refund'):
1951                 args += [('type_tax_use','in',['purchase','all'])]
1952
1953         if context.get('journal_id'):
1954             journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1955             if journal.type in ('sale', 'purchase'):
1956                 args += [('type_tax_use','in',[journal.type,'all'])]
1957
1958         return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1959
1960     def name_get(self, cr, uid, ids, context=None):
1961         if not ids:
1962             return []
1963         res = []
1964         for record in self.read(cr, uid, ids, ['description','name'], context=context):
1965             name = record['description'] and record['description'] or record['name']
1966             res.append((record['id'],name ))
1967         return res
1968
1969     def _default_company(self, cr, uid, context=None):
1970         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1971         if user.company_id:
1972             return user.company_id.id
1973         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1974
1975     _defaults = {
1976         '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''',
1977         'python_compute_inv': '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
1978         'applicable_type': 'true',
1979         'type': 'percent',
1980         'amount': 0,
1981         'price_include': 0,
1982         'active': 1,
1983         'type_tax_use': 'all',
1984         'sequence': 1,
1985         'ref_tax_sign': 1,
1986         'ref_base_sign': 1,
1987         'tax_sign': 1,
1988         'base_sign': 1,
1989         'include_base_amount': False,
1990         'company_id': _default_company,
1991     }
1992     _order = 'sequence'
1993
1994     def _applicable(self, cr, uid, taxes, price_unit, product=None, partner=None):
1995         res = []
1996         for tax in taxes:
1997             if tax.applicable_type=='code':
1998                 localdict = {'price_unit':price_unit, 'product':product, 'partner':partner}
1999                 exec tax.python_applicable in localdict
2000                 if localdict.get('result', False):
2001                     res.append(tax)
2002             else:
2003                 res.append(tax)
2004         return res
2005
2006     def _unit_compute(self, cr, uid, taxes, price_unit, product=None, partner=None, quantity=0):
2007         taxes = self._applicable(cr, uid, taxes, price_unit ,product, partner)
2008         res = []
2009         cur_price_unit=price_unit
2010         for tax in taxes:
2011             # we compute the amount for the current tax object and append it to the result
2012             data = {'id':tax.id,
2013                     'name':tax.description and tax.description + " - " + tax.name or tax.name,
2014                     'account_collected_id':tax.account_collected_id.id,
2015                     'account_paid_id':tax.account_paid_id.id,
2016                     'account_analytic_collected_id': tax.account_analytic_collected_id.id,
2017                     'account_analytic_paid_id': tax.account_analytic_paid_id.id,
2018                     'base_code_id': tax.base_code_id.id,
2019                     'ref_base_code_id': tax.ref_base_code_id.id,
2020                     'sequence': tax.sequence,
2021                     'base_sign': tax.base_sign,
2022                     'tax_sign': tax.tax_sign,
2023                     'ref_base_sign': tax.ref_base_sign,
2024                     'ref_tax_sign': tax.ref_tax_sign,
2025                     'price_unit': cur_price_unit,
2026                     'tax_code_id': tax.tax_code_id.id,
2027                     'ref_tax_code_id': tax.ref_tax_code_id.id,
2028             }
2029             res.append(data)
2030             if tax.type=='percent':
2031                 amount = cur_price_unit * tax.amount
2032                 data['amount'] = amount
2033
2034             elif tax.type=='fixed':
2035                 data['amount'] = tax.amount
2036                 data['tax_amount']=quantity
2037                # data['amount'] = quantity
2038             elif tax.type=='code':
2039                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner, 'quantity': quantity}
2040                 exec tax.python_compute in localdict
2041                 amount = localdict['result']
2042                 data['amount'] = amount
2043             elif tax.type=='balance':
2044                 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2045                 data['balance'] = cur_price_unit
2046
2047             amount2 = data.get('amount', 0.0)
2048             if tax.child_ids:
2049                 if tax.child_depend:
2050                     latest = res.pop()
2051                 amount = amount2
2052                 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, product, partner, quantity)
2053                 res.extend(child_tax)
2054                 for child in child_tax:
2055                     amount2 += child.get('amount', 0.0)
2056                 if tax.child_depend:
2057                     for r in res:
2058                         for name in ('base','ref_base'):
2059                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2060                                 r[name+'_code_id'] = latest[name+'_code_id']
2061                                 r[name+'_sign'] = latest[name+'_sign']
2062                                 r['price_unit'] = latest['price_unit']
2063                                 latest[name+'_code_id'] = False
2064                         for name in ('tax','ref_tax'):
2065                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2066                                 r[name+'_code_id'] = latest[name+'_code_id']
2067                                 r[name+'_sign'] = latest[name+'_sign']
2068                                 r['amount'] = data['amount']
2069                                 latest[name+'_code_id'] = False
2070             if tax.include_base_amount:
2071                 cur_price_unit+=amount2
2072         return res
2073
2074     def compute_for_bank_reconciliation(self, cr, uid, tax_id, amount, context=None):
2075         """ Called by RPC by the bank statement reconciliation widget """
2076         tax = self.browse(cr, uid, tax_id, context=context)
2077         return self.compute_all(cr, uid, [tax], amount, 1) # TOCHECK may use force_exclude parameter
2078
2079     @api.v7
2080     def compute_all(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, force_excluded=False):
2081         """
2082         :param force_excluded: boolean used to say that we don't want to consider the value of field price_include of
2083             tax. It's used in encoding by line where you don't matter if you encoded a tax with that boolean to True or
2084             False
2085         RETURN: {
2086                 'total': 0.0,                # Total without taxes
2087                 'total_included: 0.0,        # Total with taxes
2088                 'taxes': []                  # List of taxes, see compute for the format
2089             }
2090         """
2091
2092         # By default, for each tax, tax amount will first be computed
2093         # and rounded at the 'Account' decimal precision for each
2094         # PO/SO/invoice line and then these rounded amounts will be
2095         # summed, leading to the total amount for that tax. But, if the
2096         # company has tax_calculation_rounding_method = round_globally,
2097         # we still follow the same method, but we use a much larger
2098         # precision when we round the tax amount for each line (we use
2099         # the 'Account' decimal precision + 5), and that way it's like
2100         # rounding after the sum of the tax amounts of each line
2101         precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2102         tax_compute_precision = precision
2103         if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally':
2104             tax_compute_precision += 5
2105         totalin = totalex = round(price_unit * quantity, precision)
2106         tin = []
2107         tex = []
2108         for tax in taxes:
2109             if not tax.price_include or force_excluded:
2110                 tex.append(tax)
2111             else:
2112                 tin.append(tax)
2113         tin = self.compute_inv(cr, uid, tin, price_unit, quantity, product=product, partner=partner, precision=tax_compute_precision)
2114         for r in tin:
2115             totalex -= r.get('amount', 0.0)
2116         totlex_qty = 0.0
2117         try:
2118             totlex_qty = totalex/quantity
2119         except:
2120             pass
2121         tex = self._compute(cr, uid, tex, totlex_qty, quantity, product=product, partner=partner, precision=tax_compute_precision)
2122         for r in tex:
2123             totalin += r.get('amount', 0.0)
2124         return {
2125             'total': totalex,
2126             'total_included': totalin,
2127             'taxes': tin + tex
2128         }
2129
2130     @api.v8
2131     def compute_all(self, price_unit, quantity, product=None, partner=None, force_excluded=False):
2132         return self._model.compute_all(
2133             self._cr, self._uid, self, price_unit, quantity,
2134             product=product, partner=partner, force_excluded=force_excluded)
2135
2136     def compute(self, cr, uid, taxes, price_unit, quantity,  product=None, partner=None):
2137         _logger.warning("Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included.")
2138         return self._compute(cr, uid, taxes, price_unit, quantity, product, partner)
2139
2140     def _compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
2141         """
2142         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2143
2144         RETURN:
2145             [ tax ]
2146             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2147             one tax for each tax id in IDS and their children
2148         """
2149         if not precision:
2150             precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2151         res = self._unit_compute(cr, uid, taxes, price_unit, product, partner, quantity)
2152         total = 0.0
2153         for r in res:
2154             if r.get('balance',False):
2155                 r['amount'] = round(r.get('balance', 0.0) * quantity, precision) - total
2156             else:
2157                 r['amount'] = round(r.get('amount', 0.0) * quantity, precision)
2158                 total += r['amount']
2159         return res
2160
2161     def _unit_compute_inv(self, cr, uid, taxes, price_unit, product=None, partner=None):
2162         taxes = self._applicable(cr, uid, taxes, price_unit,  product, partner)
2163         res = []
2164         taxes.reverse()
2165         cur_price_unit = price_unit
2166
2167         tax_parent_tot = 0.0
2168         for tax in taxes:
2169             if (tax.type=='percent') and not tax.include_base_amount:
2170                 tax_parent_tot += tax.amount
2171
2172         for tax in taxes:
2173             if (tax.type=='fixed') and not tax.include_base_amount:
2174                 cur_price_unit -= tax.amount
2175
2176         for tax in taxes:
2177             if tax.type=='percent':
2178                 if tax.include_base_amount:
2179                     amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
2180                 else:
2181                     amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
2182
2183             elif tax.type=='fixed':
2184                 amount = tax.amount
2185
2186             elif tax.type=='code':
2187                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
2188                 exec tax.python_compute_inv in localdict
2189                 amount = localdict['result']
2190             elif tax.type=='balance':
2191                 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2192
2193             if tax.include_base_amount:
2194                 cur_price_unit -= amount
2195                 todo = 0
2196             else:
2197                 todo = 1
2198             res.append({
2199                 'id': tax.id,
2200                 'todo': todo,
2201                 'name': tax.name,
2202                 'amount': amount,
2203                 'account_collected_id': tax.account_collected_id.id,
2204                 'account_paid_id': tax.account_paid_id.id,
2205                 'account_analytic_collected_id': tax.account_analytic_collected_id.id,
2206                 'account_analytic_paid_id': tax.account_analytic_paid_id.id,
2207                 'base_code_id': tax.base_code_id.id,
2208                 'ref_base_code_id': tax.ref_base_code_id.id,
2209                 'sequence': tax.sequence,
2210                 'base_sign': tax.base_sign,
2211                 'tax_sign': tax.tax_sign,
2212                 'ref_base_sign': tax.ref_base_sign,
2213                 'ref_tax_sign': tax.ref_tax_sign,
2214                 'price_unit': cur_price_unit,
2215                 'tax_code_id': tax.tax_code_id.id,
2216                 'ref_tax_code_id': tax.ref_tax_code_id.id,
2217             })
2218             if tax.child_ids:
2219                 if tax.child_depend:
2220                     del res[-1]
2221                     amount = price_unit
2222
2223             parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, product, partner)
2224             res.extend(parent_tax)
2225
2226         total = 0.0
2227         for r in res:
2228             if r['todo']:
2229                 total += r['amount']
2230         for r in res:
2231             r['price_unit'] -= total
2232             r['todo'] = 0
2233         return res
2234
2235     def compute_inv(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
2236         """
2237         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2238         Price Unit is a Tax included price
2239
2240         RETURN:
2241             [ tax ]
2242             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2243             one tax for each tax id in IDS and their children
2244         """
2245         if not precision:
2246             precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2247         res = self._unit_compute_inv(cr, uid, taxes, price_unit, product, partner=None)
2248         total = 0.0
2249         for r in res:
2250             if r.get('balance',False):
2251                 r['amount'] = round(r['balance'] * quantity, precision) - total
2252             else:
2253                 r['amount'] = round(r['amount'] * quantity, precision)
2254                 total += r['amount']
2255         return res
2256
2257
2258 # ---------------------------------------------------------
2259 # Account Entries Models
2260 # ---------------------------------------------------------
2261
2262 class account_model(osv.osv):
2263     _name = "account.model"
2264     _description = "Account Model"
2265     _columns = {
2266         'name': fields.char('Model Name', required=True, help="This is a model for recurring accounting entries"),
2267         'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2268         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2269         'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries', copy=True),
2270         'legend': fields.text('Legend', readonly=True, size=100),
2271     }
2272
2273     _defaults = {
2274         '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'),
2275     }
2276
2277     def generate(self, cr, uid, ids, data=None, context=None):
2278         if data is None:
2279             data = {}
2280         move_ids = []
2281         entry = {}
2282         account_move_obj = self.pool.get('account.move')
2283         account_move_line_obj = self.pool.get('account.move.line')
2284         pt_obj = self.pool.get('account.payment.term')
2285         period_obj = self.pool.get('account.period')
2286
2287         if context is None:
2288             context = {}
2289
2290         if data.get('date', False):
2291             context = dict(context)
2292             context.update({'date': data['date']})
2293
2294         move_date = context.get('date', time.strftime('%Y-%m-%d'))
2295         move_date = datetime.strptime(move_date,"%Y-%m-%d")
2296         for model in self.browse(cr, uid, ids, context=context):
2297             ctx = context.copy()
2298             ctx.update({'company_id': model.company_id.id})
2299             period_ids = period_obj.find(cr, uid, dt=context.get('date', False), context=ctx)
2300             period_id = period_ids and period_ids[0] or False
2301             ctx.update({'journal_id': model.journal_id.id,'period_id': period_id})
2302             try:
2303                 entry['name'] = model.name%{'year': move_date.strftime('%Y'), 'month': move_date.strftime('%m'), 'date': move_date.strftime('%Y-%m')}
2304             except:
2305                 raise osv.except_osv(_('Wrong Model!'), _('You have a wrong expression "%(...)s" in your model!'))
2306             move_id = account_move_obj.create(cr, uid, {
2307                 'ref': entry['name'],
2308                 'period_id': period_id,
2309                 'journal_id': model.journal_id.id,
2310                 'date': context.get('date', fields.date.context_today(self,cr,uid,context=context))
2311             })
2312             move_ids.append(move_id)
2313             for line in model.lines_id:
2314                 analytic_account_id = False
2315                 if line.analytic_account_id:
2316                     if not model.journal_id.analytic_journal_id:
2317                         raise osv.except_osv(_('No Analytic Journal!'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2318                     analytic_account_id = line.analytic_account_id.id
2319                 val = {
2320                     'move_id': move_id,
2321                     'journal_id': model.journal_id.id,
2322                     'period_id': period_id,
2323                     'analytic_account_id': analytic_account_id
2324                 }
2325
2326                 date_maturity = context.get('date',time.strftime('%Y-%m-%d'))
2327                 if line.date_maturity == 'partner':
2328                     if not line.partner_id:
2329                         raise osv.except_osv(_('Error!'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2330                                                                 "\nPlease define partner on it!")%(line.name, model.name))
2331
2332                     payment_term_id = False
2333                     if model.journal_id.type in ('purchase', 'purchase_refund') and line.partner_id.property_supplier_payment_term:
2334                         payment_term_id = line.partner_id.property_supplier_payment_term.id
2335                     elif line.partner_id.property_payment_term:
2336                         payment_term_id = line.partner_id.property_payment_term.id
2337                     if payment_term_id:
2338                         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2339                         if pterm_list:
2340                             pterm_list = [l[0] for l in pterm_list]
2341                             pterm_list.sort()
2342                             date_maturity = pterm_list[-1]
2343
2344                 val.update({
2345                     'name': line.name,
2346                     'quantity': line.quantity,
2347                     'debit': line.debit,
2348                     'credit': line.credit,
2349                     'account_id': line.account_id.id,
2350                     'move_id': move_id,
2351                     'partner_id': line.partner_id.id,
2352                     'date': context.get('date', fields.date.context_today(self,cr,uid,context=context)),
2353                     'date_maturity': date_maturity
2354                 })
2355                 account_move_line_obj.create(cr, uid, val, context=ctx)
2356
2357         return move_ids
2358
2359     def onchange_journal_id(self, cr, uid, ids, journal_id, context=None):
2360         company_id = False
2361
2362         if journal_id:
2363             journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
2364             if journal.company_id.id:
2365                 company_id = journal.company_id.id
2366
2367         return {'value': {'company_id': company_id}}
2368
2369
2370 class account_model_line(osv.osv):
2371     _name = "account.model.line"
2372     _description = "Account Model Entries"
2373     _columns = {
2374         'name': fields.char('Name', required=True),
2375         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones."),
2376         'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries."),
2377         'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2378         'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2379         'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2380         'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2381         'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2382         'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2383         'currency_id': fields.many2one('res.currency', 'Currency'),
2384         'partner_id': fields.many2one('res.partner', 'Partner'),
2385         '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."),
2386     }
2387     _order = 'sequence'
2388     _sql_constraints = [
2389         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in model, they must be positive!'),
2390         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model, they must be positive!'),
2391     ]
2392
2393 # ---------------------------------------------------------
2394 # Account Subscription
2395 # ---------------------------------------------------------
2396
2397
2398 class account_subscription(osv.osv):
2399     _name = "account.subscription"
2400     _description = "Account Subscription"
2401     _columns = {
2402         'name': fields.char('Name', required=True),
2403         'ref': fields.char('Reference'),
2404         'model_id': fields.many2one('account.model', 'Model', required=True),
2405         'date_start': fields.date('Start Date', required=True),
2406         'period_total': fields.integer('Number of Periods', required=True),
2407         'period_nbr': fields.integer('Period', required=True),
2408         'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2409         'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', required=True, readonly=True, copy=False),
2410         'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines', copy=True)
2411     }
2412     _defaults = {
2413         'date_start': fields.date.context_today,
2414         'period_type': 'month',
2415         'period_total': 12,
2416         'period_nbr': 1,
2417         'state': 'draft',
2418     }
2419     def state_draft(self, cr, uid, ids, context=None):
2420         self.write(cr, uid, ids, {'state':'draft'})
2421         return False
2422
2423     def check(self, cr, uid, ids, context=None):
2424         todone = []
2425         for sub in self.browse(cr, uid, ids, context=context):
2426             ok = True
2427             for line in sub.lines_id:
2428                 if not line.move_id.id:
2429                     ok = False
2430                     break
2431             if ok:
2432                 todone.append(sub.id)
2433         if todone:
2434             self.write(cr, uid, todone, {'state':'done'})
2435         return False
2436
2437     def remove_line(self, cr, uid, ids, context=None):
2438         toremove = []
2439         for sub in self.browse(cr, uid, ids, context=context):
2440             for line in sub.lines_id:
2441                 if not line.move_id.id:
2442                     toremove.append(line.id)
2443         if toremove:
2444             self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2445         self.write(cr, uid, ids, {'state':'draft'})
2446         return False
2447
2448     def compute(self, cr, uid, ids, context=None):
2449         for sub in self.browse(cr, uid, ids, context=context):
2450             ds = sub.date_start
2451             for i in range(sub.period_total):
2452                 self.pool.get('account.subscription.line').create(cr, uid, {
2453                     'date': ds,
2454                     'subscription_id': sub.id,
2455                 })
2456                 if sub.period_type=='day':
2457                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2458                 if sub.period_type=='month':
2459                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2460                 if sub.period_type=='year':
2461                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2462         self.write(cr, uid, ids, {'state':'running'})
2463         return True
2464
2465
2466 class account_subscription_line(osv.osv):
2467     _name = "account.subscription.line"
2468     _description = "Account Subscription Line"
2469     _columns = {
2470         'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2471         'date': fields.date('Date', required=True),
2472         'move_id': fields.many2one('account.move', 'Entry'),
2473     }
2474
2475     def move_create(self, cr, uid, ids, context=None):
2476         tocheck = {}
2477         all_moves = []
2478         obj_model = self.pool.get('account.model')
2479         for line in self.browse(cr, uid, ids, context=context):
2480             data = {
2481                 'date': line.date,
2482             }
2483             move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], data, context)
2484             tocheck[line.subscription_id.id] = True
2485             self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2486             all_moves.extend(move_ids)
2487         if tocheck:
2488             self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2489         return all_moves
2490
2491     _rec_name = 'date'
2492
2493
2494 #  ---------------------------------------------------------------
2495 #   Account Templates: Account, Tax, Tax Code and chart. + Wizard
2496 #  ---------------------------------------------------------------
2497
2498 class account_tax_template(osv.osv):
2499     _name = 'account.tax.template'
2500
2501 class account_account_template(osv.osv):
2502     _order = "code"
2503     _name = "account.account.template"
2504     _description ='Templates for Accounts'
2505
2506     _columns = {
2507         'name': fields.char('Name', required=True, select=True),
2508         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2509         'code': fields.char('Code', size=64, required=True, select=1),
2510         'type': fields.selection([
2511             ('receivable','Receivable'),
2512             ('payable','Payable'),
2513             ('view','View'),
2514             ('consolidation','Consolidation'),
2515             ('liquidity','Liquidity'),
2516             ('other','Regular'),
2517             ('closed','Closed'),
2518             ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2519             "special effects in Odoo: view can not have entries, consolidation are accounts that "\
2520             "can have children accounts for multi-company consolidations, payable/receivable are for "\
2521             "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2522         'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2523             help="These types are defined according to your country. The type contains more information "\
2524             "about the account and its specificities."),
2525         'financial_report_ids': fields.many2many('account.financial.report', 'account_template_financial_report', 'account_template_id', 'report_line_id', 'Financial Reports'),
2526         'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2527         'shortcut': fields.char('Shortcut', size=12),
2528         'note': fields.text('Note'),
2529         'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade', domain=[('type','=','view')]),
2530         'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2531         'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2532         'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2533         '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)."),
2534     }
2535
2536     _defaults = {
2537         'reconcile': False,
2538         'type': 'view',
2539         'nocreate': False,
2540     }
2541
2542     _check_recursion = check_cycle
2543     _constraints = [
2544         (_check_recursion, 'Error!\nYou cannot create recursive account templates.', ['parent_id']),
2545     ]
2546
2547     def name_get(self, cr, uid, ids, context=None):
2548         if not ids:
2549             return []
2550         reads = self.read(cr, uid, ids, ['name','code'], context=context)
2551         res = []
2552         for record in reads:
2553             name = record['name']
2554             if record['code']:
2555                 name = record['code']+' '+name
2556             res.append((record['id'],name ))
2557         return res
2558
2559     def generate_account(self, cr, uid, chart_template_id, tax_template_ref, acc_template_ref, code_digits, company_id, context=None):
2560         """
2561         This method for generating accounts from templates.
2562
2563         :param chart_template_id: id of the chart template chosen in the wizard
2564         :param tax_template_ref: Taxes templates reference for write taxes_id in account_account.
2565         :paramacc_template_ref: dictionary with the mappping between the account templates and the real accounts.
2566         :param code_digits: number of digits got from wizard.multi.charts.accounts, this is use for account code.
2567         :param company_id: company_id selected from wizard.multi.charts.accounts.
2568         :returns: return acc_template_ref for reference purpose.
2569         :rtype: dict
2570         """
2571         if context is None:
2572             context = {}
2573         obj_acc = self.pool.get('account.account')
2574         company_name = self.pool.get('res.company').browse(cr, uid, company_id, context=context).name
2575         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
2576         #deactivate the parent_store functionnality on account_account for rapidity purpose
2577         ctx = context.copy()
2578         ctx.update({'defer_parent_store_computation': True})
2579         level_ref = {}
2580         children_acc_criteria = [('chart_template_id','=', chart_template_id)]
2581         if template.account_root_id.id:
2582             children_acc_criteria = ['|'] + children_acc_criteria + ['&',('parent_id','child_of', [template.account_root_id.id]),('chart_template_id','=', False)]
2583         children_acc_template = self.search(cr, uid, [('nocreate','!=',True)] + children_acc_criteria, order='id')
2584         for account_template in self.browse(cr, uid, children_acc_template, context=context):
2585             # skip the root of COA if it's not the main one
2586             if (template.account_root_id.id == account_template.id) and template.parent_id:
2587                 continue
2588             tax_ids = []
2589             for tax in account_template.tax_ids:
2590                 tax_ids.append(tax_template_ref[tax.id])
2591
2592             code_main = account_template.code and len(account_template.code) or 0
2593             code_acc = account_template.code or ''
2594             if code_main > 0 and code_main <= code_digits and account_template.type != 'view':
2595                 code_acc = str(code_acc) + (str('0'*(code_digits-code_main)))
2596             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
2597             #the level as to be given as well at the creation time, because of the defer_parent_store_computation in
2598             #context. Indeed because of this, the parent_left and parent_right are not computed and thus the child_of
2599             #operator does not return the expected values, with result of having the level field not computed at all.
2600             if parent_id:
2601                 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
2602             else:
2603                 level = 0
2604             vals={
2605                 'name': (template.account_root_id.id == account_template.id) and company_name or account_template.name,
2606                 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2607                 'code': code_acc,
2608                 'type': account_template.type,
2609                 'user_type': account_template.user_type and account_template.user_type.id or False,
2610                 'reconcile': account_template.reconcile,
2611                 'shortcut': account_template.shortcut,
2612                 'note': account_template.note,
2613                 'financial_report_ids': account_template.financial_report_ids and [(6,0,[x.id for x in account_template.financial_report_ids])] or False,
2614                 'parent_id': parent_id,
2615                 'tax_ids': [(6,0,tax_ids)],
2616                 'company_id': company_id,
2617                 'level': level,
2618             }
2619             new_account = obj_acc.create(cr, uid, vals, context=ctx)
2620             acc_template_ref[account_template.id] = new_account
2621             level_ref[new_account] = level
2622
2623         #reactivate the parent_store functionnality on account_account
2624         obj_acc._parent_store_compute(cr)
2625         return acc_template_ref
2626
2627
2628 class account_add_tmpl_wizard(osv.osv_memory):
2629     """Add one more account from the template.
2630
2631     With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2632     _name = 'account.addtmpl.wizard'
2633
2634     def _get_def_cparent(self, cr, uid, context=None):
2635         acc_obj = self.pool.get('account.account')
2636         tmpl_obj = self.pool.get('account.account.template')
2637         tids = tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2638         if not tids or not tids[0]['parent_id']:
2639             return False
2640         ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2641         res = None
2642         if not ptids or not ptids[0]['code']:
2643             raise osv.except_osv(_('Error!'), _('There is no parent code for the template account.'))
2644             res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2645         return res and res[0] or False
2646
2647     _columns = {
2648         'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2649     }
2650     _defaults = {
2651         'cparent_id': _get_def_cparent,
2652     }
2653
2654     def action_create(self,cr,uid,ids,context=None):
2655         if context is None:
2656             context = {}
2657         acc_obj = self.pool.get('account.account')
2658         tmpl_obj = self.pool.get('account.account.template')
2659         data = self.read(cr, uid, ids)[0]
2660         company_id = acc_obj.read(cr, uid, [data['cparent_id'][0]], ['company_id'])[0]['company_id'][0]
2661         account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2662         vals = {
2663             'name': account_template.name,
2664             'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2665             'code': account_template.code,
2666             'type': account_template.type,
2667             'user_type': account_template.user_type and account_template.user_type.id or False,
2668             'reconcile': account_template.reconcile,
2669             'shortcut': account_template.shortcut,
2670             'note': account_template.note,
2671             'parent_id': data['cparent_id'][0],
2672             'company_id': company_id,
2673             }
2674         acc_obj.create(cr, uid, vals)
2675         return {'type':'state', 'state': 'end' }
2676
2677     def action_cancel(self, cr, uid, ids, context=None):
2678         return { 'type': 'state', 'state': 'end' }
2679
2680
2681 class account_tax_code_template(osv.osv):
2682
2683     _name = 'account.tax.code.template'
2684     _description = 'Tax Code Template'
2685     _order = 'sequence, code'
2686     _rec_name = 'code'
2687     _columns = {
2688         'name': fields.char('Tax Case Name', required=True),
2689         'code': fields.char('Case Code', size=64),
2690         'info': fields.text('Description'),
2691         'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2692         'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2693         'sign': fields.float('Sign For Parent', required=True),
2694         '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."),
2695         'sequence': fields.integer(
2696             'Sequence', help=(
2697                 "Determine the display order in the report 'Accounting "
2698                 "\ Reporting \ Generic Reporting \ Taxes \ Taxes Report'"),
2699             ),
2700     }
2701
2702     _defaults = {
2703         'sign': 1.0,
2704         'notprintable': False,
2705     }
2706
2707     def generate_tax_code(self, cr, uid, tax_code_root_id, company_id, context=None):
2708         '''
2709         This function generates the tax codes from the templates of tax code that are children of the given one passed
2710         in argument. Then it returns a dictionary with the mappping between the templates and the real objects.
2711
2712         :param tax_code_root_id: id of the root of all the tax code templates to process
2713         :param company_id: id of the company the wizard is running for
2714         :returns: dictionary with the mappping between the templates and the real objects.
2715         :rtype: dict
2716         '''
2717         obj_tax_code_template = self.pool.get('account.tax.code.template')
2718         obj_tax_code = self.pool.get('account.tax.code')
2719         tax_code_template_ref = {}
2720         company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
2721
2722         #find all the children of the tax_code_root_id
2723         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 []
2724         for tax_code_template in obj_tax_code_template.browse(cr, uid, children_tax_code_template, context=context):
2725             vals = {
2726                 'name': (tax_code_root_id == tax_code_template.id) and company.name or tax_code_template.name,
2727                 'code': tax_code_template.code,
2728                 'info': tax_code_template.info,
2729                 '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,
2730                 'company_id': company_id,
2731                 'sign': tax_code_template.sign,
2732                 'sequence': tax_code_template.sequence,
2733             }
2734             #check if this tax code already exists
2735             rec_list = obj_tax_code.search(cr, uid, [('name', '=', vals['name']),('code', '=', vals['code']),('company_id', '=', vals['company_id'])], context=context)
2736             if not rec_list:
2737                 #if not yet, create it
2738                 new_tax_code = obj_tax_code.create(cr, uid, vals)
2739                 #recording the new tax code to do the mapping
2740                 tax_code_template_ref[tax_code_template.id] = new_tax_code
2741         return tax_code_template_ref
2742
2743     def name_get(self, cr, uid, ids, context=None):
2744         if not ids:
2745             return []
2746         if isinstance(ids, (int, long)):
2747             ids = [ids]
2748         reads = self.read(cr, uid, ids, ['name','code'], context=context, load='_classic_write')
2749         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2750                 for x in reads]
2751
2752     _check_recursion = check_cycle
2753     _constraints = [
2754         (_check_recursion, 'Error!\nYou cannot create recursive Tax Codes.', ['parent_id'])
2755     ]
2756     _order = 'code,name'
2757
2758
2759 class account_chart_template(osv.osv):
2760     _name="account.chart.template"
2761     _description= "Templates for Account Chart"
2762
2763     _columns={
2764         'name': fields.char('Name', required=True),
2765         'parent_id': fields.many2one('account.chart.template', 'Parent Chart Template'),
2766         'code_digits': fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2767         '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."),
2768         'currency_id': fields.many2one('res.currency', 'Currency'),
2769         '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'),
2770         'account_root_id': fields.many2one('account.account.template', 'Root Account', domain=[('parent_id','=',False)]),
2771         'tax_code_root_id': fields.many2one('account.tax.code.template', 'Root Tax Code', domain=[('parent_id','=',False)]),
2772         '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'),
2773         'bank_account_view_id': fields.many2one('account.account.template', 'Bank Account'),
2774         'property_account_receivable': fields.many2one('account.account.template', 'Receivable Account'),
2775         'property_account_payable': fields.many2one('account.account.template', 'Payable Account'),
2776         'property_account_expense_categ': fields.many2one('account.account.template', 'Expense Category Account'),
2777         'property_account_income_categ': fields.many2one('account.account.template', 'Income Category Account'),
2778         'property_account_expense': fields.many2one('account.account.template', 'Expense Account on Product Template'),
2779         'property_account_income': fields.many2one('account.account.template', 'Income Account on Product Template'),
2780         'property_account_income_opening': fields.many2one('account.account.template', 'Opening Entries Income Account'),
2781         'property_account_expense_opening': fields.many2one('account.account.template', 'Opening Entries Expense Account'),
2782     }
2783
2784     _defaults = {
2785         'visible': True,
2786         'code_digits': 6,
2787         'complete_tax_set': True,
2788     }
2789
2790
2791 class account_tax_template(osv.osv):
2792
2793     _name = 'account.tax.template'
2794     _description = 'Templates for Taxes'
2795
2796     _columns = {
2797         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2798         'name': fields.char('Tax Name', required=True),
2799         '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."),
2800         'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For Tax Type percent enter % ratio between 0-1."),
2801         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True),
2802         '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."),
2803         'domain':fields.char('Domain', help="This field is only used if you develop your own module allowing developers to create specific taxes in a custom domain."),
2804         'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2805         'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2806         'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2807         '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."),
2808         'python_compute':fields.text('Python Code'),
2809         'python_compute_inv':fields.text('Python Code (reverse)'),
2810         'python_applicable':fields.text('Applicable Code'),
2811
2812         #
2813         # Fields used for the Tax declaration
2814         #
2815         'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the tax declaration."),
2816         'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the tax declaration."),
2817         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2818         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2819
2820         # Same fields for refund invoices
2821
2822         'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the tax declaration."),
2823         'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the tax declaration."),
2824         'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1."),
2825         'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1."),
2826         '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."),
2827         'description': fields.char('Internal Name'),
2828         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,),
2829         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
2830     }
2831
2832     def name_get(self, cr, uid, ids, context=None):
2833         if not ids:
2834             return []
2835         res = []
2836         for record in self.read(cr, uid, ids, ['description','name'], context=context):
2837             name = record['description'] and record['description'] or record['name']
2838             res.append((record['id'],name ))
2839         return res
2840
2841     def _default_company(self, cr, uid, context=None):
2842         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2843         if user.company_id:
2844             return user.company_id.id
2845         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2846
2847     _defaults = {
2848         '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''',
2849         'python_compute_inv': lambda *a: '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
2850         'applicable_type': 'true',
2851         'type': 'percent',
2852         'amount': 0,
2853         'sequence': 1,
2854         'ref_tax_sign': 1,
2855         'ref_base_sign': 1,
2856         'tax_sign': 1,
2857         'base_sign': 1,
2858         'include_base_amount': False,
2859         'type_tax_use': 'all',
2860         'price_include': 0,
2861     }
2862     _order = 'sequence'
2863
2864     def _generate_tax(self, cr, uid, tax_templates, tax_code_template_ref, company_id, context=None):
2865         """
2866         This method generate taxes from templates.
2867
2868         :param tax_templates: list of browse record of the tax templates to process
2869         :param tax_code_template_ref: Taxcode templates reference.
2870         :param company_id: id of the company the wizard is running for
2871         :returns:
2872             {
2873             'tax_template_to_tax': mapping between tax template and the newly generated taxes corresponding,
2874             'account_dict': dictionary containing a to-do list with all the accounts to assign on new taxes
2875             }
2876         """
2877         if context is None:
2878             context = {}
2879         res = {}
2880         todo_dict = {}
2881         tax_template_to_tax = {}
2882         for tax in tax_templates:
2883             vals_tax = {
2884                 'name':tax.name,
2885                 'sequence': tax.sequence,
2886                 'amount': tax.amount,
2887                 'type': tax.type,
2888                 'applicable_type': tax.applicable_type,
2889                 'domain': tax.domain,
2890                 '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,
2891                 'child_depend': tax.child_depend,
2892                 'python_compute': tax.python_compute,
2893                 'python_compute_inv': tax.python_compute_inv,
2894                 'python_applicable': tax.python_applicable,
2895                 '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,
2896                 '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,
2897                 'base_sign': tax.base_sign,
2898                 'tax_sign': tax.tax_sign,
2899                 '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,
2900                 '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,
2901                 'ref_base_sign': tax.ref_base_sign,
2902                 'ref_tax_sign': tax.ref_tax_sign,
2903                 'include_base_amount': tax.include_base_amount,
2904                 'description': tax.description,
2905                 'company_id': company_id,
2906                 'type_tax_use': tax.type_tax_use,
2907                 'price_include': tax.price_include
2908             }
2909             new_tax = self.pool.get('account.tax').create(cr, uid, vals_tax)
2910             tax_template_to_tax[tax.id] = new_tax
2911             #as the accounts have not been created yet, we have to wait before filling these fields
2912             todo_dict[new_tax] = {
2913                 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2914                 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2915             }
2916         res.update({'tax_template_to_tax': tax_template_to_tax, 'account_dict': todo_dict})
2917         return res
2918
2919
2920 # Fiscal Position Templates
2921
2922 class account_fiscal_position_template(osv.osv):
2923     _name = 'account.fiscal.position.template'
2924     _description = 'Template for Fiscal Position'
2925
2926     _columns = {
2927         'name': fields.char('Fiscal Position Template', required=True),
2928         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2929         'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2930         'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping'),
2931         'note': fields.text('Notes'),
2932     }
2933
2934     def generate_fiscal_position(self, cr, uid, chart_temp_id, tax_template_ref, acc_template_ref, company_id, context=None):
2935         """
2936         This method generate Fiscal Position, Fiscal Position Accounts and Fiscal Position Taxes from templates.
2937
2938         :param chart_temp_id: Chart Template Id.
2939         :param taxes_ids: Taxes templates reference for generating account.fiscal.position.tax.
2940         :param acc_template_ref: Account templates reference for generating account.fiscal.position.account.
2941         :param company_id: company_id selected from wizard.multi.charts.accounts.
2942         :returns: True
2943         """
2944         if context is None:
2945             context = {}
2946         obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2947         obj_ac_fp = self.pool.get('account.fiscal.position.account')
2948         obj_fiscal_position = self.pool.get('account.fiscal.position')
2949         fp_ids = self.search(cr, uid, [('chart_template_id', '=', chart_temp_id)])
2950         for position in self.browse(cr, uid, fp_ids, context=context):
2951             new_fp = obj_fiscal_position.create(cr, uid, {'company_id': company_id, 'name': position.name, 'note': position.note})
2952             for tax in position.tax_ids:
2953                 obj_tax_fp.create(cr, uid, {
2954                     'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2955                     'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2956                     'position_id': new_fp
2957                 })
2958             for acc in position.account_ids:
2959                 obj_ac_fp.create(cr, uid, {
2960                     'account_src_id': acc_template_ref[acc.account_src_id.id],
2961                     'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2962                     'position_id': new_fp
2963                 })
2964         return True
2965
2966
2967 class account_fiscal_position_tax_template(osv.osv):
2968     _name = 'account.fiscal.position.tax.template'
2969     _description = 'Template Tax Fiscal Position'
2970     _rec_name = 'position_id'
2971
2972     _columns = {
2973         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2974         'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2975         'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2976     }
2977
2978
2979 class account_fiscal_position_account_template(osv.osv):
2980     _name = 'account.fiscal.position.account.template'
2981     _description = 'Template Account Fiscal Mapping'
2982     _rec_name = 'position_id'
2983     _columns = {
2984         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2985         'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2986         'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2987     }
2988
2989
2990 # ---------------------------------------------------------
2991 # Account generation from template wizards
2992 # ---------------------------------------------------------
2993
2994 class wizard_multi_charts_accounts(osv.osv_memory):
2995     """
2996     Create a new account chart for a company.
2997     Wizards ask for:
2998         * a company
2999         * an account chart template
3000         * a number of digits for formatting code of non-view accounts
3001         * a list of bank accounts owned by the company
3002     Then, the wizard:
3003         * generates all accounts from the template and assigns them to the right company
3004         * generates all taxes and tax codes, changing account assignations
3005         * generates all accounting properties and assigns them correctly
3006     """
3007     _name='wizard.multi.charts.accounts'
3008     _inherit = 'res.config'
3009
3010     _columns = {
3011         'company_id':fields.many2one('res.company', 'Company', required=True),
3012         'currency_id': fields.many2one('res.currency', 'Currency', help="Currency as per company's country."),
3013         'only_one_chart_template': fields.boolean('Only One Chart Template Available'),
3014         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
3015         'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Cash and Banks', required=True),
3016         'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
3017         "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
3018         "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
3019         'sale_tax_rate': fields.float('Sales Tax(%)'),
3020         'purchase_tax_rate': fields.float('Purchase Tax(%)'),
3021         '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'),
3022     }
3023
3024
3025     def _get_chart_parent_ids(self, cr, uid, chart_template, context=None):
3026         """ Returns the IDs of all ancestor charts, including the chart itself.
3027             (inverse of child_of operator)
3028         
3029             :param browse_record chart_template: the account.chart.template record
3030             :return: the IDS of all ancestor charts, including the chart itself.
3031         """
3032         result = [chart_template.id]
3033         while chart_template.parent_id:
3034             chart_template = chart_template.parent_id
3035             result.append(chart_template.id)
3036         return result
3037
3038     def onchange_tax_rate(self, cr, uid, ids, rate=False, context=None):
3039         return {'value': {'purchase_tax_rate': rate or False}}
3040
3041     def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
3042         res = {}
3043         tax_templ_obj = self.pool.get('account.tax.template')
3044         res['value'] = {'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False}
3045         if chart_template_id:
3046             data = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3047             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
3048             res['value'].update({'complete_tax_set': data.complete_tax_set, 'currency_id': currency_id})
3049             if data.complete_tax_set:
3050             # 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
3051                 chart_ids = self._get_chart_parent_ids(cr, uid, data, context=context)
3052                 base_tax_domain = [("chart_template_id", "in", chart_ids), ('parent_id', '=', False)]
3053                 sale_tax_domain = base_tax_domain + [('type_tax_use', 'in', ('sale','all'))]
3054                 purchase_tax_domain = base_tax_domain + [('type_tax_use', 'in', ('purchase','all'))]
3055                 sale_tax_ids = tax_templ_obj.search(cr, uid, sale_tax_domain, order="sequence, id desc")
3056                 purchase_tax_ids = tax_templ_obj.search(cr, uid, purchase_tax_domain, order="sequence, id desc")
3057                 res['value'].update({'sale_tax': sale_tax_ids and sale_tax_ids[0] or False,
3058                                      'purchase_tax': purchase_tax_ids and purchase_tax_ids[0] or False})
3059                 res.setdefault('domain', {})
3060                 res['domain']['sale_tax'] = repr(sale_tax_domain)
3061                 res['domain']['purchase_tax'] = repr(purchase_tax_domain)
3062             if data.code_digits:
3063                res['value'].update({'code_digits': data.code_digits})
3064         return res
3065
3066     def default_get(self, cr, uid, fields, context=None):
3067         res = super(wizard_multi_charts_accounts, self).default_get(cr, uid, fields, context=context)
3068         tax_templ_obj = self.pool.get('account.tax.template')
3069         account_chart_template = self.pool['account.chart.template']
3070
3071         if 'bank_accounts_id' in fields:
3072             res.update({'bank_accounts_id': [{'acc_name': _('Cash'), 'account_type': 'cash'},{'acc_name': _('Bank'), 'account_type': 'bank'}]})
3073         if 'company_id' in fields:
3074             res.update({'company_id': self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0].company_id.id})
3075         if 'currency_id' in fields:
3076             company_id = res.get('company_id') or False
3077             if company_id:
3078                 company_obj = self.pool.get('res.company')
3079                 country_id = company_obj.browse(cr, uid, company_id, context=context).country_id.id
3080                 currency_id = company_obj.on_change_country(cr, uid, company_id, country_id, context=context)['value']['currency_id']
3081                 res.update({'currency_id': currency_id})
3082
3083         ids = account_chart_template.search(cr, uid, [('visible', '=', True)], context=context)
3084         if ids:
3085             #in order to set default chart which was last created set max of ids.
3086             chart_id = max(ids)
3087             if context.get("default_charts"):
3088                 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)
3089                 if model_data:
3090                     chart_id = model_data[0]['res_id']
3091             chart = account_chart_template.browse(cr, uid, chart_id, context=context)
3092             chart_hierarchy_ids = self._get_chart_parent_ids(cr, uid, chart, context=context) 
3093             if 'chart_template_id' in fields:
3094                 res.update({'only_one_chart_template': len(ids) == 1,
3095                             'chart_template_id': chart_id})
3096             if 'sale_tax' in fields:
3097                 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id", "in", chart_hierarchy_ids),
3098                                                               ('type_tax_use', 'in', ('sale','all'))],
3099                                                     order="sequence")
3100                 res.update({'sale_tax': sale_tax_ids and sale_tax_ids[0] or False})
3101             if 'purchase_tax' in fields:
3102                 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id", "in", chart_hierarchy_ids),
3103                                                                   ('type_tax_use', 'in', ('purchase','all'))],
3104                                                         order="sequence")
3105                 res.update({'purchase_tax': purchase_tax_ids and purchase_tax_ids[0] or False})
3106         res.update({
3107             'purchase_tax_rate': 15.0,
3108             'sale_tax_rate': 15.0,
3109         })
3110         return res
3111
3112     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
3113         if context is None:context = {}
3114         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)
3115         cmp_select = []
3116         acc_template_obj = self.pool.get('account.chart.template')
3117         company_obj = self.pool.get('res.company')
3118
3119         company_ids = company_obj.search(cr, uid, [], context=context)
3120         #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)
3121         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",))
3122         configured_cmp = [r[0] for r in cr.fetchall()]
3123         unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
3124         for field in res['fields']:
3125             if field == 'company_id':
3126                 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
3127                 res['fields'][field]['selection'] = [('', '')]
3128                 if unconfigured_cmp:
3129                     cmp_select = [(line.id, line.name) for line in company_obj.browse(cr, uid, unconfigured_cmp)]
3130                     res['fields'][field]['selection'] = cmp_select
3131         return res
3132
3133     def check_created_journals(self, cr, uid, vals_journal, company_id, context=None):
3134         """
3135         This method used for checking journals already created or not. If not then create new journal.
3136         """
3137         obj_journal = self.pool.get('account.journal')
3138         rec_list = obj_journal.search(cr, uid, [('name','=', vals_journal['name']),('company_id', '=', company_id)], context=context)
3139         if not rec_list:
3140             obj_journal.create(cr, uid, vals_journal, context=context)
3141         return True
3142
3143     def generate_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3144         """
3145         This method is used for creating journals.
3146
3147         :param chart_temp_id: Chart Template Id.
3148         :param acc_template_ref: Account templates reference.
3149         :param company_id: company_id selected from wizard.multi.charts.accounts.
3150         :returns: True
3151         """
3152         journal_data = self._prepare_all_journals(cr, uid, chart_template_id, acc_template_ref, company_id, context=context)
3153         for vals_journal in journal_data:
3154             self.check_created_journals(cr, uid, vals_journal, company_id, context=context)
3155         return True
3156
3157     def _prepare_all_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3158         def _get_analytic_journal(journal_type):
3159             # Get the analytic journal
3160             data = False
3161             try:
3162                 if journal_type in ('sale', 'sale_refund'):
3163                     data = obj_data.get_object_reference(cr, uid, 'account', 'analytic_journal_sale')
3164                 elif journal_type in ('purchase', 'purchase_refund'):
3165                     data = obj_data.get_object_reference(cr, uid, 'account', 'exp')
3166                 elif journal_type == 'general':
3167                     pass
3168             except ValueError:
3169                 pass
3170             return data and data[1] or False
3171
3172         def _get_default_account(journal_type, type='debit'):
3173             # Get the default accounts
3174             default_account = False
3175             if journal_type in ('sale', 'sale_refund'):
3176                 default_account = acc_template_ref.get(template.property_account_income_categ.id)
3177             elif journal_type in ('purchase', 'purchase_refund'):
3178                 default_account = acc_template_ref.get(template.property_account_expense_categ.id)
3179             elif journal_type == 'situation':
3180                 if type == 'debit':
3181                     default_account = acc_template_ref.get(template.property_account_expense_opening.id)
3182                 else:
3183                     default_account = acc_template_ref.get(template.property_account_income_opening.id)
3184             return default_account
3185
3186         journal_names = {
3187             'sale': _('Sales Journal'),
3188             'purchase': _('Purchase Journal'),
3189             'sale_refund': _('Sales Refund Journal'),
3190             'purchase_refund': _('Purchase Refund Journal'),
3191             'general': _('Miscellaneous Journal'),
3192             'situation': _('Opening Entries Journal'),
3193         }
3194         journal_codes = {
3195             'sale': _('SAJ'),
3196             'purchase': _('EXJ'),
3197             'sale_refund': _('SCNJ'),
3198             'purchase_refund': _('ECNJ'),
3199             'general': _('MISC'),
3200             'situation': _('OPEJ'),
3201         }
3202
3203         obj_data = self.pool.get('ir.model.data')
3204         analytic_journal_obj = self.pool.get('account.analytic.journal')
3205         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3206
3207         journal_data = []
3208         for journal_type in ['sale', 'purchase', 'sale_refund', 'purchase_refund', 'general', 'situation']:
3209             vals = {
3210                 'type': journal_type,
3211                 'name': journal_names[journal_type],
3212                 'code': journal_codes[journal_type],
3213                 'company_id': company_id,
3214                 'centralisation': journal_type == 'situation',
3215                 'analytic_journal_id': _get_analytic_journal(journal_type),
3216                 'default_credit_account_id': _get_default_account(journal_type, 'credit'),
3217                 'default_debit_account_id': _get_default_account(journal_type, 'debit'),
3218             }
3219             journal_data.append(vals)
3220         return journal_data
3221
3222     def generate_properties(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3223         """
3224         This method used for creating properties.
3225
3226         :param chart_template_id: id of the current chart template for which we need to create properties
3227         :param acc_template_ref: Mapping between ids of account templates and real accounts created from them
3228         :param company_id: company_id selected from wizard.multi.charts.accounts.
3229         :returns: True
3230         """
3231         property_obj = self.pool.get('ir.property')
3232         field_obj = self.pool.get('ir.model.fields')
3233         todo_list = [
3234             ('property_account_receivable','res.partner','account.account'),
3235             ('property_account_payable','res.partner','account.account'),
3236             ('property_account_expense_categ','product.category','account.account'),
3237             ('property_account_income_categ','product.category','account.account'),
3238             ('property_account_expense','product.template','account.account'),
3239             ('property_account_income','product.template','account.account'),
3240         ]
3241         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3242         for record in todo_list:
3243             account = getattr(template, record[0])
3244             value = account and 'account.account,' + str(acc_template_ref[account.id]) or False
3245             if value:
3246                 field = field_obj.search(cr, uid, [('name', '=', record[0]),('model', '=', record[1]),('relation', '=', record[2])], context=context)
3247                 vals = {
3248                     'name': record[0],
3249                     'company_id': company_id,
3250                     'fields_id': field[0],
3251                     'value': value,
3252                 }
3253                 property_ids = property_obj.search(cr, uid, [('name','=', record[0]),('company_id', '=', company_id)], context=context)
3254                 if property_ids:
3255                     #the property exist: modify it
3256                     property_obj.write(cr, uid, property_ids, vals, context=context)
3257                 else:
3258                     #create the property
3259                     property_obj.create(cr, uid, vals, context=context)
3260         return True
3261
3262     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):
3263         '''
3264         This function recursively loads the template objects and create the real objects from them.
3265
3266         :param template_id: id of the chart template to load
3267         :param company_id: id of the company the wizard is running for
3268         :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3269         :param obj_wizard: the current wizard for generating the COA from the templates
3270         :param acc_ref: Mapping between ids of account templates and real accounts created from them
3271         :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3272         :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3273         :returns: return a tuple with a dictionary containing
3274             * the mapping between the account template ids and the ids of the real accounts that have been generated
3275               from them, as first item,
3276             * a similar dictionary for mapping the tax templates and taxes, as second item,
3277             * a last identical containing the mapping of tax code templates and tax codes
3278         :rtype: tuple(dict, dict, dict)
3279         '''
3280         if acc_ref is None:
3281             acc_ref = {}
3282         if taxes_ref is None:
3283             taxes_ref = {}
3284         if tax_code_ref is None:
3285             tax_code_ref = {}
3286         template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3287         if template.parent_id:
3288             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)
3289             acc_ref.update(tmp1)
3290             taxes_ref.update(tmp2)
3291             tax_code_ref.update(tmp3)
3292         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)
3293         acc_ref.update(tmp1)
3294         taxes_ref.update(tmp2)
3295         tax_code_ref.update(tmp3)
3296         return acc_ref, taxes_ref, tax_code_ref
3297
3298     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):
3299         '''
3300         This function generates all the objects from the templates
3301
3302         :param template_id: id of the chart template to load
3303         :param company_id: id of the company the wizard is running for
3304         :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3305         :param obj_wizard: the current wizard for generating the COA from the templates
3306         :param acc_ref: Mapping between ids of account templates and real accounts created from them
3307         :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3308         :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3309         :returns: return a tuple with a dictionary containing
3310             * the mapping between the account template ids and the ids of the real accounts that have been generated
3311               from them, as first item,
3312             * a similar dictionary for mapping the tax templates and taxes, as second item,
3313             * a last identical containing the mapping of tax code templates and tax codes
3314         :rtype: tuple(dict, dict, dict)
3315         '''
3316         if account_ref is None:
3317             account_ref = {}
3318         if taxes_ref is None:
3319             taxes_ref = {}
3320         if tax_code_ref is None:
3321             tax_code_ref = {}
3322         template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3323         obj_tax_code_template = self.pool.get('account.tax.code.template')
3324         obj_acc_tax = self.pool.get('account.tax')
3325         obj_tax_temp = self.pool.get('account.tax.template')
3326         obj_acc_template = self.pool.get('account.account.template')
3327         obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
3328
3329         # create all the tax code.
3330         tax_code_ref.update(obj_tax_code_template.generate_tax_code(cr, uid, template.tax_code_root_id.id, company_id, context=context))
3331
3332         # Generate taxes from templates.
3333         tax_templates = [x for x in template.tax_template_ids]
3334         generated_tax_res = obj_tax_temp._generate_tax(cr, uid, tax_templates, tax_code_ref, company_id, context=context)
3335         taxes_ref.update(generated_tax_res['tax_template_to_tax'])
3336
3337         # Generating Accounts from templates.
3338         account_template_ref = obj_acc_template.generate_account(cr, uid, template_id, taxes_ref, account_ref, code_digits, company_id, context=context)
3339         account_ref.update(account_template_ref)
3340
3341         # writing account values on tax after creation of accounts
3342         for key,value in generated_tax_res['account_dict'].items():
3343             if value['account_collected_id'] or value['account_paid_id']:
3344                 obj_acc_tax.write(cr, uid, [key], {
3345                     'account_collected_id': account_ref.get(value['account_collected_id'], False),
3346                     'account_paid_id': account_ref.get(value['account_paid_id'], False),
3347                 })
3348
3349         # Create Journals
3350         self.generate_journals(cr, uid, template_id, account_ref, company_id, context=context)
3351
3352         # generate properties function
3353         self.generate_properties(cr, uid, template_id, account_ref, company_id, context=context)
3354
3355         # Generate Fiscal Position , Fiscal Position Accounts and Fiscal Position Taxes from templates
3356         obj_fiscal_position_template.generate_fiscal_position(cr, uid, template_id, taxes_ref, account_ref, company_id, context=context)
3357
3358         return account_ref, taxes_ref, tax_code_ref
3359
3360     def _create_tax_templates_from_rates(self, cr, uid, obj_wizard, company_id, context=None):
3361         '''
3362         This function checks if the chosen chart template is configured as containing a full set of taxes, and if
3363         it's not the case, it creates the templates for account.tax.code and for account.account.tax objects accordingly
3364         to the provided sale/purchase rates. Then it saves the new tax templates as default taxes to use for this chart
3365         template.
3366
3367         :param obj_wizard: browse record of wizard to generate COA from templates
3368         :param company_id: id of the company for wich the wizard is running
3369         :return: True
3370         '''
3371         obj_tax_code_template = self.pool.get('account.tax.code.template')
3372         obj_tax_temp = self.pool.get('account.tax.template')
3373         chart_template = obj_wizard.chart_template_id
3374         vals = {}
3375         all_parents = self._get_chart_parent_ids(cr, uid, chart_template, context=context)
3376         # create tax templates and tax code templates from purchase_tax_rate and sale_tax_rate fields
3377         if not chart_template.complete_tax_set:
3378             value = obj_wizard.sale_tax_rate
3379             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)
3380             obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Tax %.2f%%') % value})
3381             value = obj_wizard.purchase_tax_rate
3382             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)
3383             obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Purchase Tax %.2f%%') % value})
3384         return True
3385
3386     def execute(self, cr, uid, ids, context=None):
3387         '''
3388         This function is called at the confirmation of the wizard to generate the COA from the templates. It will read
3389         all the provided information to create the accounts, the banks, the journals, the taxes, the tax codes, the
3390         accounting properties... accordingly for the chosen company.
3391         '''
3392         if uid != SUPERUSER_ID and not self.pool['res.users'].has_group(cr, uid, 'base.group_erp_manager'):
3393             raise openerp.exceptions.AccessError(_("Only administrators can change the settings"))
3394         obj_data = self.pool.get('ir.model.data')
3395         ir_values_obj = self.pool.get('ir.values')
3396         obj_wizard = self.browse(cr, uid, ids[0])
3397         company_id = obj_wizard.company_id.id
3398
3399         self.pool.get('res.company').write(cr, uid, [company_id], {'currency_id': obj_wizard.currency_id.id})
3400
3401         # When we install the CoA of first company, set the currency to price types and pricelists
3402         if company_id==1:
3403             for ref in (('product','list_price'),('product','standard_price'),('product','list0'),('purchase','list0')):
3404                 try:
3405                     tmp2 = obj_data.get_object_reference(cr, uid, *ref)
3406                     if tmp2: 
3407                         self.pool[tmp2[0]].write(cr, uid, tmp2[1], {
3408                             'currency_id': obj_wizard.currency_id.id
3409                         })
3410                 except ValueError:
3411                     pass
3412
3413         # If the floats for sale/purchase rates have been filled, create templates from them
3414         self._create_tax_templates_from_rates(cr, uid, obj_wizard, company_id, context=context)
3415
3416         # Install all the templates objects and generate the real objects
3417         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)
3418
3419         # write values of default taxes for product as super user
3420         if obj_wizard.sale_tax and taxes_ref:
3421             ir_values_obj.set_default(cr, SUPERUSER_ID, 'product.template', "taxes_id", [taxes_ref[obj_wizard.sale_tax.id]], for_all_users=True, company_id=company_id)
3422         if obj_wizard.purchase_tax and taxes_ref:
3423             ir_values_obj.set_default(cr, SUPERUSER_ID, 'product.template', "supplier_taxes_id", [taxes_ref[obj_wizard.purchase_tax.id]], for_all_users=True, company_id=company_id)
3424
3425         # Create Bank journals
3426         self._create_bank_journals_from_o2m(cr, uid, obj_wizard, company_id, acc_template_ref, context=context)
3427         return {}
3428
3429     def _prepare_bank_journal(self, cr, uid, line, current_num, default_account_id, company_id, context=None):
3430         '''
3431         This function prepares the value to use for the creation of a bank journal created through the wizard of
3432         generating COA from templates.
3433
3434         :param line: dictionary containing the values encoded by the user related to his bank account
3435         :param current_num: integer corresponding to a counter of the already created bank journals through this wizard.
3436         :param default_account_id: id of the default debit.credit account created before for this journal.
3437         :param company_id: id of the company for which the wizard is running
3438         :return: mapping of field names and values
3439         :rtype: dict
3440         '''
3441         obj_data = self.pool.get('ir.model.data')
3442         obj_journal = self.pool.get('account.journal')
3443         
3444
3445         # we need to loop again to find next number for journal code
3446         # because we can't rely on the value current_num as,
3447         # its possible that we already have bank journals created (e.g. by the creation of res.partner.bank)
3448         # and the next number for account code might have been already used before for journal
3449         for num in xrange(current_num, 100):
3450             # journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
3451             journal_code = _('BNK')[:3] + str(num)
3452             ids = obj_journal.search(cr, uid, [('code', '=', journal_code), ('company_id', '=', company_id)], context=context)
3453             if not ids:
3454                 break
3455         else:
3456             raise osv.except_osv(_('Error!'), _('Cannot generate an unused journal code.'))
3457
3458         vals = {
3459                 'name': line['acc_name'],
3460                 'code': journal_code,
3461                 'type': line['account_type'] == 'cash' and 'cash' or 'bank',
3462                 'company_id': company_id,
3463                 'analytic_journal_id': False,
3464                 'currency': False,
3465                 'default_credit_account_id': default_account_id,
3466                 'default_debit_account_id': default_account_id,
3467         }
3468         if line['currency_id']:
3469             vals['currency'] = line['currency_id']
3470         
3471         return vals
3472
3473     def _prepare_bank_account(self, cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=None):
3474         '''
3475         This function prepares the value to use for the creation of the default debit and credit accounts of a
3476         bank journal created through the wizard of generating COA from templates.
3477
3478         :param line: dictionary containing the values encoded by the user related to his bank account
3479         :param new_code: integer corresponding to the next available number to use as account code
3480         :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3481             of the accounts that have been generated from them.
3482         :param ref_acc_bank: browse record of the account template set as root of all bank accounts for the chosen
3483             template
3484         :param company_id: id of the company for which the wizard is running
3485         :return: mapping of field names and values
3486         :rtype: dict
3487         '''
3488         obj_data = self.pool.get('ir.model.data')
3489
3490         # Get the id of the user types fr-or cash and bank
3491         tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_cash')
3492         cash_type = tmp and tmp[1] or False
3493         tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_bank')
3494         bank_type = tmp and tmp[1] or False
3495         return {
3496                 'name': line['acc_name'],
3497                 'currency_id': line['currency_id'],
3498                 'code': new_code,
3499                 'type': 'liquidity',
3500                 'user_type': line['account_type'] == 'cash' and cash_type or bank_type,
3501                 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
3502                 'company_id': company_id,
3503         }
3504
3505     def _create_bank_journals_from_o2m(self, cr, uid, obj_wizard, company_id, acc_template_ref, context=None):
3506         '''
3507         This function creates bank journals and its accounts for each line encoded in the field bank_accounts_id of the
3508         wizard.
3509
3510         :param obj_wizard: the current wizard that generates the COA from the templates.
3511         :param company_id: the id of the company for which the wizard is running.
3512         :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3513             of the accounts that have been generated from them.
3514         :return: True
3515         '''
3516         obj_acc = self.pool.get('account.account')
3517         obj_journal = self.pool.get('account.journal')
3518         code_digits = obj_wizard.code_digits
3519
3520         # Build a list with all the data to process
3521         journal_data = []
3522         if obj_wizard.bank_accounts_id:
3523             for acc in obj_wizard.bank_accounts_id:
3524                 vals = {
3525                     'acc_name': acc.acc_name,
3526                     'account_type': acc.account_type,
3527                     'currency_id': acc.currency_id.id,
3528                 }
3529                 journal_data.append(vals)
3530         ref_acc_bank = obj_wizard.chart_template_id.bank_account_view_id
3531         if journal_data and not ref_acc_bank.code:
3532             raise osv.except_osv(_('Configuration Error!'), _('You have to set a code for the bank account defined on the selected chart of accounts.'))
3533
3534         current_num = 1
3535         for line in journal_data:
3536             # Seek the next available number for the account code
3537             while True:
3538                 new_code = str(ref_acc_bank.code.ljust(code_digits-len(str(current_num)), '0')) + str(current_num)
3539                 ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)])
3540                 if not ids:
3541                     break
3542                 else:
3543                     current_num += 1
3544             # Create the default debit/credit accounts for this bank journal
3545             vals = self._prepare_bank_account(cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=context)
3546             default_account_id  = obj_acc.create(cr, uid, vals, context=context)
3547
3548             #create the bank journal
3549             vals_journal = self._prepare_bank_journal(cr, uid, line, current_num, default_account_id, company_id, context=context)
3550             obj_journal.create(cr, uid, vals_journal)
3551             current_num += 1
3552         return True
3553
3554
3555 class account_bank_accounts_wizard(osv.osv_memory):
3556     _name='account.bank.accounts.wizard'
3557
3558     _columns = {
3559         'acc_name': fields.char('Account Name.', required=True),
3560         'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True, ondelete='cascade'),
3561         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
3562         'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type'),
3563     }
3564
3565
3566 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: