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