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