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