[FIX] account_invoice: due date is reset to current date when we have no payment...
[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         args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
1011         if context.get('company_id', False):
1012             args.append(('company_id', '=', context['company_id']))
1013         else:
1014             company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
1015             args.append(('company_id', '=', company_id))
1016         result = []
1017         #WARNING: in next version the default value for account_periof_prefer_normal will be True
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,
1044                               [('code', 'ilike', name)] + args,
1045                               limit=limit,
1046                               context=context)
1047         if not ids:
1048             ids = self.search(cr, user,
1049                               [('name', operator, name)] + args,
1050                               limit=limit,
1051                               context=context)
1052         return self.name_get(cr, user, ids, context=context)
1053
1054     def write(self, cr, uid, ids, vals, context=None):
1055         if 'company_id' in vals:
1056             move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
1057             if move_lines:
1058                 raise osv.except_osv(_('Warning!'), _('This journal already contains items for this period, therefore you cannot modify its company field.'))
1059         return super(account_period, self).write(cr, uid, ids, vals, context=context)
1060
1061     def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
1062         if period_from_id == period_to_id:
1063             return [period_from_id]
1064         period_from = self.browse(cr, uid, period_from_id)
1065         period_date_start = period_from.date_start
1066         company1_id = period_from.company_id.id
1067         period_to = self.browse(cr, uid, period_to_id)
1068         period_date_stop = period_to.date_stop
1069         company2_id = period_to.company_id.id
1070         if company1_id != company2_id:
1071             raise osv.except_osv(_('Error!'), _('You should choose the periods that belong to the same company.'))
1072         if period_date_start > period_date_stop:
1073             raise osv.except_osv(_('Error!'), _('Start period should precede then end period.'))
1074
1075         # /!\ We do not include a criterion on the company_id field below, to allow producing consolidated reports
1076         # on multiple companies. It will only work when start/end periods are selected and no fiscal year is chosen.
1077
1078         #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).
1079         if period_from.special:
1080             return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop)])
1081         return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('special', '=', False)])
1082
1083 account_period()
1084
1085 class account_journal_period(osv.osv):
1086     _name = "account.journal.period"
1087     _description = "Journal Period"
1088
1089     def _icon_get(self, cr, uid, ids, field_name, arg=None, context=None):
1090         result = {}.fromkeys(ids, 'STOCK_NEW')
1091         for r in self.read(cr, uid, ids, ['state']):
1092             result[r['id']] = {
1093                 'draft': 'STOCK_NEW',
1094                 'printed': 'STOCK_PRINT_PREVIEW',
1095                 'done': 'STOCK_DIALOG_AUTHENTICATION',
1096             }.get(r['state'], 'STOCK_NEW')
1097         return result
1098
1099     _columns = {
1100         'name': fields.char('Journal-Period Name', size=64, required=True),
1101         'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
1102         'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
1103         'icon': fields.function(_icon_get, string='Icon', type='char', size=32),
1104         '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."),
1105         'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'Status', required=True, readonly=True,
1106                                   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.'),
1107         'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
1108         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
1109     }
1110
1111     def _check(self, cr, uid, ids, context=None):
1112         for obj in self.browse(cr, uid, ids, context=context):
1113             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))
1114             res = cr.fetchall()
1115             if res:
1116                 raise osv.except_osv(_('Error!'), _('You cannot modify/delete a journal with entries for this period.'))
1117         return True
1118
1119     def write(self, cr, uid, ids, vals, context=None):
1120         self._check(cr, uid, ids, context=context)
1121         return super(account_journal_period, self).write(cr, uid, ids, vals, context=context)
1122
1123     def create(self, cr, uid, vals, context=None):
1124         period_id = vals.get('period_id',False)
1125         if period_id:
1126             period = self.pool.get('account.period').browse(cr, uid, period_id, context=context)
1127             vals['state']=period.state
1128         return super(account_journal_period, self).create(cr, uid, vals, context)
1129
1130     def unlink(self, cr, uid, ids, context=None):
1131         self._check(cr, uid, ids, context=context)
1132         return super(account_journal_period, self).unlink(cr, uid, ids, context=context)
1133
1134     _defaults = {
1135         'state': 'draft',
1136         'active': True,
1137     }
1138     _order = "period_id"
1139
1140 account_journal_period()
1141
1142 class account_fiscalyear(osv.osv):
1143     _inherit = "account.fiscalyear"
1144     _description = "Fiscal Year"
1145     _columns = {
1146         'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1147     }
1148
1149     def copy(self, cr, uid, id, default=None, context=None):
1150         default = {} if default is None else default.copy()
1151         default.update({
1152             'period_ids': [],
1153             'end_journal_period_id': False
1154         })
1155         return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1156
1157 account_fiscalyear()
1158 #----------------------------------------------------------
1159 # Entries
1160 #----------------------------------------------------------
1161 class account_move(osv.osv):
1162     _name = "account.move"
1163     _description = "Account Entry"
1164     _order = 'id desc'
1165
1166     def account_move_prepare(self, cr, uid, journal_id, date=False, ref='', company_id=False, context=None):
1167         '''
1168         Prepares and returns a dictionary of values, ready to be passed to create() based on the parameters received.
1169         '''
1170         if not date:
1171             date = fields.date.today()
1172         period_obj = self.pool.get('account.period')
1173         if not company_id:
1174             user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1175             company_id = user.company_id.id
1176         if context is None:
1177             context = {}
1178         #put the company in context to find the good period
1179         ctx = context.copy()
1180         ctx.update({'company_id': company_id, 'account_period_prefer_normal': True})
1181         return {
1182             'journal_id': journal_id,
1183             'date': date,
1184             'period_id': period_obj.find(cr, uid, date, context=ctx)[0],
1185             'ref': ref,
1186             'company_id': company_id,
1187         }
1188
1189     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1190         """
1191         Returns a list of tupples containing id, name, as internally it is called {def name_get}
1192         result format: {[(id, name), (id, name), ...]}
1193
1194         @param cr: A database cursor
1195         @param user: ID of the user currently logged in
1196         @param name: name to search
1197         @param args: other arguments
1198         @param operator: default operator is 'ilike', it can be changed
1199         @param context: context arguments, like lang, time zone
1200         @param limit: Returns first 'n' ids of complete result, default is 80.
1201
1202         @return: Returns a list of tuples containing id and name
1203         """
1204
1205         if not args:
1206           args = []
1207         ids = []
1208         if name:
1209             ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1210
1211         if not ids and name and type(name) == int:
1212             ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1213
1214         if not ids:
1215             ids += self.search(cr, user, args, limit=limit, context=context)
1216
1217         return self.name_get(cr, user, ids, context=context)
1218
1219     def name_get(self, cursor, user, ids, context=None):
1220         if isinstance(ids, (int, long)):
1221             ids = [ids]
1222         if not ids:
1223             return []
1224         res = []
1225         data_move = self.pool.get('account.move').browse(cursor, user, ids, context=context)
1226         for move in data_move:
1227             if move.state=='draft':
1228                 name = '*' + str(move.id)
1229             else:
1230                 name = move.name
1231             res.append((move.id, name))
1232         return res
1233
1234     def _get_period(self, cr, uid, context=None):
1235         ctx = dict(context or {}, account_period_prefer_normal=True)
1236         period_ids = self.pool.get('account.period').find(cr, uid, context=ctx)
1237         return period_ids[0]
1238
1239     def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1240         if not ids: return {}
1241         cr.execute( 'SELECT move_id, SUM(debit) '\
1242                     'FROM account_move_line '\
1243                     'WHERE move_id IN %s '\
1244                     'GROUP BY move_id', (tuple(ids),))
1245         result = dict(cr.fetchall())
1246         for id in ids:
1247             result.setdefault(id, 0.0)
1248         return result
1249
1250     def _search_amount(self, cr, uid, obj, name, args, context):
1251         ids = set()
1252         for cond in args:
1253             amount = cond[2]
1254             if isinstance(cond[2],(list,tuple)):
1255                 if cond[1] in ['in','not in']:
1256                     amount = tuple(cond[2])
1257                 else:
1258                     continue
1259             else:
1260                 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1261                     continue
1262
1263             cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1264             res_ids = set(id[0] for id in cr.fetchall())
1265             ids = ids and (ids & res_ids) or res_ids
1266         if ids:
1267             return [('id', 'in', tuple(ids))]
1268         return [('id', '=', '0')]
1269
1270     _columns = {
1271         'name': fields.char('Number', size=64, required=True),
1272         'ref': fields.char('Reference', size=64),
1273         'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1274         'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1275         'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'Status', required=True, readonly=True,
1276             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.'),
1277         'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1278         '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.'),
1279         'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1280         'amount': fields.function(_amount_compute, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1281         'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}, select=True),
1282         'narration':fields.text('Internal Note'),
1283         'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1284         '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"),
1285     }
1286
1287     _defaults = {
1288         'name': '/',
1289         'state': 'draft',
1290         'period_id': _get_period,
1291         'date': fields.date.context_today,
1292         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1293     }
1294
1295     def _check_centralisation(self, cursor, user, ids, context=None):
1296         for move in self.browse(cursor, user, ids, context=context):
1297             if move.journal_id.centralisation:
1298                 move_ids = self.search(cursor, user, [
1299                     ('period_id', '=', move.period_id.id),
1300                     ('journal_id', '=', move.journal_id.id),
1301                     ])
1302                 if len(move_ids) > 1:
1303                     return False
1304         return True
1305
1306     _constraints = [
1307         (_check_centralisation,
1308             'You cannot create more than one move per period on a centralized journal.',
1309             ['journal_id']),
1310     ]
1311
1312     def post(self, cr, uid, ids, context=None):
1313         if context is None:
1314             context = {}
1315         invoice = context.get('invoice', False)
1316         valid_moves = self.validate(cr, uid, ids, context)
1317
1318         if not valid_moves:
1319             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.'))
1320         obj_sequence = self.pool.get('ir.sequence')
1321         for move in self.browse(cr, uid, valid_moves, context=context):
1322             if move.name =='/':
1323                 new_name = False
1324                 journal = move.journal_id
1325
1326                 if invoice and invoice.internal_number:
1327                     new_name = invoice.internal_number
1328                 else:
1329                     if journal.sequence_id:
1330                         c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1331                         new_name = obj_sequence.next_by_id(cr, uid, journal.sequence_id.id, c)
1332                     else:
1333                         raise osv.except_osv(_('Error!'), _('Please define a sequence on the journal.'))
1334
1335                 if new_name:
1336                     self.write(cr, uid, [move.id], {'name':new_name})
1337
1338         cr.execute('UPDATE account_move '\
1339                    'SET state=%s '\
1340                    'WHERE id IN %s',
1341                    ('posted', tuple(valid_moves),))
1342         return True
1343
1344     def button_validate(self, cursor, user, ids, context=None):
1345         for move in self.browse(cursor, user, ids, context=context):
1346             # check that all accounts have the same topmost ancestor
1347             top_common = None
1348             for line in move.line_id:
1349                 account = line.account_id
1350                 top_account = account
1351                 while top_account.parent_id:
1352                     top_account = top_account.parent_id
1353                 if not top_common:
1354                     top_common = top_account
1355                 elif top_account.id != top_common.id:
1356                     raise osv.except_osv(_('Error!'),
1357                                          _('You cannot validate this journal entry because account "%s" does not belong to chart of accounts "%s".') % (account.name, top_common.name))
1358         return self.post(cursor, user, ids, context=context)
1359
1360     def button_cancel(self, cr, uid, ids, context=None):
1361         for line in self.browse(cr, uid, ids, context=context):
1362             if not line.journal_id.update_posted:
1363                 raise osv.except_osv(_('Error!'), _('You cannot modify a posted entry of this journal.\nFirst you should set the journal to allow cancelling entries.'))
1364         if ids:
1365             cr.execute('UPDATE account_move '\
1366                        'SET state=%s '\
1367                        'WHERE id IN %s', ('draft', tuple(ids),))
1368         return True
1369
1370     def write(self, cr, uid, ids, vals, context=None):
1371         if context is None:
1372             context = {}
1373         c = context.copy()
1374         c['novalidate'] = True
1375         result = super(account_move, self).write(cr, uid, ids, vals, c)
1376         self.validate(cr, uid, ids, context=context)
1377         return result
1378
1379     #
1380     # TODO: Check if period is closed !
1381     #
1382     def create(self, cr, uid, vals, context=None):
1383         if context is None:
1384             context = {}
1385         if 'line_id' in vals and context.get('copy'):
1386             for l in vals['line_id']:
1387                 if not l[0]:
1388                     l[2].update({
1389                         'reconcile_id':False,
1390                         'reconcile_partial_id':False,
1391                         'analytic_lines':False,
1392                         'invoice':False,
1393                         'ref':False,
1394                         'balance':False,
1395                         'account_tax_id':False,
1396                         'statement_id': False,
1397                     })
1398
1399             if 'journal_id' in vals and vals.get('journal_id', False):
1400                 for l in vals['line_id']:
1401                     if not l[0]:
1402                         l[2]['journal_id'] = vals['journal_id']
1403                 context['journal_id'] = vals['journal_id']
1404             if 'period_id' in vals:
1405                 for l in vals['line_id']:
1406                     if not l[0]:
1407                         l[2]['period_id'] = vals['period_id']
1408                 context['period_id'] = vals['period_id']
1409             else:
1410                 default_period = self._get_period(cr, uid, context)
1411                 for l in vals['line_id']:
1412                     if not l[0]:
1413                         l[2]['period_id'] = default_period
1414                 context['period_id'] = default_period
1415
1416         if 'line_id' in vals:
1417             c = context.copy()
1418             c['novalidate'] = True
1419             c['period_id'] = vals['period_id'] if 'period_id' in vals else self._get_period(cr, uid, context)
1420             c['journal_id'] = vals['journal_id']
1421             if 'date' in vals: c['date'] = vals['date']
1422             result = super(account_move, self).create(cr, uid, vals, c)
1423             self.validate(cr, uid, [result], context)
1424         else:
1425             result = super(account_move, self).create(cr, uid, vals, context)
1426         return result
1427
1428     def copy(self, cr, uid, id, default=None, context=None):
1429         default = {} if default is None else default.copy()
1430         context = {} if context is None else context.copy()
1431         default.update({
1432             'state':'draft',
1433             'ref': False,
1434             'name':'/',
1435         })
1436         context.update({
1437             'copy':True
1438         })
1439         return super(account_move, self).copy(cr, uid, id, default, context)
1440
1441     def unlink(self, cr, uid, ids, context=None, check=True):
1442         if context is None:
1443             context = {}
1444         toremove = []
1445         obj_move_line = self.pool.get('account.move.line')
1446         for move in self.browse(cr, uid, ids, context=context):
1447             if move['state'] != 'draft':
1448                 raise osv.except_osv(_('User Error!'),
1449                         _('You cannot delete a posted journal entry "%s".') % \
1450                                 move['name'])
1451             for line in move.line_id:
1452                 if line.invoice:
1453                     raise osv.except_osv(_('User Error!'),
1454                             _("Move cannot be deleted if linked to an invoice. (Invoice: %s - Move ID:%s)") % \
1455                                     (line.invoice.number,move.name))
1456             line_ids = map(lambda x: x.id, move.line_id)
1457             context['journal_id'] = move.journal_id.id
1458             context['period_id'] = move.period_id.id
1459             obj_move_line._update_check(cr, uid, line_ids, context)
1460             obj_move_line.unlink(cr, uid, line_ids, context=context)
1461             toremove.append(move.id)
1462         result = super(account_move, self).unlink(cr, uid, toremove, context)
1463         return result
1464
1465     def _compute_balance(self, cr, uid, id, context=None):
1466         move = self.browse(cr, uid, id, context=context)
1467         amount = 0
1468         for line in move.line_id:
1469             amount+= (line.debit - line.credit)
1470         return amount
1471
1472     def _centralise(self, cr, uid, move, mode, context=None):
1473         assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1474         currency_obj = self.pool.get('res.currency')
1475         if context is None:
1476             context = {}
1477
1478         if mode=='credit':
1479             account_id = move.journal_id.default_debit_account_id.id
1480             mode2 = 'debit'
1481             if not account_id:
1482                 raise osv.except_osv(_('User Error!'),
1483                         _('There is no default debit account defined \n' \
1484                                 'on journal "%s".') % move.journal_id.name)
1485         else:
1486             account_id = move.journal_id.default_credit_account_id.id
1487             mode2 = 'credit'
1488             if not account_id:
1489                 raise osv.except_osv(_('User Error!'),
1490                         _('There is no default credit account defined \n' \
1491                                 'on journal "%s".') % move.journal_id.name)
1492
1493         # find the first line of this move with the current mode
1494         # or create it if it doesn't exist
1495         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1496         res = cr.fetchone()
1497         if res:
1498             line_id = res[0]
1499         else:
1500             context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1501             line_id = self.pool.get('account.move.line').create(cr, uid, {
1502                 'name': _(mode.capitalize()+' Centralisation'),
1503                 'centralisation': mode,
1504                 'partner_id': False,
1505                 'account_id': account_id,
1506                 'move_id': move.id,
1507                 'journal_id': move.journal_id.id,
1508                 'period_id': move.period_id.id,
1509                 'date': move.period_id.date_stop,
1510                 'debit': 0.0,
1511                 'credit': 0.0,
1512             }, context)
1513
1514         # find the first line of this move with the other mode
1515         # so that we can exclude it from our calculation
1516         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1517         res = cr.fetchone()
1518         if res:
1519             line_id2 = res[0]
1520         else:
1521             line_id2 = 0
1522
1523         cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1524         result = cr.fetchone()[0] or 0.0
1525         cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1526
1527         #adjust also the amount in currency if needed
1528         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,))
1529         for row in cr.dictfetchall():
1530             currency_id = currency_obj.browse(cr, uid, row['currency_id'], context=context)
1531             if not currency_obj.is_zero(cr, uid, currency_id, row['amount_currency']):
1532                 amount_currency = row['amount_currency'] * -1
1533                 account_id = amount_currency > 0 and move.journal_id.default_debit_account_id.id or move.journal_id.default_credit_account_id.id
1534                 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']))
1535                 res = cr.fetchone()
1536                 if res:
1537                     cr.execute('update account_move_line set amount_currency=%s , account_id=%s where id=%s', (amount_currency, account_id, res[0]))
1538                 else:
1539                     context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1540                     line_id = self.pool.get('account.move.line').create(cr, uid, {
1541                         'name': _('Currency Adjustment'),
1542                         'centralisation': 'currency',
1543                         'partner_id': False,
1544                         'account_id': account_id,
1545                         'move_id': move.id,
1546                         'journal_id': move.journal_id.id,
1547                         'period_id': move.period_id.id,
1548                         'date': move.period_id.date_stop,
1549                         'debit': 0.0,
1550                         'credit': 0.0,
1551                         'currency_id': row['currency_id'],
1552                         'amount_currency': amount_currency,
1553                     }, context)
1554
1555         return True
1556
1557     #
1558     # Validate a balanced move. If it is a centralised journal, create a move.
1559     #
1560     def validate(self, cr, uid, ids, context=None):
1561         if context and ('__last_update' in context):
1562             del context['__last_update']
1563
1564         valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1565         obj_analytic_line = self.pool.get('account.analytic.line')
1566         obj_move_line = self.pool.get('account.move.line')
1567         for move in self.browse(cr, uid, ids, context):
1568             # Unlink old analytic lines on move_lines
1569             for obj_line in move.line_id:
1570                 for obj in obj_line.analytic_lines:
1571                     obj_analytic_line.unlink(cr,uid,obj.id)
1572
1573             journal = move.journal_id
1574             amount = 0
1575             line_ids = []
1576             line_draft_ids = []
1577             company_id = None
1578             for line in move.line_id:
1579                 amount += line.debit - line.credit
1580                 line_ids.append(line.id)
1581                 if line.state=='draft':
1582                     line_draft_ids.append(line.id)
1583
1584                 if not company_id:
1585                     company_id = line.account_id.company_id.id
1586                 if not company_id == line.account_id.company_id.id:
1587                     raise osv.except_osv(_('Error!'), _("Cannot create moves for different companies."))
1588
1589                 if line.account_id.currency_id and line.currency_id:
1590                     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):
1591                         raise osv.except_osv(_('Error!'), _("""Cannot create move with currency different from ..""") % (line.account_id.code, line.account_id.name))
1592
1593             if abs(amount) < 10 ** -4:
1594                 # If the move is balanced
1595                 # Add to the list of valid moves
1596                 # (analytic lines will be created later for valid moves)
1597                 valid_moves.append(move)
1598
1599                 # Check whether the move lines are confirmed
1600
1601                 if not line_draft_ids:
1602                     continue
1603                 # Update the move lines (set them as valid)
1604
1605                 obj_move_line.write(cr, uid, line_draft_ids, {
1606                     'state': 'valid'
1607                 }, context, check=False)
1608
1609                 account = {}
1610                 account2 = {}
1611
1612                 if journal.type in ('purchase','sale'):
1613                     for line in move.line_id:
1614                         code = amount = 0
1615                         key = (line.account_id.id, line.tax_code_id.id)
1616                         if key in account2:
1617                             code = account2[key][0]
1618                             amount = account2[key][1] * (line.debit + line.credit)
1619                         elif line.account_id.id in account:
1620                             code = account[line.account_id.id][0]
1621                             amount = account[line.account_id.id][1] * (line.debit + line.credit)
1622                         if (code or amount) and not (line.tax_code_id or line.tax_amount):
1623                             obj_move_line.write(cr, uid, [line.id], {
1624                                 'tax_code_id': code,
1625                                 'tax_amount': amount
1626                             }, context, check=False)
1627             elif journal.centralisation:
1628                 # If the move is not balanced, it must be centralised...
1629
1630                 # Add to the list of valid moves
1631                 # (analytic lines will be created later for valid moves)
1632                 valid_moves.append(move)
1633
1634                 #
1635                 # Update the move lines (set them as valid)
1636                 #
1637                 self._centralise(cr, uid, move, 'debit', context=context)
1638                 self._centralise(cr, uid, move, 'credit', context=context)
1639                 obj_move_line.write(cr, uid, line_draft_ids, {
1640                     'state': 'valid'
1641                 }, context, check=False)
1642             else:
1643                 # We can't validate it (it's unbalanced)
1644                 # Setting the lines as draft
1645                 obj_move_line.write(cr, uid, line_ids, {
1646                     'state': 'draft'
1647                 }, context, check=False)
1648         # Create analytic lines for the valid moves
1649         for record in valid_moves:
1650             obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1651
1652         valid_moves = [move.id for move in valid_moves]
1653         return len(valid_moves) > 0 and valid_moves or False
1654
1655 account_move()
1656
1657 class account_move_reconcile(osv.osv):
1658     _name = "account.move.reconcile"
1659     _description = "Account Reconciliation"
1660     _columns = {
1661         'name': fields.char('Name', size=64, required=True),
1662         'type': fields.char('Type', size=16, required=True),
1663         'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1664         'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1665         'create_date': fields.date('Creation date', readonly=True),
1666         'opening_reconciliation': fields.boolean('Opening Entries Reconciliation', help="Is this reconciliation produced by the opening of a new fiscal year ?."),
1667     }
1668     _defaults = {
1669         'name': lambda self,cr,uid,ctx=None: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile', context=ctx) or '/',
1670     }
1671     
1672     # You cannot unlink a reconciliation if it is a opening_reconciliation one,
1673     # you should use the generate opening entries wizard for that
1674     def unlink(self, cr, uid, ids, context=None):
1675         for move_rec in self.browse(cr, uid, ids, context=context):
1676             if move_rec.opening_reconciliation:
1677                 raise osv.except_osv(_('Error!'), _('You cannot unreconcile journal items if they has been generated by the \
1678                                                         opening/closing fiscal year process.'))
1679         return super(account_move_reconcile, self).unlink(cr, uid, ids, context=context)
1680     
1681     # Look in the line_id and line_partial_ids to ensure the partner is the same or empty
1682     # on all lines. We allow that only for opening/closing period
1683     def _check_same_partner(self, cr, uid, ids, context=None):
1684         for reconcile in self.browse(cr, uid, ids, context=context):
1685             move_lines = []
1686             if not reconcile.opening_reconciliation:
1687                 if reconcile.line_id:
1688                     first_partner = reconcile.line_id[0].partner_id.id
1689                     move_lines = reconcile.line_id
1690                 elif reconcile.line_partial_ids:
1691                     first_partner = reconcile.line_partial_ids[0].partner_id.id
1692                     move_lines = reconcile.line_partial_ids
1693                 if any([(line.account_id.type in ('receivable', 'payable') and line.partner_id.id != first_partner) for line in move_lines]):
1694                     return False
1695         return True
1696
1697     _constraints = [
1698         (_check_same_partner, 'You can only reconcile journal items with the same partner.', ['line_id']),
1699     ]
1700     
1701     def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None):
1702         total = 0.0
1703         for rec in self.browse(cr, uid, ids, context=context):
1704             for line in rec.line_partial_ids:
1705                 if line.account_id.currency_id:
1706                     total += line.amount_currency
1707                 else:
1708                     total += (line.debit or 0.0) - (line.credit or 0.0)
1709         if not total:
1710             self.pool.get('account.move.line').write(cr, uid,
1711                 map(lambda x: x.id, rec.line_partial_ids),
1712                 {'reconcile_id': rec.id }
1713             )
1714         return True
1715
1716     def name_get(self, cr, uid, ids, context=None):
1717         if not ids:
1718             return []
1719         result = []
1720         for r in self.browse(cr, uid, ids, context=context):
1721             total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1722             if total:
1723                 name = '%s (%.2f)' % (r.name, total)
1724                 result.append((r.id,name))
1725             else:
1726                 result.append((r.id,r.name))
1727         return result
1728
1729 account_move_reconcile()
1730
1731 #----------------------------------------------------------
1732 # Tax
1733 #----------------------------------------------------------
1734 """
1735 a documenter
1736 child_depend: la taxe depend des taxes filles
1737 """
1738 class account_tax_code(osv.osv):
1739     """
1740     A code for the tax object.
1741
1742     This code is used for some tax declarations.
1743     """
1744     def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1745         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1746         if context.get('based_on', 'invoices') == 'payments':
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                         LEFT JOIN account_invoice invoice ON \
1751                             (invoice.move_id = move.id) \
1752                     WHERE line.tax_code_id IN %s '+where+' \
1753                         AND move.id = line.move_id \
1754                         AND ((invoice.state = \'paid\') \
1755                             OR (invoice.id IS NULL)) \
1756                             GROUP BY line.tax_code_id',
1757                                 (parent_ids,) + where_params)
1758         else:
1759             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1760                     FROM account_move_line AS line, \
1761                     account_move AS move \
1762                     WHERE line.tax_code_id IN %s '+where+' \
1763                     AND move.id = line.move_id \
1764                     GROUP BY line.tax_code_id',
1765                        (parent_ids,) + where_params)
1766         res=dict(cr.fetchall())
1767         obj_precision = self.pool.get('decimal.precision')
1768         res2 = {}
1769         for record in self.browse(cr, uid, ids, context=context):
1770             def _rec_get(record):
1771                 amount = res.get(record.id, 0.0)
1772                 for rec in record.child_ids:
1773                     amount += _rec_get(rec) * rec.sign
1774                 return amount
1775             res2[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1776         return res2
1777
1778     def _sum_year(self, cr, uid, ids, name, args, context=None):
1779         if context is None:
1780             context = {}
1781         move_state = ('posted', )
1782         if context.get('state', 'all') == 'all':
1783             move_state = ('draft', 'posted', )
1784         if context.get('fiscalyear_id', False):
1785             fiscalyear_id = [context['fiscalyear_id']]
1786         else:
1787             fiscalyear_id = self.pool.get('account.fiscalyear').finds(cr, uid, exception=False)
1788         where = ''
1789         where_params = ()
1790         if fiscalyear_id:
1791             pids = []
1792             for fy in fiscalyear_id:
1793                 pids += map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fy).period_ids)
1794             if pids:
1795                 where = ' AND line.period_id IN %s AND move.state IN %s '
1796                 where_params = (tuple(pids), move_state)
1797         return self._sum(cr, uid, ids, name, args, context,
1798                 where=where, where_params=where_params)
1799
1800     def _sum_period(self, cr, uid, ids, name, args, context):
1801         if context is None:
1802             context = {}
1803         move_state = ('posted', )
1804         if context.get('state', False) == 'all':
1805             move_state = ('draft', 'posted', )
1806         if context.get('period_id', False):
1807             period_id = context['period_id']
1808         else:
1809             ctx = dict(context, account_period_prefer_normal=True)
1810             period_id = self.pool.get('account.period').find(cr, uid, context=ctx)
1811             if not period_id:
1812                 return dict.fromkeys(ids, 0.0)
1813             period_id = period_id[0]
1814         return self._sum(cr, uid, ids, name, args, context,
1815                 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1816
1817     _name = 'account.tax.code'
1818     _description = 'Tax Code'
1819     _rec_name = 'code'
1820     _columns = {
1821         'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1822         'code': fields.char('Case Code', size=64),
1823         'info': fields.text('Description'),
1824         'sum': fields.function(_sum_year, string="Year Sum"),
1825         'sum_period': fields.function(_sum_period, string="Period Sum"),
1826         'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1827         'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1828         'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1829         'company_id': fields.many2one('res.company', 'Company', required=True),
1830         '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.'),
1831         '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"),
1832         'sequence': fields.integer('Sequence', help="Determine the display order in the report 'Accounting \ Reporting \ Generic Reporting \ Taxes \ Taxes Report'"),
1833     }
1834
1835     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1836         if not args:
1837             args = []
1838         if context is None:
1839             context = {}
1840         ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1841         return self.name_get(cr, user, ids, context)
1842
1843     def name_get(self, cr, uid, ids, context=None):
1844         if isinstance(ids, (int, long)):
1845             ids = [ids]
1846         if not ids:
1847             return []
1848         if isinstance(ids, (int, long)):
1849             ids = [ids]
1850         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1851         return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1852                 for x in reads]
1853
1854     def _default_company(self, cr, uid, context=None):
1855         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1856         if user.company_id:
1857             return user.company_id.id
1858         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1859     _defaults = {
1860         'company_id': _default_company,
1861         'sign': 1.0,
1862         'notprintable': False,
1863     }
1864
1865     def copy(self, cr, uid, id, default=None, context=None):
1866         if default is None:
1867             default = {}
1868         default = default.copy()
1869         default.update({'line_ids': []})
1870         return super(account_tax_code, self).copy(cr, uid, id, default, context)
1871
1872     _check_recursion = check_cycle
1873     _constraints = [
1874         (_check_recursion, 'Error!\nYou cannot create recursive accounts.', ['parent_id'])
1875     ]
1876     _order = 'code'
1877
1878 account_tax_code()
1879
1880 def get_precision_tax():
1881     def change_digit_tax(cr):
1882         res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, SUPERUSER_ID, 'Account')
1883         return (16, res+3)
1884     return change_digit_tax
1885
1886 class account_tax(osv.osv):
1887     """
1888     A tax object.
1889
1890     Type: percent, fixed, none, code
1891         PERCENT: tax = price * amount
1892         FIXED: tax = price + amount
1893         NONE: no tax line
1894         CODE: execute python code. localcontext = {'price_unit':pu}
1895             return result in the context
1896             Ex: result=round(price_unit*0.21,4)
1897     """
1898     def copy_data(self, cr, uid, id, default=None, context=None):
1899         if default is None:
1900             default = {}
1901         name = self.read(cr, uid, id, ['name'], context=context)['name']
1902         default = default.copy()
1903         default.update({'name': name + _(' (Copy)')})
1904         return super(account_tax, self).copy_data(cr, uid, id, default=default, context=context)
1905
1906     _name = 'account.tax'
1907     _description = 'Tax'
1908     _columns = {
1909         'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1910         '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."),
1911         'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For taxes of type percentage, enter % ratio between 0-1."),
1912         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the tax without removing it."),
1913         'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1914             help="The computation method for the tax amount."),
1915         'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1916             help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1917         '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."),
1918         '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."),
1919         '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."),
1920         '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."),
1921         '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."),
1922         'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1923         'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1924         '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."),
1925         'python_compute':fields.text('Python Code'),
1926         'python_compute_inv':fields.text('Python Code (reverse)'),
1927         'python_applicable':fields.text('Python Code'),
1928
1929         #
1930         # Fields used for the Tax declaration
1931         #
1932         'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the tax declaration."),
1933         'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the tax declaration."),
1934         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1935         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1936
1937         # Same fields for refund invoices
1938
1939         'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the tax declaration."),
1940         'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the tax declaration."),
1941         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1942         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1943         '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"),
1944         'company_id': fields.many2one('res.company', 'Company', required=True),
1945         'description': fields.char('Tax Code'),
1946         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1947         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1948
1949     }
1950     _sql_constraints = [
1951         ('name_company_uniq', 'unique(name, company_id)', 'Tax Name must be unique per company!'),
1952     ]
1953
1954     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1955         """
1956         Returns a list of tupples containing id, name, as internally it is called {def name_get}
1957         result format: {[(id, name), (id, name), ...]}
1958
1959         @param cr: A database cursor
1960         @param user: ID of the user currently logged in
1961         @param name: name to search
1962         @param args: other arguments
1963         @param operator: default operator is 'ilike', it can be changed
1964         @param context: context arguments, like lang, time zone
1965         @param limit: Returns first 'n' ids of complete result, default is 80.
1966
1967         @return: Returns a list of tupples containing id and name
1968         """
1969         if not args:
1970             args = []
1971         if context is None:
1972             context = {}
1973         ids = []
1974         if name:
1975             ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context)
1976             if not ids:
1977                 ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context)
1978         else:
1979             ids = self.search(cr, user, args, limit=limit, context=context or {})
1980         return self.name_get(cr, user, ids, context=context)
1981
1982     def write(self, cr, uid, ids, vals, context=None):
1983         if vals.get('type', False) and vals['type'] in ('none', 'code'):
1984             vals.update({'amount': 0.0})
1985         return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1986
1987     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1988         journal_pool = self.pool.get('account.journal')
1989
1990         if context and context.has_key('type'):
1991             if context.get('type') in ('out_invoice','out_refund'):
1992                 args += [('type_tax_use','in',['sale','all'])]
1993             elif context.get('type') in ('in_invoice','in_refund'):
1994                 args += [('type_tax_use','in',['purchase','all'])]
1995
1996         if context and context.has_key('journal_id'):
1997             journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1998             if journal.type in ('sale', 'purchase'):
1999                 args += [('type_tax_use','in',[journal.type,'all'])]
2000
2001         return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
2002
2003     def name_get(self, cr, uid, ids, context=None):
2004         if not ids:
2005             return []
2006         res = []
2007         for record in self.read(cr, uid, ids, ['description','name'], context=context):
2008             name = record['description'] and record['description'] or record['name']
2009             res.append((record['id'],name ))
2010         return res
2011
2012     def _default_company(self, cr, uid, context=None):
2013         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2014         if user.company_id:
2015             return user.company_id.id
2016         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2017
2018     _defaults = {
2019         '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''',
2020         'python_compute_inv': '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
2021         'applicable_type': 'true',
2022         'type': 'percent',
2023         'amount': 0,
2024         'price_include': 0,
2025         'active': 1,
2026         'type_tax_use': 'all',
2027         'sequence': 1,
2028         'ref_tax_sign': 1,
2029         'ref_base_sign': 1,
2030         'tax_sign': 1,
2031         'base_sign': 1,
2032         'include_base_amount': False,
2033         'company_id': _default_company,
2034     }
2035     _order = 'sequence'
2036
2037     def _applicable(self, cr, uid, taxes, price_unit, product=None, partner=None):
2038         res = []
2039         for tax in taxes:
2040             if tax.applicable_type=='code':
2041                 localdict = {'price_unit':price_unit, 'product':product, 'partner':partner}
2042                 exec tax.python_applicable in localdict
2043                 if localdict.get('result', False):
2044                     res.append(tax)
2045             else:
2046                 res.append(tax)
2047         return res
2048
2049     def _unit_compute(self, cr, uid, taxes, price_unit, product=None, partner=None, quantity=0):
2050         taxes = self._applicable(cr, uid, taxes, price_unit ,product, partner)
2051         res = []
2052         cur_price_unit=price_unit
2053         for tax in taxes:
2054             # we compute the amount for the current tax object and append it to the result
2055             data = {'id':tax.id,
2056                     'name':tax.description and tax.description + " - " + tax.name or tax.name,
2057                     'account_collected_id':tax.account_collected_id.id,
2058                     'account_paid_id':tax.account_paid_id.id,
2059                     'account_analytic_collected_id': tax.account_analytic_collected_id.id,
2060                     'account_analytic_paid_id': tax.account_analytic_paid_id.id,
2061                     'base_code_id': tax.base_code_id.id,
2062                     'ref_base_code_id': tax.ref_base_code_id.id,
2063                     'sequence': tax.sequence,
2064                     'base_sign': tax.base_sign,
2065                     'tax_sign': tax.tax_sign,
2066                     'ref_base_sign': tax.ref_base_sign,
2067                     'ref_tax_sign': tax.ref_tax_sign,
2068                     'price_unit': cur_price_unit,
2069                     'tax_code_id': tax.tax_code_id.id,
2070                     'ref_tax_code_id': tax.ref_tax_code_id.id,
2071             }
2072             res.append(data)
2073             if tax.type=='percent':
2074                 amount = cur_price_unit * tax.amount
2075                 data['amount'] = amount
2076
2077             elif tax.type=='fixed':
2078                 data['amount'] = tax.amount
2079                 data['tax_amount']=quantity
2080                # data['amount'] = quantity
2081             elif tax.type=='code':
2082                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
2083                 exec tax.python_compute in localdict
2084                 amount = localdict['result']
2085                 data['amount'] = amount
2086             elif tax.type=='balance':
2087                 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2088                 data['balance'] = cur_price_unit
2089
2090             amount2 = data.get('amount', 0.0)
2091             if tax.child_ids:
2092                 if tax.child_depend:
2093                     latest = res.pop()
2094                 amount = amount2
2095                 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, product, partner, quantity)
2096                 res.extend(child_tax)
2097                 if tax.child_depend:
2098                     for r in res:
2099                         for name in ('base','ref_base'):
2100                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2101                                 r[name+'_code_id'] = latest[name+'_code_id']
2102                                 r[name+'_sign'] = latest[name+'_sign']
2103                                 r['price_unit'] = latest['price_unit']
2104                                 latest[name+'_code_id'] = False
2105                         for name in ('tax','ref_tax'):
2106                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2107                                 r[name+'_code_id'] = latest[name+'_code_id']
2108                                 r[name+'_sign'] = latest[name+'_sign']
2109                                 r['amount'] = data['amount']
2110                                 latest[name+'_code_id'] = False
2111             if tax.include_base_amount:
2112                 cur_price_unit+=amount2
2113         return res
2114
2115     def compute_all(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, force_excluded=False):
2116         """
2117         :param force_excluded: boolean used to say that we don't want to consider the value of field price_include of
2118             tax. It's used in encoding by line where you don't matter if you encoded a tax with that boolean to True or
2119             False
2120         RETURN: {
2121                 'total': 0.0,                # Total without taxes
2122                 'total_included: 0.0,        # Total with taxes
2123                 'taxes': []                  # List of taxes, see compute for the format
2124             }
2125         """
2126
2127         # By default, for each tax, tax amount will first be computed
2128         # and rounded at the 'Account' decimal precision for each
2129         # PO/SO/invoice line and then these rounded amounts will be
2130         # summed, leading to the total amount for that tax. But, if the
2131         # company has tax_calculation_rounding_method = round_globally,
2132         # we still follow the same method, but we use a much larger
2133         # precision when we round the tax amount for each line (we use
2134         # the 'Account' decimal precision + 5), and that way it's like
2135         # rounding after the sum of the tax amounts of each line
2136         precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2137         tax_compute_precision = precision
2138         if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally':
2139             tax_compute_precision += 5
2140         totalin = totalex = float_round(price_unit * quantity, precision)
2141         tin = []
2142         tex = []
2143         for tax in taxes:
2144             if not tax.price_include or force_excluded:
2145                 tex.append(tax)
2146             else:
2147                 tin.append(tax)
2148         tin = self.compute_inv(cr, uid, tin, price_unit, quantity, product=product, partner=partner, precision=tax_compute_precision)
2149         for r in tin:
2150             totalex -= r.get('amount', 0.0)
2151         totlex_qty = 0.0
2152         try:
2153             totlex_qty = totalex/quantity
2154         except:
2155             pass
2156         tex = self._compute(cr, uid, tex, totlex_qty, quantity, product=product, partner=partner, precision=tax_compute_precision)
2157         for r in tex:
2158             totalin += r.get('amount', 0.0)
2159         return {
2160             'total': totalex,
2161             'total_included': totalin,
2162             'taxes': tin + tex
2163         }
2164
2165     def compute(self, cr, uid, taxes, price_unit, quantity,  product=None, partner=None):
2166         _logger.warning("Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included.")
2167         return self._compute(cr, uid, taxes, price_unit, quantity, product, partner)
2168
2169     def _compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
2170         """
2171         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2172
2173         RETURN:
2174             [ tax ]
2175             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2176             one tax for each tax id in IDS and their children
2177         """
2178         if not precision:
2179             precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2180         res = self._unit_compute(cr, uid, taxes, price_unit, product, partner, quantity)
2181         total = 0.0
2182         for r in res:
2183             if r.get('balance',False):
2184                 r['amount'] = round(r.get('balance', 0.0) * quantity, precision) - total
2185             else:
2186                 r['amount'] = round(r.get('amount', 0.0) * quantity, precision)
2187                 total += r['amount']
2188         return res
2189
2190     def _unit_compute_inv(self, cr, uid, taxes, price_unit, product=None, partner=None):
2191         taxes = self._applicable(cr, uid, taxes, price_unit,  product, partner)
2192         res = []
2193         taxes.reverse()
2194         cur_price_unit = price_unit
2195
2196         tax_parent_tot = 0.0
2197         for tax in taxes:
2198             if (tax.type=='percent') and not tax.include_base_amount:
2199                 tax_parent_tot += tax.amount
2200
2201         for tax in taxes:
2202             if (tax.type=='fixed') and not tax.include_base_amount:
2203                 cur_price_unit -= tax.amount
2204
2205         for tax in taxes:
2206             if tax.type=='percent':
2207                 if tax.include_base_amount:
2208                     amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
2209                 else:
2210                     amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
2211
2212             elif tax.type=='fixed':
2213                 amount = tax.amount
2214
2215             elif tax.type=='code':
2216                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
2217                 exec tax.python_compute_inv in localdict
2218                 amount = localdict['result']
2219             elif tax.type=='balance':
2220                 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2221
2222             if tax.include_base_amount:
2223                 cur_price_unit -= amount
2224                 todo = 0
2225             else:
2226                 todo = 1
2227             res.append({
2228                 'id': tax.id,
2229                 'todo': todo,
2230                 'name': tax.name,
2231                 'amount': amount,
2232                 'account_collected_id': tax.account_collected_id.id,
2233                 'account_paid_id': tax.account_paid_id.id,
2234                 'account_analytic_collected_id': tax.account_analytic_collected_id.id,
2235                 'account_analytic_paid_id': tax.account_analytic_paid_id.id,
2236                 'base_code_id': tax.base_code_id.id,
2237                 'ref_base_code_id': tax.ref_base_code_id.id,
2238                 'sequence': tax.sequence,
2239                 'base_sign': tax.base_sign,
2240                 'tax_sign': tax.tax_sign,
2241                 'ref_base_sign': tax.ref_base_sign,
2242                 'ref_tax_sign': tax.ref_tax_sign,
2243                 'price_unit': cur_price_unit,
2244                 'tax_code_id': tax.tax_code_id.id,
2245                 'ref_tax_code_id': tax.ref_tax_code_id.id,
2246             })
2247             if tax.child_ids:
2248                 if tax.child_depend:
2249                     del res[-1]
2250                     amount = price_unit
2251
2252             parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, product, partner)
2253             res.extend(parent_tax)
2254
2255         total = 0.0
2256         for r in res:
2257             if r['todo']:
2258                 total += r['amount']
2259         for r in res:
2260             r['price_unit'] -= total
2261             r['todo'] = 0
2262         return res
2263
2264     def compute_inv(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
2265         """
2266         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2267         Price Unit is a Tax included price
2268
2269         RETURN:
2270             [ tax ]
2271             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2272             one tax for each tax id in IDS and their children
2273         """
2274         if not precision:
2275             precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2276         res = self._unit_compute_inv(cr, uid, taxes, price_unit, product, partner=None)
2277         total = 0.0
2278         for r in res:
2279             if r.get('balance',False):
2280                 r['amount'] = round(r['balance'] * quantity, precision) - total
2281             else:
2282                 r['amount'] = round(r['amount'] * quantity, precision)
2283                 total += r['amount']
2284         return res
2285
2286 account_tax()
2287
2288 # ---------------------------------------------------------
2289 # Account Entries Models
2290 # ---------------------------------------------------------
2291
2292 class account_model(osv.osv):
2293     _name = "account.model"
2294     _description = "Account Model"
2295     _columns = {
2296         'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2297         'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2298         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2299         'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2300         'legend': fields.text('Legend', readonly=True, size=100),
2301     }
2302
2303     _defaults = {
2304         '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'),
2305     }
2306
2307     def generate(self, cr, uid, ids, data=None, context=None):
2308         if data is None:
2309             data = {}
2310         move_ids = []
2311         entry = {}
2312         account_move_obj = self.pool.get('account.move')
2313         account_move_line_obj = self.pool.get('account.move.line')
2314         pt_obj = self.pool.get('account.payment.term')
2315         period_obj = self.pool.get('account.period')
2316
2317         if context is None:
2318             context = {}
2319
2320         if data.get('date', False):
2321             context.update({'date': data['date']})
2322
2323         move_date = context.get('date', time.strftime('%Y-%m-%d'))
2324         move_date = datetime.strptime(move_date,"%Y-%m-%d")
2325         for model in self.browse(cr, uid, ids, context=context):
2326             ctx = context.copy()
2327             ctx.update({'company_id': model.company_id.id, 'account_period_prefer_normal': True})
2328             period_ids = period_obj.find(cr, uid, dt=context.get('date', False), context=ctx)
2329             period_id = period_ids and period_ids[0] or False
2330             ctx.update({'journal_id': model.journal_id.id,'period_id': period_id})
2331             try:
2332                 entry['name'] = model.name%{'year': move_date.strftime('%Y'), 'month': move_date.strftime('%m'), 'date': move_date.strftime('%Y-%m')}
2333             except:
2334                 raise osv.except_osv(_('Wrong Model!'), _('You have a wrong expression "%(...)s" in your model!'))
2335             move_id = account_move_obj.create(cr, uid, {
2336                 'ref': entry['name'],
2337                 'period_id': period_id,
2338                 'journal_id': model.journal_id.id,
2339                 'date': context.get('date', fields.date.context_today(self,cr,uid,context=context))
2340             })
2341             move_ids.append(move_id)
2342             for line in model.lines_id:
2343                 analytic_account_id = False
2344                 if line.analytic_account_id:
2345                     if not model.journal_id.analytic_journal_id:
2346                         raise osv.except_osv(_('No Analytic Journal!'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2347                     analytic_account_id = line.analytic_account_id.id
2348                 val = {
2349                     'move_id': move_id,
2350                     'journal_id': model.journal_id.id,
2351                     'period_id': period_id,
2352                     'analytic_account_id': analytic_account_id
2353                 }
2354
2355                 date_maturity = context.get('date',time.strftime('%Y-%m-%d'))
2356                 if line.date_maturity == 'partner':
2357                     if not line.partner_id:
2358                         raise osv.except_osv(_('Error!'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2359                                                                 "\nPlease define partner on it!")%(line.name, model.name))
2360
2361                     payment_term_id = False
2362                     if model.journal_id.type in ('purchase', 'purchase_refund') and line.partner_id.property_supplier_payment_term:
2363                         payment_term_id = line.partner_id.property_supplier_payment_term.id
2364                     elif line.partner_id.property_payment_term:
2365                         payment_term_id = line.partner_id.property_payment_term.id
2366                     if payment_term_id:
2367                         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2368                         if pterm_list:
2369                             pterm_list = [l[0] for l in pterm_list]
2370                             pterm_list.sort()
2371                             date_maturity = pterm_list[-1]
2372
2373                 val.update({
2374                     'name': line.name,
2375                     'quantity': line.quantity,
2376                     'debit': line.debit,
2377                     'credit': line.credit,
2378                     'account_id': line.account_id.id,
2379                     'move_id': move_id,
2380                     'partner_id': line.partner_id.id,
2381                     'date': context.get('date', fields.date.context_today(self,cr,uid,context=context)),
2382                     'date_maturity': date_maturity
2383                 })
2384                 account_move_line_obj.create(cr, uid, val, context=ctx)
2385
2386         return move_ids
2387
2388     def onchange_journal_id(self, cr, uid, ids, journal_id, context=None):
2389         company_id = False
2390
2391         if journal_id:
2392             journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context)
2393             if journal.company_id.id:
2394                 company_id = journal.company_id.id
2395
2396         return {'value': {'company_id': company_id}}
2397
2398 account_model()
2399
2400 class account_model_line(osv.osv):
2401     _name = "account.model.line"
2402     _description = "Account Model Entries"
2403     _columns = {
2404         'name': fields.char('Name', size=64, required=True),
2405         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones."),
2406         'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries."),
2407         'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2408         'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2409         'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2410         'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2411         'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2412         'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2413         'currency_id': fields.many2one('res.currency', 'Currency'),
2414         'partner_id': fields.many2one('res.partner', 'Partner'),
2415         '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."),
2416     }
2417     _order = 'sequence'
2418     _sql_constraints = [
2419         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in model, they must be positive!'),
2420         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model, they must be positive!'),
2421     ]
2422 account_model_line()
2423
2424 # ---------------------------------------------------------
2425 # Account Subscription
2426 # ---------------------------------------------------------
2427
2428
2429 class account_subscription(osv.osv):
2430     _name = "account.subscription"
2431     _description = "Account Subscription"
2432     _columns = {
2433         'name': fields.char('Name', size=64, required=True),
2434         'ref': fields.char('Reference', size=16),
2435         'model_id': fields.many2one('account.model', 'Model', required=True),
2436         'date_start': fields.date('Start Date', required=True),
2437         'period_total': fields.integer('Number of Periods', required=True),
2438         'period_nbr': fields.integer('Period', required=True),
2439         'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2440         'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', required=True, readonly=True),
2441         'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2442     }
2443     _defaults = {
2444         'date_start': fields.date.context_today,
2445         'period_type': 'month',
2446         'period_total': 12,
2447         'period_nbr': 1,
2448         'state': 'draft',
2449     }
2450     def state_draft(self, cr, uid, ids, context=None):
2451         self.write(cr, uid, ids, {'state':'draft'})
2452         return False
2453
2454     def check(self, cr, uid, ids, context=None):
2455         todone = []
2456         for sub in self.browse(cr, uid, ids, context=context):
2457             ok = True
2458             for line in sub.lines_id:
2459                 if not line.move_id.id:
2460                     ok = False
2461                     break
2462             if ok:
2463                 todone.append(sub.id)
2464         if todone:
2465             self.write(cr, uid, todone, {'state':'done'})
2466         return False
2467
2468     def remove_line(self, cr, uid, ids, context=None):
2469         toremove = []
2470         for sub in self.browse(cr, uid, ids, context=context):
2471             for line in sub.lines_id:
2472                 if not line.move_id.id:
2473                     toremove.append(line.id)
2474         if toremove:
2475             self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2476         self.write(cr, uid, ids, {'state':'draft'})
2477         return False
2478
2479     def compute(self, cr, uid, ids, context=None):
2480         for sub in self.browse(cr, uid, ids, context=context):
2481             ds = sub.date_start
2482             for i in range(sub.period_total):
2483                 self.pool.get('account.subscription.line').create(cr, uid, {
2484                     'date': ds,
2485                     'subscription_id': sub.id,
2486                 })
2487                 if sub.period_type=='day':
2488                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2489                 if sub.period_type=='month':
2490                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2491                 if sub.period_type=='year':
2492                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2493         self.write(cr, uid, ids, {'state':'running'})
2494         return True
2495
2496 account_subscription()
2497
2498 class account_subscription_line(osv.osv):
2499     _name = "account.subscription.line"
2500     _description = "Account Subscription Line"
2501     _columns = {
2502         'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2503         'date': fields.date('Date', required=True),
2504         'move_id': fields.many2one('account.move', 'Entry'),
2505     }
2506
2507     def move_create(self, cr, uid, ids, context=None):
2508         tocheck = {}
2509         all_moves = []
2510         obj_model = self.pool.get('account.model')
2511         for line in self.browse(cr, uid, ids, context=context):
2512             data = {
2513                 'date': line.date,
2514             }
2515             move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], data, context)
2516             tocheck[line.subscription_id.id] = True
2517             self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2518             all_moves.extend(move_ids)
2519         if tocheck:
2520             self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2521         return all_moves
2522
2523     _rec_name = 'date'
2524
2525 account_subscription_line()
2526
2527 #  ---------------------------------------------------------------
2528 #   Account Templates: Account, Tax, Tax Code and chart. + Wizard
2529 #  ---------------------------------------------------------------
2530
2531 class account_tax_template(osv.osv):
2532     _name = 'account.tax.template'
2533 account_tax_template()
2534
2535 class account_account_template(osv.osv):
2536     _order = "code"
2537     _name = "account.account.template"
2538     _description ='Templates for Accounts'
2539
2540     _columns = {
2541         'name': fields.char('Name', size=256, required=True, select=True),
2542         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2543         'code': fields.char('Code', size=64, required=True, select=1),
2544         'type': fields.selection([
2545             ('receivable','Receivable'),
2546             ('payable','Payable'),
2547             ('view','View'),
2548             ('consolidation','Consolidation'),
2549             ('liquidity','Liquidity'),
2550             ('other','Regular'),
2551             ('closed','Closed'),
2552             ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2553             "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2554             "can have children accounts for multi-company consolidations, payable/receivable are for "\
2555             "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2556         'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2557             help="These types are defined according to your country. The type contains more information "\
2558             "about the account and its specificities."),
2559         'financial_report_ids': fields.many2many('account.financial.report', 'account_template_financial_report', 'account_template_id', 'report_line_id', 'Financial Reports'),
2560         'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2561         'shortcut': fields.char('Shortcut', size=12),
2562         'note': fields.text('Note'),
2563         'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade', domain=[('type','=','view')]),
2564         'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2565         'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2566         'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2567         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', help="This optional field allow you to link an account template to a specific chart template that may differ from the one its root parent belongs to. This allow you to define chart templates that extend another and complete it with few new accounts (You don't need to define the whole structure that is common to both several times)."),
2568     }
2569
2570     _defaults = {
2571         'reconcile': False,
2572         'type': 'view',
2573         'nocreate': False,
2574     }
2575
2576     _check_recursion = check_cycle
2577     _constraints = [
2578         (_check_recursion, 'Error!\nYou cannot create recursive account templates.', ['parent_id']),
2579     ]
2580
2581     def name_get(self, cr, uid, ids, context=None):
2582         if not ids:
2583             return []
2584         reads = self.read(cr, uid, ids, ['name','code'], context=context)
2585         res = []
2586         for record in reads:
2587             name = record['name']
2588             if record['code']:
2589                 name = record['code']+' '+name
2590             res.append((record['id'],name ))
2591         return res
2592
2593     def generate_account(self, cr, uid, chart_template_id, tax_template_ref, acc_template_ref, code_digits, company_id, context=None):
2594         """
2595         This method for generating accounts from templates.
2596
2597         :param chart_template_id: id of the chart template chosen in the wizard
2598         :param tax_template_ref: Taxes templates reference for write taxes_id in account_account.
2599         :paramacc_template_ref: dictionary with the mappping between the account templates and the real accounts.
2600         :param code_digits: number of digits got from wizard.multi.charts.accounts, this is use for account code.
2601         :param company_id: company_id selected from wizard.multi.charts.accounts.
2602         :returns: return acc_template_ref for reference purpose.
2603         :rtype: dict
2604         """
2605         if context is None:
2606             context = {}
2607         obj_acc = self.pool.get('account.account')
2608         company_name = self.pool.get('res.company').browse(cr, uid, company_id, context=context).name
2609         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
2610         #deactivate the parent_store functionnality on account_account for rapidity purpose
2611         ctx = context.copy()
2612         ctx.update({'defer_parent_store_computation': True})
2613         level_ref = {}
2614         children_acc_criteria = [('chart_template_id','=', chart_template_id)]
2615         if template.account_root_id.id:
2616             children_acc_criteria = ['|'] + children_acc_criteria + ['&',('parent_id','child_of', [template.account_root_id.id]),('chart_template_id','=', False)]
2617         children_acc_template = self.search(cr, uid, [('nocreate','!=',True)] + children_acc_criteria, order='id')
2618         for account_template in self.browse(cr, uid, children_acc_template, context=context):
2619             # skip the root of COA if it's not the main one
2620             if (template.account_root_id.id == account_template.id) and template.parent_id:
2621                 continue
2622             tax_ids = []
2623             for tax in account_template.tax_ids:
2624                 tax_ids.append(tax_template_ref[tax.id])
2625
2626             code_main = account_template.code and len(account_template.code) or 0
2627             code_acc = account_template.code or ''
2628             if code_main > 0 and code_main <= code_digits and account_template.type != 'view':
2629                 code_acc = str(code_acc) + (str('0'*(code_digits-code_main)))
2630             parent_id = account_template.parent_id and ((account_template.parent_id.id in acc_template_ref) and acc_template_ref[account_template.parent_id.id]) or False
2631             #the level as to be given as well at the creation time, because of the defer_parent_store_computation in
2632             #context. Indeed because of this, the parent_left and parent_right are not computed and thus the child_of
2633             #operator does not return the expected values, with result of having the level field not computed at all.
2634             if parent_id:
2635                 level = parent_id in level_ref and level_ref[parent_id] + 1 or obj_acc._get_level(cr, uid, [parent_id], 'level', None, context=context)[parent_id] + 1
2636             else:
2637                 level = 0
2638             vals={
2639                 'name': (template.account_root_id.id == account_template.id) and company_name or account_template.name,
2640                 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2641                 'code': code_acc,
2642                 'type': account_template.type,
2643                 'user_type': account_template.user_type and account_template.user_type.id or False,
2644                 'reconcile': account_template.reconcile,
2645                 'shortcut': account_template.shortcut,
2646                 'note': account_template.note,
2647                 'financial_report_ids': account_template.financial_report_ids and [(6,0,[x.id for x in account_template.financial_report_ids])] or False,
2648                 'parent_id': parent_id,
2649                 'tax_ids': [(6,0,tax_ids)],
2650                 'company_id': company_id,
2651                 'level': level,
2652             }
2653             new_account = obj_acc.create(cr, uid, vals, context=ctx)
2654             acc_template_ref[account_template.id] = new_account
2655             level_ref[new_account] = level
2656
2657         #reactivate the parent_store functionnality on account_account
2658         obj_acc._parent_store_compute(cr)
2659         return acc_template_ref
2660
2661 account_account_template()
2662
2663 class account_add_tmpl_wizard(osv.osv_memory):
2664     """Add one more account from the template.
2665
2666     With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2667     _name = 'account.addtmpl.wizard'
2668
2669     def _get_def_cparent(self, cr, uid, context=None):
2670         acc_obj = self.pool.get('account.account')
2671         tmpl_obj = self.pool.get('account.account.template')
2672         tids = tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2673         if not tids or not tids[0]['parent_id']:
2674             return False
2675         ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2676         res = None
2677         if not ptids or not ptids[0]['code']:
2678             raise osv.except_osv(_('Error!'), _('There is no parent code for the template account.'))
2679             res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2680         return res and res[0] or False
2681
2682     _columns = {
2683         'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2684     }
2685     _defaults = {
2686         'cparent_id': _get_def_cparent,
2687     }
2688
2689     def action_create(self,cr,uid,ids,context=None):
2690         if context is None:
2691             context = {}
2692         acc_obj = self.pool.get('account.account')
2693         tmpl_obj = self.pool.get('account.account.template')
2694         data = self.read(cr, uid, ids)[0]
2695         company_id = acc_obj.read(cr, uid, [data['cparent_id'][0]], ['company_id'])[0]['company_id'][0]
2696         account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2697         vals = {
2698             'name': account_template.name,
2699             'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2700             'code': account_template.code,
2701             'type': account_template.type,
2702             'user_type': account_template.user_type and account_template.user_type.id or False,
2703             'reconcile': account_template.reconcile,
2704             'shortcut': account_template.shortcut,
2705             'note': account_template.note,
2706             'parent_id': data['cparent_id'][0],
2707             'company_id': company_id,
2708             }
2709         acc_obj.create(cr, uid, vals)
2710         return {'type':'state', 'state': 'end' }
2711
2712     def action_cancel(self, cr, uid, ids, context=None):
2713         return { 'type': 'state', 'state': 'end' }
2714
2715 account_add_tmpl_wizard()
2716
2717 class account_tax_code_template(osv.osv):
2718
2719     _name = 'account.tax.code.template'
2720     _description = 'Tax Code Template'
2721     _order = 'code'
2722     _rec_name = 'code'
2723     _columns = {
2724         'name': fields.char('Tax Case Name', size=64, required=True),
2725         'code': fields.char('Case Code', size=64),
2726         'info': fields.text('Description'),
2727         'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2728         'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2729         'sign': fields.float('Sign For Parent', required=True),
2730         '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."),
2731     }
2732
2733     _defaults = {
2734         'sign': 1.0,
2735         'notprintable': False,
2736     }
2737
2738     def generate_tax_code(self, cr, uid, tax_code_root_id, company_id, context=None):
2739         '''
2740         This function generates the tax codes from the templates of tax code that are children of the given one passed
2741         in argument. Then it returns a dictionary with the mappping between the templates and the real objects.
2742
2743         :param tax_code_root_id: id of the root of all the tax code templates to process
2744         :param company_id: id of the company the wizard is running for
2745         :returns: dictionary with the mappping between the templates and the real objects.
2746         :rtype: dict
2747         '''
2748         obj_tax_code_template = self.pool.get('account.tax.code.template')
2749         obj_tax_code = self.pool.get('account.tax.code')
2750         tax_code_template_ref = {}
2751         company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
2752
2753         #find all the children of the tax_code_root_id
2754         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 []
2755         for tax_code_template in obj_tax_code_template.browse(cr, uid, children_tax_code_template, context=context):
2756             vals = {
2757                 'name': (tax_code_root_id == tax_code_template.id) and company.name or tax_code_template.name,
2758                 'code': tax_code_template.code,
2759                 'info': tax_code_template.info,
2760                 '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,
2761                 'company_id': company_id,
2762                 'sign': tax_code_template.sign,
2763             }
2764             #check if this tax code already exists
2765             rec_list = obj_tax_code.search(cr, uid, [('name', '=', vals['name']),('code', '=', vals['code']),('company_id', '=', vals['company_id'])], context=context)
2766             if not rec_list:
2767                 #if not yet, create it
2768                 new_tax_code = obj_tax_code.create(cr, uid, vals)
2769                 #recording the new tax code to do the mapping
2770                 tax_code_template_ref[tax_code_template.id] = new_tax_code
2771         return tax_code_template_ref
2772
2773     def name_get(self, cr, uid, ids, context=None):
2774         if not ids:
2775             return []
2776         if isinstance(ids, (int, long)):
2777             ids = [ids]
2778         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2779         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2780                 for x in reads]
2781
2782     _check_recursion = check_cycle
2783     _constraints = [
2784         (_check_recursion, 'Error!\nYou cannot create recursive Tax Codes.', ['parent_id'])
2785     ]
2786     _order = 'code,name'
2787 account_tax_code_template()
2788
2789
2790 class account_chart_template(osv.osv):
2791     _name="account.chart.template"
2792     _description= "Templates for Account Chart"
2793
2794     _columns={
2795         'name': fields.char('Name', size=64, required=True),
2796         'parent_id': fields.many2one('account.chart.template', 'Parent Chart Template'),
2797         'code_digits': fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2798         '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."),
2799         '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'),
2800         'account_root_id': fields.many2one('account.account.template', 'Root Account', domain=[('parent_id','=',False)]),
2801         'tax_code_root_id': fields.many2one('account.tax.code.template', 'Root Tax Code', domain=[('parent_id','=',False)]),
2802         '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'),
2803         'bank_account_view_id': fields.many2one('account.account.template', 'Bank Account'),
2804         'property_account_receivable': fields.many2one('account.account.template', 'Receivable Account'),
2805         'property_account_payable': fields.many2one('account.account.template', 'Payable Account'),
2806         'property_account_expense_categ': fields.many2one('account.account.template', 'Expense Category Account'),
2807         'property_account_income_categ': fields.many2one('account.account.template', 'Income Category Account'),
2808         'property_account_expense': fields.many2one('account.account.template', 'Expense Account on Product Template'),
2809         'property_account_income': fields.many2one('account.account.template', 'Income Account on Product Template'),
2810         'property_account_income_opening': fields.many2one('account.account.template', 'Opening Entries Income Account'),
2811         'property_account_expense_opening': fields.many2one('account.account.template', 'Opening Entries Expense Account'),
2812     }
2813
2814     _defaults = {
2815         'visible': True,
2816         'code_digits': 6,
2817         'complete_tax_set': True,
2818     }
2819
2820 account_chart_template()
2821
2822 class account_tax_template(osv.osv):
2823
2824     _name = 'account.tax.template'
2825     _description = 'Templates for Taxes'
2826
2827     _columns = {
2828         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2829         'name': fields.char('Tax Name', size=64, required=True),
2830         '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."),
2831         'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For Tax Type percent enter % ratio between 0-1."),
2832         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True),
2833         '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."),
2834         '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."),
2835         'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2836         'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2837         'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2838         '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."),
2839         'python_compute':fields.text('Python Code'),
2840         'python_compute_inv':fields.text('Python Code (reverse)'),
2841         'python_applicable':fields.text('Python Code'),
2842
2843         #
2844         # Fields used for the Tax declaration
2845         #
2846         'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the tax declaration."),
2847         'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the tax declaration."),
2848         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2849         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2850
2851         # Same fields for refund invoices
2852
2853         'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the tax declaration."),
2854         'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the tax declaration."),
2855         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2856         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2857         '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."),
2858         'description': fields.char('Internal Name'),
2859         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,),
2860         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
2861     }
2862
2863     def name_get(self, cr, uid, ids, context=None):
2864         if not ids:
2865             return []
2866         res = []
2867         for record in self.read(cr, uid, ids, ['description','name'], context=context):
2868             name = record['description'] and record['description'] or record['name']
2869             res.append((record['id'],name ))
2870         return res
2871
2872     def _default_company(self, cr, uid, context=None):
2873         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2874         if user.company_id:
2875             return user.company_id.id
2876         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2877
2878     _defaults = {
2879         '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''',
2880         'python_compute_inv': lambda *a: '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
2881         'applicable_type': 'true',
2882         'type': 'percent',
2883         'amount': 0,
2884         'sequence': 1,
2885         'ref_tax_sign': 1,
2886         'ref_base_sign': 1,
2887         'tax_sign': 1,
2888         'base_sign': 1,
2889         'include_base_amount': False,
2890         'type_tax_use': 'all',
2891         'price_include': 0,
2892     }
2893     _order = 'sequence'
2894
2895     def _generate_tax(self, cr, uid, tax_templates, tax_code_template_ref, company_id, context=None):
2896         """
2897         This method generate taxes from templates.
2898
2899         :param tax_templates: list of browse record of the tax templates to process
2900         :param tax_code_template_ref: Taxcode templates reference.
2901         :param company_id: id of the company the wizard is running for
2902         :returns:
2903             {
2904             'tax_template_to_tax': mapping between tax template and the newly generated taxes corresponding,
2905             'account_dict': dictionary containing a to-do list with all the accounts to assign on new taxes
2906             }
2907         """
2908         if context is None:
2909             context = {}
2910         res = {}
2911         todo_dict = {}
2912         tax_template_to_tax = {}
2913         for tax in tax_templates:
2914             vals_tax = {
2915                 'name':tax.name,
2916                 'sequence': tax.sequence,
2917                 'amount': tax.amount,
2918                 'type': tax.type,
2919                 'applicable_type': tax.applicable_type,
2920                 'domain': tax.domain,
2921                 '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,
2922                 'child_depend': tax.child_depend,
2923                 'python_compute': tax.python_compute,
2924                 'python_compute_inv': tax.python_compute_inv,
2925                 'python_applicable': tax.python_applicable,
2926                 '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,
2927                 '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,
2928                 'base_sign': tax.base_sign,
2929                 'tax_sign': tax.tax_sign,
2930                 '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,
2931                 '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,
2932                 'ref_base_sign': tax.ref_base_sign,
2933                 'ref_tax_sign': tax.ref_tax_sign,
2934                 'include_base_amount': tax.include_base_amount,
2935                 'description': tax.description,
2936                 'company_id': company_id,
2937                 'type_tax_use': tax.type_tax_use,
2938                 'price_include': tax.price_include
2939             }
2940             new_tax = self.pool.get('account.tax').create(cr, uid, vals_tax)
2941             tax_template_to_tax[tax.id] = new_tax
2942             #as the accounts have not been created yet, we have to wait before filling these fields
2943             todo_dict[new_tax] = {
2944                 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2945                 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2946             }
2947         res.update({'tax_template_to_tax': tax_template_to_tax, 'account_dict': todo_dict})
2948         return res
2949
2950 account_tax_template()
2951
2952 # Fiscal Position Templates
2953
2954 class account_fiscal_position_template(osv.osv):
2955     _name = 'account.fiscal.position.template'
2956     _description = 'Template for Fiscal Position'
2957
2958     _columns = {
2959         'name': fields.char('Fiscal Position Template', size=64, required=True),
2960         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2961         'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2962         'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping'),
2963         'note': fields.text('Notes', translate=True),
2964     }
2965
2966     def generate_fiscal_position(self, cr, uid, chart_temp_id, tax_template_ref, acc_template_ref, company_id, context=None):
2967         """
2968         This method generate Fiscal Position, Fiscal Position Accounts and Fiscal Position Taxes from templates.
2969
2970         :param chart_temp_id: Chart Template Id.
2971         :param taxes_ids: Taxes templates reference for generating account.fiscal.position.tax.
2972         :param acc_template_ref: Account templates reference for generating account.fiscal.position.account.
2973         :param company_id: company_id selected from wizard.multi.charts.accounts.
2974         :returns: True
2975         """
2976         if context is None:
2977             context = {}
2978         obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2979         obj_ac_fp = self.pool.get('account.fiscal.position.account')
2980         obj_fiscal_position = self.pool.get('account.fiscal.position')
2981         fp_ids = self.search(cr, uid, [('chart_template_id', '=', chart_temp_id)])
2982         for position in self.browse(cr, uid, fp_ids, context=context):
2983             new_fp = obj_fiscal_position.create(cr, uid, {'company_id': company_id, 'name': position.name, 'note': position.note})
2984             for tax in position.tax_ids:
2985                 obj_tax_fp.create(cr, uid, {
2986                     'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2987                     'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2988                     'position_id': new_fp
2989                 })
2990             for acc in position.account_ids:
2991                 obj_ac_fp.create(cr, uid, {
2992                     'account_src_id': acc_template_ref[acc.account_src_id.id],
2993                     'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2994                     'position_id': new_fp
2995                 })
2996         return True
2997
2998 account_fiscal_position_template()
2999
3000 class account_fiscal_position_tax_template(osv.osv):
3001     _name = 'account.fiscal.position.tax.template'
3002     _description = 'Template Tax Fiscal Position'
3003     _rec_name = 'position_id'
3004
3005     _columns = {
3006         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
3007         'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
3008         'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
3009     }
3010
3011 account_fiscal_position_tax_template()
3012
3013 class account_fiscal_position_account_template(osv.osv):
3014     _name = 'account.fiscal.position.account.template'
3015     _description = 'Template Account Fiscal Mapping'
3016     _rec_name = 'position_id'
3017     _columns = {
3018         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
3019         'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
3020         'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
3021     }
3022
3023 account_fiscal_position_account_template()
3024
3025 # ---------------------------------------------------------
3026 # Account generation from template wizards
3027 # ---------------------------------------------------------
3028
3029 class wizard_multi_charts_accounts(osv.osv_memory):
3030     """
3031     Create a new account chart for a company.
3032     Wizards ask for:
3033         * a company
3034         * an account chart template
3035         * a number of digits for formatting code of non-view accounts
3036         * a list of bank accounts owned by the company
3037     Then, the wizard:
3038         * generates all accounts from the template and assigns them to the right company
3039         * generates all taxes and tax codes, changing account assignations
3040         * generates all accounting properties and assigns them correctly
3041     """
3042     _name='wizard.multi.charts.accounts'
3043     _inherit = 'res.config'
3044
3045     _columns = {
3046         'company_id':fields.many2one('res.company', 'Company', required=True),
3047         'currency_id': fields.many2one('res.currency', 'Currency', help="Currency as per company's country."),
3048         'only_one_chart_template': fields.boolean('Only One Chart Template Available'),
3049         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
3050         'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Cash and Banks', required=True),
3051         'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
3052         "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
3053         "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
3054         'sale_tax_rate': fields.float('Sales Tax(%)'),
3055         'purchase_tax_rate': fields.float('Purchase Tax(%)'),
3056         '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'),
3057     }
3058
3059     def onchange_company_id(self, cr, uid, ids, company_id, context=None):
3060         currency_id = False
3061         if company_id:
3062             currency_id = self.pool.get('res.company').browse(cr, uid, company_id, context=context).currency_id.id
3063         return {'value': {'currency_id': currency_id}}
3064
3065     def onchange_tax_rate(self, cr, uid, ids, rate=False, context=None):
3066         return {'value': {'purchase_tax_rate': rate or False}}
3067
3068     def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
3069         res = {}
3070         tax_templ_obj = self.pool.get('account.tax.template')
3071         res['value'] = {'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False}
3072         if chart_template_id:
3073             data = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3074             res['value'].update({'complete_tax_set': data.complete_tax_set})
3075             if data.complete_tax_set:
3076             # 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
3077                 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3078                                               , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence, id desc")
3079                 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3080                                               , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence, id desc")
3081                 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})
3082
3083             if data.code_digits:
3084                res['value'].update({'code_digits': data.code_digits})
3085         return res
3086
3087     def default_get(self, cr, uid, fields, context=None):
3088         res = super(wizard_multi_charts_accounts, self).default_get(cr, uid, fields, context=context)
3089         tax_templ_obj = self.pool.get('account.tax.template')
3090
3091         if 'bank_accounts_id' in fields:
3092             res.update({'bank_accounts_id': [{'acc_name': _('Cash'), 'account_type': 'cash'},{'acc_name': _('Bank'), 'account_type': 'bank'}]})
3093         if 'company_id' in fields:
3094             res.update({'company_id': self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0].company_id.id})
3095         if 'currency_id' in fields:
3096             company_id = res.get('company_id') or False
3097             if company_id:
3098                 company_obj = self.pool.get('res.company')
3099                 country_id = company_obj.browse(cr, uid, company_id, context=context).country_id.id
3100                 currency_id = company_obj.on_change_country(cr, uid, company_id, country_id, context=context)['value']['currency_id']
3101                 res.update({'currency_id': currency_id})
3102
3103         ids = self.pool.get('account.chart.template').search(cr, uid, [('visible', '=', True)], context=context)
3104         if ids:
3105             if 'chart_template_id' in fields:
3106                 res.update({'only_one_chart_template': len(ids) == 1, 'chart_template_id': ids[0]})
3107             if 'sale_tax' in fields:
3108                 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3109                                               , "=", ids[0]), ('type_tax_use', 'in', ('sale','all'))], order="sequence")
3110                 res.update({'sale_tax': sale_tax_ids and sale_tax_ids[0] or False})
3111             if 'purchase_tax' in fields:
3112                 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3113                                           , "=", ids[0]), ('type_tax_use', 'in', ('purchase','all'))], order="sequence")
3114                 res.update({'purchase_tax': purchase_tax_ids and purchase_tax_ids[0] or False})
3115         res.update({
3116             'purchase_tax_rate': 15.0,
3117             'sale_tax_rate': 15.0,
3118         })
3119         return res
3120
3121     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
3122         if context is None:context = {}
3123         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)
3124         cmp_select = []
3125         acc_template_obj = self.pool.get('account.chart.template')
3126         company_obj = self.pool.get('res.company')
3127
3128         company_ids = company_obj.search(cr, uid, [], context=context)
3129         #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)
3130         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",))
3131         configured_cmp = [r[0] for r in cr.fetchall()]
3132         unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
3133         for field in res['fields']:
3134             if field == 'company_id':
3135                 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
3136                 res['fields'][field]['selection'] = [('', '')]
3137                 if unconfigured_cmp:
3138                     cmp_select = [(line.id, line.name) for line in company_obj.browse(cr, uid, unconfigured_cmp)]
3139                     res['fields'][field]['selection'] = cmp_select
3140         return res
3141
3142     def check_created_journals(self, cr, uid, vals_journal, company_id, context=None):
3143         """
3144         This method used for checking journals already created or not. If not then create new journal.
3145         """
3146         obj_journal = self.pool.get('account.journal')
3147         rec_list = obj_journal.search(cr, uid, [('name','=', vals_journal['name']),('company_id', '=', company_id)], context=context)
3148         if not rec_list:
3149             obj_journal.create(cr, uid, vals_journal, context=context)
3150         return True
3151
3152     def generate_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3153         """
3154         This method is used for creating journals.
3155
3156         :param chart_temp_id: Chart Template Id.
3157         :param acc_template_ref: Account templates reference.
3158         :param company_id: company_id selected from wizard.multi.charts.accounts.
3159         :returns: True
3160         """
3161         journal_data = self._prepare_all_journals(cr, uid, chart_template_id, acc_template_ref, company_id, context=context)
3162         for vals_journal in journal_data:
3163             self.check_created_journals(cr, uid, vals_journal, company_id, context=context)
3164         return True
3165
3166     def _prepare_all_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3167         def _get_analytic_journal(journal_type):
3168             # Get the analytic journal
3169             data = False
3170             if journal_type in ('sale', 'sale_refund'):
3171                 data = obj_data.get_object_reference(cr, uid, 'account', 'analytic_journal_sale')
3172             elif journal_type in ('purchase', 'purchase_refund'):
3173                 pass
3174             elif journal_type == 'general':
3175                 pass
3176             return data and data[1] or False
3177
3178         def _get_default_account(journal_type, type='debit'):
3179             # Get the default accounts
3180             default_account = False
3181             if journal_type in ('sale', 'sale_refund'):
3182                 default_account = acc_template_ref.get(template.property_account_income_categ.id)
3183             elif journal_type in ('purchase', 'purchase_refund'):
3184                 default_account = acc_template_ref.get(template.property_account_expense_categ.id)
3185             elif journal_type == 'situation':
3186                 if type == 'debit':
3187                     default_account = acc_template_ref.get(template.property_account_expense_opening.id)
3188                 else:
3189                     default_account = acc_template_ref.get(template.property_account_income_opening.id)
3190             return default_account
3191
3192         journal_names = {
3193             'sale': _('Sales Journal'),
3194             'purchase': _('Purchase Journal'),
3195             'sale_refund': _('Sales Refund Journal'),
3196             'purchase_refund': _('Purchase Refund Journal'),
3197             'general': _('Miscellaneous Journal'),
3198             'situation': _('Opening Entries Journal'),
3199         }
3200         journal_codes = {
3201             'sale': _('SAJ'),
3202             'purchase': _('EXJ'),
3203             'sale_refund': _('SCNJ'),
3204             'purchase_refund': _('ECNJ'),
3205             'general': _('MISC'),
3206             'situation': _('OPEJ'),
3207         }
3208
3209         obj_data = self.pool.get('ir.model.data')
3210         analytic_journal_obj = self.pool.get('account.analytic.journal')
3211         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3212
3213         journal_data = []
3214         for journal_type in ['sale', 'purchase', 'sale_refund', 'purchase_refund', 'general', 'situation']:
3215             vals = {
3216                 'type': journal_type,
3217                 'name': journal_names[journal_type],
3218                 'code': journal_codes[journal_type],
3219                 'company_id': company_id,
3220                 'centralisation': journal_type == 'situation',
3221                 'analytic_journal_id': _get_analytic_journal(journal_type),
3222                 'default_credit_account_id': _get_default_account(journal_type, 'credit'),
3223                 'default_debit_account_id': _get_default_account(journal_type, 'debit'),
3224             }
3225             journal_data.append(vals)
3226         return journal_data
3227
3228     def generate_properties(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3229         """
3230         This method used for creating properties.
3231
3232         :param chart_template_id: id of the current chart template for which we need to create properties
3233         :param acc_template_ref: Mapping between ids of account templates and real accounts created from them
3234         :param company_id: company_id selected from wizard.multi.charts.accounts.
3235         :returns: True
3236         """
3237         property_obj = self.pool.get('ir.property')
3238         field_obj = self.pool.get('ir.model.fields')
3239         todo_list = [
3240             ('property_account_receivable','res.partner','account.account'),
3241             ('property_account_payable','res.partner','account.account'),
3242             ('property_account_expense_categ','product.category','account.account'),
3243             ('property_account_income_categ','product.category','account.account'),
3244             ('property_account_expense','product.template','account.account'),
3245             ('property_account_income','product.template','account.account'),
3246         ]
3247         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3248         for record in todo_list:
3249             account = getattr(template, record[0])
3250             value = account and 'account.account,' + str(acc_template_ref[account.id]) or False
3251             if value:
3252                 field = field_obj.search(cr, uid, [('name', '=', record[0]),('model', '=', record[1]),('relation', '=', record[2])], context=context)
3253                 vals = {
3254                     'name': record[0],
3255                     'company_id': company_id,
3256                     'fields_id': field[0],
3257                     'value': value,
3258                 }
3259                 property_ids = property_obj.search(cr, uid, [('name','=', record[0]),('company_id', '=', company_id)], context=context)
3260                 if property_ids:
3261                     #the property exist: modify it
3262                     property_obj.write(cr, uid, property_ids, vals, context=context)
3263                 else:
3264                     #create the property
3265                     property_obj.create(cr, uid, vals, context=context)
3266         return True
3267
3268     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):
3269         '''
3270         This function recursively loads the template objects and create the real objects from them.
3271
3272         :param template_id: id of the chart template to load
3273         :param company_id: id of the company the wizard is running for
3274         :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3275         :param obj_wizard: the current wizard for generating the COA from the templates
3276         :param acc_ref: Mapping between ids of account templates and real accounts created from them
3277         :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3278         :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3279         :returns: return a tuple with a dictionary containing
3280             * the mapping between the account template ids and the ids of the real accounts that have been generated
3281               from them, as first item,
3282             * a similar dictionary for mapping the tax templates and taxes, as second item,
3283             * a last identical containing the mapping of tax code templates and tax codes
3284         :rtype: tuple(dict, dict, dict)
3285         '''
3286         if acc_ref is None:
3287             acc_ref = {}
3288         if taxes_ref is None:
3289             taxes_ref = {}
3290         if tax_code_ref is None:
3291             tax_code_ref = {}
3292         template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3293         if template.parent_id:
3294             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)
3295             acc_ref.update(tmp1)
3296             taxes_ref.update(tmp2)
3297             tax_code_ref.update(tmp3)
3298         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)
3299         acc_ref.update(tmp1)
3300         taxes_ref.update(tmp2)
3301         tax_code_ref.update(tmp3)
3302         return acc_ref, taxes_ref, tax_code_ref
3303
3304     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):
3305         '''
3306         This function generates all the objects from the templates
3307
3308         :param template_id: id of the chart template to load
3309         :param company_id: id of the company the wizard is running for
3310         :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3311         :param obj_wizard: the current wizard for generating the COA from the templates
3312         :param acc_ref: Mapping between ids of account templates and real accounts created from them
3313         :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3314         :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3315         :returns: return a tuple with a dictionary containing
3316             * the mapping between the account template ids and the ids of the real accounts that have been generated
3317               from them, as first item,
3318             * a similar dictionary for mapping the tax templates and taxes, as second item,
3319             * a last identical containing the mapping of tax code templates and tax codes
3320         :rtype: tuple(dict, dict, dict)
3321         '''
3322         if account_ref is None:
3323             account_ref = {}
3324         if taxes_ref is None:
3325             taxes_ref = {}
3326         if tax_code_ref is None:
3327             tax_code_ref = {}
3328         template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3329         obj_tax_code_template = self.pool.get('account.tax.code.template')
3330         obj_acc_tax = self.pool.get('account.tax')
3331         obj_tax_temp = self.pool.get('account.tax.template')
3332         obj_acc_template = self.pool.get('account.account.template')
3333         obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
3334
3335         # create all the tax code.
3336         tax_code_ref.update(obj_tax_code_template.generate_tax_code(cr, uid, template.tax_code_root_id.id, company_id, context=context))
3337
3338         # Generate taxes from templates.
3339         tax_templates = [x for x in template.tax_template_ids]
3340         generated_tax_res = obj_tax_temp._generate_tax(cr, uid, tax_templates, tax_code_ref, company_id, context=context)
3341         taxes_ref.update(generated_tax_res['tax_template_to_tax'])
3342
3343         # Generating Accounts from templates.
3344         account_template_ref = obj_acc_template.generate_account(cr, uid, template_id, taxes_ref, account_ref, code_digits, company_id, context=context)
3345         account_ref.update(account_template_ref)
3346
3347         # writing account values on tax after creation of accounts
3348         for key,value in generated_tax_res['account_dict'].items():
3349             if value['account_collected_id'] or value['account_paid_id']:
3350                 obj_acc_tax.write(cr, uid, [key], {
3351                     'account_collected_id': account_ref.get(value['account_collected_id'], False),
3352                     'account_paid_id': account_ref.get(value['account_paid_id'], False),
3353                 })
3354
3355         # Create Journals
3356         self.generate_journals(cr, uid, template_id, account_ref, company_id, context=context)
3357
3358         # generate properties function
3359         self.generate_properties(cr, uid, template_id, account_ref, company_id, context=context)
3360
3361         # Generate Fiscal Position , Fiscal Position Accounts and Fiscal Position Taxes from templates
3362         obj_fiscal_position_template.generate_fiscal_position(cr, uid, template_id, taxes_ref, account_ref, company_id, context=context)
3363
3364         return account_ref, taxes_ref, tax_code_ref
3365
3366     def _create_tax_templates_from_rates(self, cr, uid, obj_wizard, company_id, context=None):
3367         '''
3368         This function checks if the chosen chart template is configured as containing a full set of taxes, and if
3369         it's not the case, it creates the templates for account.tax.code and for account.account.tax objects accordingly
3370         to the provided sale/purchase rates. Then it saves the new tax templates as default taxes to use for this chart
3371         template.
3372
3373         :param obj_wizard: browse record of wizard to generate COA from templates
3374         :param company_id: id of the company for wich the wizard is running
3375         :return: True
3376         '''
3377         obj_tax_code_template = self.pool.get('account.tax.code.template')
3378         obj_tax_temp = self.pool.get('account.tax.template')
3379         chart_template = obj_wizard.chart_template_id
3380         vals = {}
3381         # get the ids of all the parents of the selected account chart template
3382         current_chart_template = chart_template
3383         all_parents = [current_chart_template.id]
3384         while current_chart_template.parent_id:
3385             current_chart_template = current_chart_template.parent_id
3386             all_parents.append(current_chart_template.id)
3387         # create tax templates and tax code templates from purchase_tax_rate and sale_tax_rate fields
3388         if not chart_template.complete_tax_set:
3389             value = obj_wizard.sale_tax_rate
3390             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)
3391             obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Tax %.2f%%') % value})
3392             value = obj_wizard.purchase_tax_rate
3393             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)
3394             obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Purchase Tax %.2f%%') % value})
3395         return True
3396
3397     def execute(self, cr, uid, ids, context=None):
3398         '''
3399         This function is called at the confirmation of the wizard to generate the COA from the templates. It will read
3400         all the provided information to create the accounts, the banks, the journals, the taxes, the tax codes, the
3401         accounting properties... accordingly for the chosen company.
3402         '''
3403         obj_data = self.pool.get('ir.model.data')
3404         ir_values_obj = self.pool.get('ir.values')
3405         obj_wizard = self.browse(cr, uid, ids[0])
3406         company_id = obj_wizard.company_id.id
3407
3408         self.pool.get('res.company').write(cr, uid, [company_id], {'currency_id': obj_wizard.currency_id.id})
3409
3410         # When we install the CoA of first company, set the currency to price types and pricelists
3411         if company_id==1:
3412             for ref in (('product','list_price'),('product','standard_price'),('product','list0'),('purchase','list0')):
3413                 try:
3414                     tmp2 = obj_data.get_object_reference(cr, uid, *ref)
3415                     if tmp2: 
3416                         self.pool.get(tmp2[0]).write(cr, uid, tmp2[1], {
3417                             'currency_id': obj_wizard.currency_id.id
3418                         })
3419                 except ValueError, e:
3420                     pass
3421
3422         # If the floats for sale/purchase rates have been filled, create templates from them
3423         self._create_tax_templates_from_rates(cr, uid, obj_wizard, company_id, context=context)
3424
3425         # Install all the templates objects and generate the real objects
3426         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)
3427
3428         # write values of default taxes for product as super user
3429         if obj_wizard.sale_tax and taxes_ref:
3430             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)
3431         if obj_wizard.purchase_tax and taxes_ref:
3432             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)
3433
3434         # Create Bank journals
3435         self._create_bank_journals_from_o2m(cr, uid, obj_wizard, company_id, acc_template_ref, context=context)
3436         return {}
3437
3438     def _prepare_bank_journal(self, cr, uid, line, current_num, default_account_id, company_id, context=None):
3439         '''
3440         This function prepares the value to use for the creation of a bank journal created through the wizard of
3441         generating COA from templates.
3442
3443         :param line: dictionary containing the values encoded by the user related to his bank account
3444         :param current_num: integer corresponding to a counter of the already created bank journals through this wizard.
3445         :param default_account_id: id of the default debit.credit account created before for this journal.
3446         :param company_id: id of the company for which the wizard is running
3447         :return: mapping of field names and values
3448         :rtype: dict
3449         '''
3450         obj_data = self.pool.get('ir.model.data')
3451         obj_journal = self.pool.get('account.journal')
3452         
3453
3454         # we need to loop again to find next number for journal code
3455         # because we can't rely on the value current_num as,
3456         # its possible that we already have bank journals created (e.g. by the creation of res.partner.bank)
3457         # and the next number for account code might have been already used before for journal
3458         for num in xrange(current_num, 100):
3459             # journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
3460             journal_code = _('BNK')[:3] + str(num)
3461             ids = obj_journal.search(cr, uid, [('code', '=', journal_code), ('company_id', '=', company_id)], context=context)
3462             if not ids:
3463                 break
3464         else:
3465             raise osv.except_osv(_('Error!'), _('Cannot generate an unused journal code.'))
3466
3467         vals = {
3468                 'name': line['acc_name'],
3469                 'code': journal_code,
3470                 'type': line['account_type'] == 'cash' and 'cash' or 'bank',
3471                 'company_id': company_id,
3472                 'analytic_journal_id': False,
3473                 'currency': False,
3474                 'default_credit_account_id': default_account_id,
3475                 'default_debit_account_id': default_account_id,
3476         }
3477         if line['currency_id']:
3478             vals['currency'] = line['currency_id']
3479         
3480         return vals
3481
3482     def _prepare_bank_account(self, cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=None):
3483         '''
3484         This function prepares the value to use for the creation of the default debit and credit accounts of a
3485         bank journal created through the wizard of generating COA from templates.
3486
3487         :param line: dictionary containing the values encoded by the user related to his bank account
3488         :param new_code: integer corresponding to the next available number to use as account code
3489         :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3490             of the accounts that have been generated from them.
3491         :param ref_acc_bank: browse record of the account template set as root of all bank accounts for the chosen
3492             template
3493         :param company_id: id of the company for which the wizard is running
3494         :return: mapping of field names and values
3495         :rtype: dict
3496         '''
3497         obj_data = self.pool.get('ir.model.data')
3498
3499         # Get the id of the user types fr-or cash and bank
3500         tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_cash')
3501         cash_type = tmp and tmp[1] or False
3502         tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_bank')
3503         bank_type = tmp and tmp[1] or False
3504         return {
3505                 'name': line['acc_name'],
3506                 'currency_id': line['currency_id'],
3507                 'code': new_code,
3508                 'type': 'liquidity',
3509                 'user_type': line['account_type'] == 'cash' and cash_type or bank_type,
3510                 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
3511                 'company_id': company_id,
3512         }
3513
3514     def _create_bank_journals_from_o2m(self, cr, uid, obj_wizard, company_id, acc_template_ref, context=None):
3515         '''
3516         This function creates bank journals and its accounts for each line encoded in the field bank_accounts_id of the
3517         wizard.
3518
3519         :param obj_wizard: the current wizard that generates the COA from the templates.
3520         :param company_id: the id of the company for which the wizard is running.
3521         :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3522             of the accounts that have been generated from them.
3523         :return: True
3524         '''
3525         obj_acc = self.pool.get('account.account')
3526         obj_journal = self.pool.get('account.journal')
3527         code_digits = obj_wizard.code_digits
3528
3529         # Build a list with all the data to process
3530         journal_data = []
3531         if obj_wizard.bank_accounts_id:
3532             for acc in obj_wizard.bank_accounts_id:
3533                 vals = {
3534                     'acc_name': acc.acc_name,
3535                     'account_type': acc.account_type,
3536                     'currency_id': acc.currency_id.id,
3537                 }
3538                 journal_data.append(vals)
3539         ref_acc_bank = obj_wizard.chart_template_id.bank_account_view_id
3540         if journal_data and not ref_acc_bank.code:
3541             raise osv.except_osv(_('Configuration Error!'), _('You have to set a code for the bank account defined on the selected chart of accounts.'))
3542
3543         current_num = 1
3544         for line in journal_data:
3545             # Seek the next available number for the account code
3546             while True:
3547                 new_code = str(ref_acc_bank.code.ljust(code_digits-len(str(current_num)), '0')) + str(current_num)
3548                 ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)])
3549                 if not ids:
3550                     break
3551                 else:
3552                     current_num += 1
3553             # Create the default debit/credit accounts for this bank journal
3554             vals = self._prepare_bank_account(cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=context)
3555             default_account_id  = obj_acc.create(cr, uid, vals, context=context)
3556
3557             #create the bank journal
3558             vals_journal = self._prepare_bank_journal(cr, uid, line, current_num, default_account_id, company_id, context=context)
3559             obj_journal.create(cr, uid, vals_journal)
3560             current_num += 1
3561         return True
3562
3563 wizard_multi_charts_accounts()
3564
3565 class account_bank_accounts_wizard(osv.osv_memory):
3566     _name='account.bank.accounts.wizard'
3567
3568     _columns = {
3569         'acc_name': fields.char('Account Name.', size=64, required=True),
3570         'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True, ondelete='cascade'),
3571         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
3572         'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
3573     }
3574
3575 account_bank_accounts_wizard()
3576
3577 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: