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