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