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