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