caf1d68c45dabef96940aca854481c1d2d3a8bdc
[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('Note'),
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         'name': fields.char('Journal Name', size=64, required=True),
718         'code': fields.char('Code', size=5, required=True, help="The code will be displayed on reports."),
719         '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,
720                                  help="Select 'Sale' for customer invoices journals."\
721                                  " Select 'Purchase' for supplier invoices journals."\
722                                  " Select 'Cash' or 'Bank' for journals that are used in customer or supplier payments."\
723                                  " Select 'General' for miscellaneous operations journals."\
724                                  " Select 'Opening/Closing Situation' for entries generated for new fiscal years."),
725         'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
726         'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
727         '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."),
728         'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
729         'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
730         '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."),
731         '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"),
732         '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."),
733         '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),
734         'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
735         'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
736         'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
737         '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.'),
738         'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
739         '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'),
740     }
741
742     _defaults = {
743         'user_id': lambda self, cr, uid, context: uid,
744         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
745     }
746     _sql_constraints = [
747         ('code_company_uniq', 'unique (code, company_id)', 'The code of the journal must be unique per company !'),
748         ('name_company_uniq', 'unique (name, company_id)', 'The name of the journal must be unique per company !'),
749     ]
750
751     _order = 'code'
752
753     def _check_currency(self, cr, uid, ids, context=None):
754         for journal in self.browse(cr, uid, ids, context=context):
755             if journal.currency:
756                 if journal.default_credit_account_id and not journal.default_credit_account_id.currency_id.id == journal.currency.id:
757                     return False
758                 if journal.default_debit_account_id and not journal.default_debit_account_id.currency_id.id == journal.currency.id:
759                     return False
760         return True
761
762     _constraints = [
763         (_check_currency, 'Configuration error! The currency chosen should be shared by the default accounts too.', ['currency','default_debit_account_id','default_credit_account_id']),
764     ]
765
766     def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
767         journal = self.browse(cr, uid, id, context=context)
768         if not default:
769             default = {}
770         default = default.copy()
771         default['code'] = (journal['code'] or '') + '(copy)'
772         default['name'] = (journal['name'] or '') + '(copy)'
773         default['sequence_id'] = False
774         return super(account_journal, self).copy(cr, uid, id, default, context=context)
775
776     def write(self, cr, uid, ids, vals, context=None):
777         if context is None:
778             context = {}
779         if isinstance(ids, (int, long)):
780             ids = [ids]
781         for journal in self.browse(cr, uid, ids, context=context):
782             if 'company_id' in vals and journal.company_id.id != vals['company_id']:
783                 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
784                 if move_lines:
785                     raise osv.except_osv(_('Warning !'), _('You can not modify the company of this journal as its related record exist in journal items'))
786         return super(account_journal, self).write(cr, uid, ids, vals, context=context)
787
788     def create_sequence(self, cr, uid, vals, context=None):
789         """ Create new no_gap entry sequence for every new Joural
790         """
791         # in account.journal code is actually the prefix of the sequence
792         # whereas ir.sequence code is a key to lookup global sequences.
793         prefix = vals['code'].upper()
794
795         seq = {
796             'name': vals['name'],
797             'implementation':'no_gap',
798             'prefix': prefix + "/%(year)s/",
799             'padding': 4,
800             'number_increment': 1
801         }
802         if 'company_id' in vals:
803             seq['company_id'] = vals['company_id']
804         return self.pool.get('ir.sequence').create(cr, uid, seq)
805
806     def create(self, cr, uid, vals, context=None):
807         if not 'sequence_id' in vals or not vals['sequence_id']:
808             # if we have the right to create a journal, we should be able to
809             # create it's sequence.
810             vals.update({'sequence_id': self.create_sequence(cr, 1, vals, context)})
811         return super(account_journal, self).create(cr, uid, vals, context)
812
813     def name_get(self, cr, user, ids, context=None):
814         """
815         Returns a list of tupples containing id, name.
816         result format: {[(id, name), (id, name), ...]}
817
818         @param cr: A database cursor
819         @param user: ID of the user currently logged in
820         @param ids: list of ids for which name should be read
821         @param context: context arguments, like lang, time zone
822
823         @return: Returns a list of tupples containing id, name
824         """
825         result = self.browse(cr, user, ids, context=context)
826         res = []
827         for rs in result:
828             if rs.currency:
829                 currency = rs.currency
830             else:
831                 currency = rs.company_id.currency_id
832             name = "%s (%s)" % (rs.name, currency.name)
833             res += [(rs.id, name)]
834         return res
835
836     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
837         if not args:
838             args = []
839         if context is None:
840             context = {}
841         ids = []
842         if context.get('journal_type', False):
843             args += [('type','=',context.get('journal_type'))]
844         if name:
845             ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
846         if not ids:
847             ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
848
849         return self.name_get(cr, user, ids, context=context)
850
851     def onchange_type(self, cr, uid, ids, type, currency, context=None):
852         obj_data = self.pool.get('ir.model.data')
853         user_pool = self.pool.get('res.users')
854
855         type_map = {
856             'sale':'account_sp_journal_view',
857             'sale_refund':'account_sp_refund_journal_view',
858             'purchase':'account_sp_journal_view',
859             'purchase_refund':'account_sp_refund_journal_view',
860             'cash':'account_journal_bank_view',
861             'bank':'account_journal_bank_view',
862             'general':'account_journal_view',
863             'situation':'account_journal_view'
864         }
865
866         res = {}
867         view_id = type_map.get(type, 'account_journal_view')
868         user = user_pool.browse(cr, uid, uid)
869         if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
870             view_id = 'account_journal_bank_view_multi'
871         data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
872         data = obj_data.browse(cr, uid, data_id[0], context=context)
873
874         res.update({
875             'centralisation':type == 'situation',
876             'view_id':data.res_id,
877         })
878         return {
879             'value':res
880         }
881
882 account_journal()
883
884 class account_fiscalyear(osv.osv):
885     _name = "account.fiscalyear"
886     _description = "Fiscal Year"
887     _columns = {
888         'name': fields.char('Fiscal Year', size=64, required=True),
889         'code': fields.char('Code', size=6, required=True),
890         'company_id': fields.many2one('res.company', 'Company', required=True),
891         'date_start': fields.date('Start Date', required=True),
892         'date_stop': fields.date('End Date', required=True),
893         'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
894         'state': fields.selection([('draft','Open'), ('done','Closed')], 'Status', readonly=True),
895     }
896     _defaults = {
897         'state': 'draft',
898         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
899     }
900     _order = "date_start, id"
901
902
903     def _check_duration(self, cr, uid, ids, context=None):
904         obj_fy = self.browse(cr, uid, ids[0], context=context)
905         if obj_fy.date_stop < obj_fy.date_start:
906             return False
907         return True
908
909     _constraints = [
910         (_check_duration, 'Error! The start date of the fiscal year must be before his end date.', ['date_start','date_stop'])
911     ]
912
913     def create_period3(self, cr, uid, ids, context=None):
914         return self.create_period(cr, uid, ids, context, 3)
915
916     def create_period(self, cr, uid, ids, context=None, interval=1):
917         period_obj = self.pool.get('account.period')
918         for fy in self.browse(cr, uid, ids, context=context):
919             ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
920             period_obj.create(cr, uid, {
921                     'name':  "%s %s" % (_('Opening Period'), ds.strftime('%Y')),
922                     'code': ds.strftime('00/%Y'),
923                     'date_start': ds,
924                     'date_stop': ds,
925                     'special': True,
926                     'fiscalyear_id': fy.id,
927                 })
928             while ds.strftime('%Y-%m-%d') < fy.date_stop:
929                 de = ds + relativedelta(months=interval, days=-1)
930
931                 if de.strftime('%Y-%m-%d') > fy.date_stop:
932                     de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
933
934                 period_obj.create(cr, uid, {
935                     'name': ds.strftime('%m/%Y'),
936                     'code': ds.strftime('%m/%Y'),
937                     'date_start': ds.strftime('%Y-%m-%d'),
938                     'date_stop': de.strftime('%Y-%m-%d'),
939                     'fiscalyear_id': fy.id,
940                 })
941                 ds = ds + relativedelta(months=interval)
942         return True
943
944     def find(self, cr, uid, dt=None, exception=True, context=None):
945         res = self.finds(cr, uid, dt, exception, context=context)
946         return res and res[0] or False
947
948     def finds(self, cr, uid, dt=None, exception=True, context=None):
949         if context is None: context = {}
950         if not dt:
951             dt = fields.date.context_today(self,cr,uid,context=context)
952         args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
953         if context.get('company_id', False):
954             company_id = context['company_id']
955         else:
956             company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
957         args.append(('company_id', '=', company_id))
958         ids = self.search(cr, uid, args, context=context)
959         if not ids:
960             if exception:
961                 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one from the configuration of the accounting menu.'))
962             else:
963                 return []
964         return ids
965
966     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
967         if args is None:
968             args = []
969         if context is None:
970             context = {}
971         ids = []
972         if name:
973             ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
974         if not ids:
975             ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
976         return self.name_get(cr, user, ids, context=context)
977
978 account_fiscalyear()
979
980 class account_period(osv.osv):
981     _name = "account.period"
982     _description = "Account period"
983     _columns = {
984         'name': fields.char('Period Name', size=64, required=True),
985         'code': fields.char('Code', size=12),
986         'special': fields.boolean('Opening/Closing Period', size=12,
987             help="These periods can overlap."),
988         'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
989         'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
990         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
991         'state': fields.selection([('draft','Open'), ('done','Closed')], 'Status', readonly=True,
992                                   help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
993         'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
994     }
995     _defaults = {
996         'state': 'draft',
997     }
998     _order = "date_start, special desc"
999     _sql_constraints = [
1000         ('name_company_uniq', 'unique(name, company_id)', 'The name of the period must be unique per company!'),
1001     ]
1002
1003     def _check_duration(self,cr,uid,ids,context=None):
1004         obj_period = self.browse(cr, uid, ids[0], context=context)
1005         if obj_period.date_stop < obj_period.date_start:
1006             return False
1007         return True
1008
1009     def _check_year_limit(self,cr,uid,ids,context=None):
1010         for obj_period in self.browse(cr, uid, ids, context=context):
1011             if obj_period.special:
1012                 continue
1013
1014             if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
1015                obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
1016                obj_period.fiscalyear_id.date_start > obj_period.date_start or \
1017                obj_period.fiscalyear_id.date_start > obj_period.date_stop:
1018                 return False
1019
1020             pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
1021             for period in self.browse(cr, uid, pids):
1022                 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
1023                     return False
1024         return True
1025
1026     _constraints = [
1027         (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
1028         (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
1029     ]
1030
1031     def next(self, cr, uid, period, step, context=None):
1032         ids = self.search(cr, uid, [('date_start','>',period.date_start)])
1033         if len(ids)>=step:
1034             return ids[step-1]
1035         return False
1036
1037     def find(self, cr, uid, dt=None, context=None):
1038         if context is None: context = {}
1039         if not dt:
1040             dt = fields.date.context_today(self,cr,uid,context=context)
1041 #CHECKME: shouldn't we check the state of the period?
1042         args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
1043         if context.get('company_id', False):
1044             args.append(('company_id', '=', context['company_id']))
1045         else:
1046             company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
1047             args.append(('company_id', '=', company_id))
1048         ids = self.search(cr, uid, args, context=context)
1049         if not ids:
1050             raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create one.')%dt)
1051         return ids
1052
1053     def action_draft(self, cr, uid, ids, *args):
1054         mode = 'draft'
1055         cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),))
1056         cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),))
1057         return True
1058
1059     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
1060         if args is None:
1061             args = []
1062         if context is None:
1063             context = {}
1064         ids = []
1065         if name:
1066             ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
1067         if not ids:
1068             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
1069         return self.name_get(cr, user, ids, context=context)
1070
1071     def write(self, cr, uid, ids, vals, context=None):
1072         if 'company_id' in vals:
1073             move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
1074             if move_lines:
1075                 raise osv.except_osv(_('Warning !'), _('You can not modify company of this period as some journal items exists.'))
1076         return super(account_period, self).write(cr, uid, ids, vals, context=context)
1077
1078     def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
1079         if period_from_id == period_to_id:
1080             return [period_from_id]
1081         period_from = self.browse(cr, uid, period_from_id)
1082         period_date_start = period_from.date_start
1083         company1_id = period_from.company_id.id
1084         period_to = self.browse(cr, uid, period_to_id)
1085         period_date_stop = period_to.date_stop
1086         company2_id = period_to.company_id.id
1087         if company1_id != company2_id:
1088             raise osv.except_osv(_('Error'), _('You should have chosen periods that belongs to the same company'))
1089         if period_date_start > period_date_stop:
1090             raise osv.except_osv(_('Error'), _('Start period should be smaller then End period'))
1091         #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).
1092         if period_from.special:
1093             return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id)])
1094         return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id), ('special', '=', False)])
1095
1096 account_period()
1097
1098 class account_journal_period(osv.osv):
1099     _name = "account.journal.period"
1100     _description = "Journal Period"
1101
1102     def _icon_get(self, cr, uid, ids, field_name, arg=None, context=None):
1103         result = {}.fromkeys(ids, 'STOCK_NEW')
1104         for r in self.read(cr, uid, ids, ['state']):
1105             result[r['id']] = {
1106                 'draft': 'STOCK_NEW',
1107                 'printed': 'STOCK_PRINT_PREVIEW',
1108                 'done': 'STOCK_DIALOG_AUTHENTICATION',
1109             }.get(r['state'], 'STOCK_NEW')
1110         return result
1111
1112     _columns = {
1113         'name': fields.char('Journal-Period Name', size=64, required=True),
1114         'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
1115         'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
1116         'icon': fields.function(_icon_get, string='Icon', type='char', size=32),
1117         '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."),
1118         'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'Status', required=True, readonly=True,
1119                                   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.'),
1120         'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
1121         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
1122     }
1123
1124     def _check(self, cr, uid, ids, context=None):
1125         for obj in self.browse(cr, uid, ids, context=context):
1126             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))
1127             res = cr.fetchall()
1128             if res:
1129                 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
1130         return True
1131
1132     def write(self, cr, uid, ids, vals, context=None):
1133         self._check(cr, uid, ids, context=context)
1134         return super(account_journal_period, self).write(cr, uid, ids, vals, context=context)
1135
1136     def create(self, cr, uid, vals, context=None):
1137         period_id = vals.get('period_id',False)
1138         if period_id:
1139             period = self.pool.get('account.period').browse(cr, uid, period_id, context=context)
1140             vals['state']=period.state
1141         return super(account_journal_period, self).create(cr, uid, vals, context)
1142
1143     def unlink(self, cr, uid, ids, context=None):
1144         self._check(cr, uid, ids, context=context)
1145         return super(account_journal_period, self).unlink(cr, uid, ids, context=context)
1146
1147     _defaults = {
1148         'state': 'draft',
1149         'active': True,
1150     }
1151     _order = "period_id"
1152
1153 account_journal_period()
1154
1155 class account_fiscalyear(osv.osv):
1156     _inherit = "account.fiscalyear"
1157     _description = "Fiscal Year"
1158     _columns = {
1159         'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1160     }
1161
1162     def copy(self, cr, uid, id, default={}, context=None):
1163         default.update({
1164             'period_ids': [],
1165             'end_journal_period_id': False
1166         })
1167         return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1168
1169 account_fiscalyear()
1170 #----------------------------------------------------------
1171 # Entries
1172 #----------------------------------------------------------
1173 class account_move(osv.osv):
1174     _name = "account.move"
1175     _description = "Account Entry"
1176     _order = 'id desc'
1177
1178     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1179         """
1180         Returns a list of tupples containing id, name, as internally it is called {def name_get}
1181         result format: {[(id, name), (id, name), ...]}
1182
1183         @param cr: A database cursor
1184         @param user: ID of the user currently logged in
1185         @param name: name to search
1186         @param args: other arguments
1187         @param operator: default operator is 'ilike', it can be changed
1188         @param context: context arguments, like lang, time zone
1189         @param limit: Returns first 'n' ids of complete result, default is 80.
1190
1191         @return: Returns a list of tuples containing id and name
1192         """
1193
1194         if not args:
1195           args = []
1196         ids = []
1197         if name:
1198             ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1199
1200         if not ids and name and type(name) == int:
1201             ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1202
1203         if not ids:
1204             ids += self.search(cr, user, args, limit=limit, context=context)
1205
1206         return self.name_get(cr, user, ids, context=context)
1207
1208     def name_get(self, cursor, user, ids, context=None):
1209         if isinstance(ids, (int, long)):
1210             ids = [ids]
1211         if not ids:
1212             return []
1213         res = []
1214         data_move = self.pool.get('account.move').browse(cursor, user, ids, context=context)
1215         for move in data_move:
1216             if move.state=='draft':
1217                 name = '*' + str(move.id)
1218             else:
1219                 name = move.name
1220             res.append((move.id, name))
1221         return res
1222
1223     def _get_period(self, cr, uid, context=None):
1224         periods = self.pool.get('account.period').find(cr, uid)
1225         if periods:
1226             return periods[0]
1227         return False
1228
1229     def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1230         if not ids: return {}
1231         cr.execute( 'SELECT move_id, SUM(debit) '\
1232                     'FROM account_move_line '\
1233                     'WHERE move_id IN %s '\
1234                     'GROUP BY move_id', (tuple(ids),))
1235         result = dict(cr.fetchall())
1236         for id in ids:
1237             result.setdefault(id, 0.0)
1238         return result
1239
1240     def _search_amount(self, cr, uid, obj, name, args, context):
1241         ids = set()
1242         for cond in args:
1243             amount = cond[2]
1244             if isinstance(cond[2],(list,tuple)):
1245                 if cond[1] in ['in','not in']:
1246                     amount = tuple(cond[2])
1247                 else:
1248                     continue
1249             else:
1250                 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1251                     continue
1252
1253             cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1254             res_ids = set(id[0] for id in cr.fetchall())
1255             ids = ids and (ids & res_ids) or res_ids
1256         if ids:
1257             return [('id', 'in', tuple(ids))]
1258         return [('id', '=', '0')]
1259
1260     _columns = {
1261         'name': fields.char('Number', size=64, required=True),
1262         'ref': fields.char('Reference', size=64),
1263         'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1264         'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1265         'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'Status', required=True, readonly=True,
1266             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.'),
1267         'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1268         '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.'),
1269         'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1270         'amount': fields.function(_amount_compute, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1271         'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}, select=True),
1272         'narration':fields.text('Internal Note'),
1273         'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1274         '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"),
1275     }
1276
1277     _defaults = {
1278         'name': '/',
1279         'state': 'draft',
1280         'period_id': _get_period,
1281         'date': fields.date.context_today,
1282         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1283     }
1284
1285     def _check_centralisation(self, cursor, user, ids, context=None):
1286         for move in self.browse(cursor, user, ids, context=context):
1287             if move.journal_id.centralisation:
1288                 move_ids = self.search(cursor, user, [
1289                     ('period_id', '=', move.period_id.id),
1290                     ('journal_id', '=', move.journal_id.id),
1291                     ])
1292                 if len(move_ids) > 1:
1293                     return False
1294         return True
1295
1296     _constraints = [
1297         (_check_centralisation,
1298             'You can not create more than one move per period on centralized journal',
1299             ['journal_id']),
1300     ]
1301
1302     def post(self, cr, uid, ids, context=None):
1303         if context is None:
1304             context = {}
1305         invoice = context.get('invoice', False)
1306         valid_moves = self.validate(cr, uid, ids, context)
1307
1308         if not valid_moves:
1309             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" !'))
1310         obj_sequence = self.pool.get('ir.sequence')
1311         for move in self.browse(cr, uid, valid_moves, context=context):
1312             if move.name =='/':
1313                 new_name = False
1314                 journal = move.journal_id
1315
1316                 if invoice and invoice.internal_number:
1317                     new_name = invoice.internal_number
1318                 else:
1319                     if journal.sequence_id:
1320                         c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1321                         new_name = obj_sequence.next_by_id(cr, uid, journal.sequence_id.id, c)
1322                     else:
1323                         raise osv.except_osv(_('Error'), _('No sequence defined on the journal !'))
1324
1325                 if new_name:
1326                     self.write(cr, uid, [move.id], {'name':new_name})
1327
1328         cr.execute('UPDATE account_move '\
1329                    'SET state=%s '\
1330                    'WHERE id IN %s',
1331                    ('posted', tuple(valid_moves),))
1332         return True
1333
1334     def button_validate(self, cursor, user, ids, context=None):
1335         for move in self.browse(cursor, user, ids, context=context):
1336             # check that all accounts have the same topmost ancestor
1337             top_common = None
1338             for line in move.line_id:
1339                 account = line.account_id
1340                 top_account = account
1341                 while top_account.parent_id:
1342                     top_account = top_account.parent_id
1343                 if not top_common:
1344                     top_common = top_account
1345                 elif top_account.id != top_common.id:
1346                     raise osv.except_osv(_('Error !'),
1347                                          _('You cannot validate this journal entry because account "%s" does not belong to chart of accounts "%s"!') % (account.name, top_common.name))
1348         return self.post(cursor, user, ids, context=context)
1349
1350     def button_cancel(self, cr, uid, ids, context=None):
1351         for line in self.browse(cr, uid, ids, context=context):
1352             if not line.journal_id.update_posted:
1353                 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.'))
1354         if ids:
1355             cr.execute('UPDATE account_move '\
1356                        'SET state=%s '\
1357                        'WHERE id IN %s', ('draft', tuple(ids),))
1358         return True
1359
1360     def onchange_line_id(self, cr, uid, ids, line_ids, context=None):
1361         balance = 0.0
1362         for line in line_ids:
1363             if line[2]:
1364                 balance += (line[2]['debit'] or 0.00)- (line[2]['credit'] or 0.00)
1365         return {'value': {'balance': balance}}
1366
1367     def write(self, cr, uid, ids, vals, context=None):
1368         if context is None:
1369             context = {}
1370         c = context.copy()
1371         c['novalidate'] = True
1372         result = super(account_move, self).write(cr, uid, ids, vals, c)
1373         self.validate(cr, uid, ids, context=context)
1374         return result
1375
1376     #
1377     # TODO: Check if period is closed !
1378     #
1379     def create(self, cr, uid, vals, context=None):
1380         if context is None:
1381             context = {}
1382         if 'line_id' in vals and context.get('copy'):
1383             for l in vals['line_id']:
1384                 if not l[0]:
1385                     l[2].update({
1386                         'reconcile_id':False,
1387                         'reconcil_partial_id':False,
1388                         'analytic_lines':False,
1389                         'invoice':False,
1390                         'ref':False,
1391                         'balance':False,
1392                         'account_tax_id':False,
1393                     })
1394
1395             if 'journal_id' in vals and vals.get('journal_id', False):
1396                 for l in vals['line_id']:
1397                     if not l[0]:
1398                         l[2]['journal_id'] = vals['journal_id']
1399                 context['journal_id'] = vals['journal_id']
1400             if 'period_id' in vals:
1401                 for l in vals['line_id']:
1402                     if not l[0]:
1403                         l[2]['period_id'] = vals['period_id']
1404                 context['period_id'] = vals['period_id']
1405             else:
1406                 default_period = self._get_period(cr, uid, context)
1407                 for l in vals['line_id']:
1408                     if not l[0]:
1409                         l[2]['period_id'] = default_period
1410                 context['period_id'] = default_period
1411
1412         if 'line_id' in vals:
1413             c = context.copy()
1414             c['novalidate'] = True
1415             result = super(account_move, self).create(cr, uid, vals, c)
1416             self.validate(cr, uid, [result], context)
1417         else:
1418             result = super(account_move, self).create(cr, uid, vals, context)
1419         return result
1420
1421     def copy(self, cr, uid, id, default={}, context=None):
1422         if context is None:
1423             context = {}
1424         default.update({
1425             'state':'draft',
1426             'name':'/',
1427         })
1428         context.update({
1429             'copy':True
1430         })
1431         return super(account_move, self).copy(cr, uid, id, default, context)
1432
1433     def unlink(self, cr, uid, ids, context=None, check=True):
1434         if context is None:
1435             context = {}
1436         toremove = []
1437         obj_move_line = self.pool.get('account.move.line')
1438         for move in self.browse(cr, uid, ids, context=context):
1439             if move['state'] != 'draft':
1440                 raise osv.except_osv(_('UserError'),
1441                         _('You can not delete a posted journal entry "%s"!') % \
1442                                 move['name'])
1443             line_ids = map(lambda x: x.id, move.line_id)
1444             context['journal_id'] = move.journal_id.id
1445             context['period_id'] = move.period_id.id
1446             obj_move_line._update_check(cr, uid, line_ids, context)
1447             obj_move_line.unlink(cr, uid, line_ids, context=context)
1448             toremove.append(move.id)
1449         result = super(account_move, self).unlink(cr, uid, toremove, context)
1450         return result
1451
1452     def _compute_balance(self, cr, uid, id, context=None):
1453         move = self.browse(cr, uid, id, context=context)
1454         amount = 0
1455         for line in move.line_id:
1456             amount+= (line.debit - line.credit)
1457         return amount
1458
1459     def _centralise(self, cr, uid, move, mode, context=None):
1460         assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1461         currency_obj = self.pool.get('res.currency')
1462         if context is None:
1463             context = {}
1464
1465         if mode=='credit':
1466             account_id = move.journal_id.default_debit_account_id.id
1467             mode2 = 'debit'
1468             if not account_id:
1469                 raise osv.except_osv(_('UserError'),
1470                         _('There is no default default debit account defined \n' \
1471                                 'on journal "%s"') % move.journal_id.name)
1472         else:
1473             account_id = move.journal_id.default_credit_account_id.id
1474             mode2 = 'credit'
1475             if not account_id:
1476                 raise osv.except_osv(_('UserError'),
1477                         _('There is no default default credit account defined \n' \
1478                                 'on journal "%s"') % move.journal_id.name)
1479
1480         # find the first line of this move with the current mode
1481         # or create it if it doesn't exist
1482         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1483         res = cr.fetchone()
1484         if res:
1485             line_id = res[0]
1486         else:
1487             context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1488             line_id = self.pool.get('account.move.line').create(cr, uid, {
1489                 'name': _(mode.capitalize()+' Centralisation'),
1490                 'centralisation': mode,
1491                 'account_id': account_id,
1492                 'move_id': move.id,
1493                 'journal_id': move.journal_id.id,
1494                 'period_id': move.period_id.id,
1495                 'date': move.period_id.date_stop,
1496                 'debit': 0.0,
1497                 'credit': 0.0,
1498             }, context)
1499
1500         # find the first line of this move with the other mode
1501         # so that we can exclude it from our calculation
1502         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1503         res = cr.fetchone()
1504         if res:
1505             line_id2 = res[0]
1506         else:
1507             line_id2 = 0
1508
1509         cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1510         result = cr.fetchone()[0] or 0.0
1511         cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1512
1513         #adjust also the amount in currency if needed
1514         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,))
1515         for row in cr.dictfetchall():
1516             currency_id = currency_obj.browse(cr, uid, row['currency_id'], context=context)
1517             if not currency_obj.is_zero(cr, uid, currency_id, row['amount_currency']):
1518                 amount_currency = row['amount_currency'] * -1
1519                 account_id = amount_currency > 0 and move.journal_id.default_debit_account_id.id or move.journal_id.default_credit_account_id.id
1520                 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']))
1521                 res = cr.fetchone()
1522                 if res:
1523                     cr.execute('update account_move_line set amount_currency=%s , account_id=%s where id=%s', (amount_currency, account_id, res[0]))
1524                 else:
1525                     context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1526                     line_id = self.pool.get('account.move.line').create(cr, uid, {
1527                         'name': _('Currency Adjustment'),
1528                         'centralisation': 'currency',
1529                         'account_id': account_id,
1530                         'move_id': move.id,
1531                         'journal_id': move.journal_id.id,
1532                         'period_id': move.period_id.id,
1533                         'date': move.period_id.date_stop,
1534                         'debit': 0.0,
1535                         'credit': 0.0,
1536                         'currency_id': row['currency_id'],
1537                         'amount_currency': amount_currency,
1538                     }, context)
1539
1540         return True
1541
1542     #
1543     # Validate a balanced move. If it is a centralised journal, create a move.
1544     #
1545     def validate(self, cr, uid, ids, context=None):
1546         if context and ('__last_update' in context):
1547             del context['__last_update']
1548
1549         valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1550         obj_analytic_line = self.pool.get('account.analytic.line')
1551         obj_move_line = self.pool.get('account.move.line')
1552         for move in self.browse(cr, uid, ids, context):
1553             # Unlink old analytic lines on move_lines
1554             for obj_line in move.line_id:
1555                 for obj in obj_line.analytic_lines:
1556                     obj_analytic_line.unlink(cr,uid,obj.id)
1557
1558             journal = move.journal_id
1559             amount = 0
1560             line_ids = []
1561             line_draft_ids = []
1562             company_id = None
1563             for line in move.line_id:
1564                 amount += line.debit - line.credit
1565                 line_ids.append(line.id)
1566                 if line.state=='draft':
1567                     line_draft_ids.append(line.id)
1568
1569                 if not company_id:
1570                     company_id = line.account_id.company_id.id
1571                 if not company_id == line.account_id.company_id.id:
1572                     raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1573
1574                 if line.account_id.currency_id and line.currency_id:
1575                     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):
1576                         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))
1577
1578             if abs(amount) < 10 ** -4:
1579                 # If the move is balanced
1580                 # Add to the list of valid moves
1581                 # (analytic lines will be created later for valid moves)
1582                 valid_moves.append(move)
1583
1584                 # Check whether the move lines are confirmed
1585
1586                 if not line_draft_ids:
1587                     continue
1588                 # Update the move lines (set them as valid)
1589
1590                 obj_move_line.write(cr, uid, line_draft_ids, {
1591                     'state': 'valid'
1592                 }, context, check=False)
1593
1594                 account = {}
1595                 account2 = {}
1596
1597                 if journal.type in ('purchase','sale'):
1598                     for line in move.line_id:
1599                         code = amount = 0
1600                         key = (line.account_id.id, line.tax_code_id.id)
1601                         if key in account2:
1602                             code = account2[key][0]
1603                             amount = account2[key][1] * (line.debit + line.credit)
1604                         elif line.account_id.id in account:
1605                             code = account[line.account_id.id][0]
1606                             amount = account[line.account_id.id][1] * (line.debit + line.credit)
1607                         if (code or amount) and not (line.tax_code_id or line.tax_amount):
1608                             obj_move_line.write(cr, uid, [line.id], {
1609                                 'tax_code_id': code,
1610                                 'tax_amount': amount
1611                             }, context, check=False)
1612             elif journal.centralisation:
1613                 # If the move is not balanced, it must be centralised...
1614
1615                 # Add to the list of valid moves
1616                 # (analytic lines will be created later for valid moves)
1617                 valid_moves.append(move)
1618
1619                 #
1620                 # Update the move lines (set them as valid)
1621                 #
1622                 self._centralise(cr, uid, move, 'debit', context=context)
1623                 self._centralise(cr, uid, move, 'credit', context=context)
1624                 obj_move_line.write(cr, uid, line_draft_ids, {
1625                     'state': 'valid'
1626                 }, context, check=False)
1627             else:
1628                 # We can't validate it (it's unbalanced)
1629                 # Setting the lines as draft
1630                 obj_move_line.write(cr, uid, line_ids, {
1631                     'state': 'draft'
1632                 }, context, check=False)
1633         # Create analytic lines for the valid moves
1634         for record in valid_moves:
1635             obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1636
1637         valid_moves = [move.id for move in valid_moves]
1638         return len(valid_moves) > 0 and valid_moves or False
1639
1640 account_move()
1641
1642 class account_move_reconcile(osv.osv):
1643     _name = "account.move.reconcile"
1644     _description = "Account Reconciliation"
1645     _columns = {
1646         'name': fields.char('Name', size=64, required=True),
1647         'type': fields.char('Type', size=16, required=True),
1648         'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1649         'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1650         'create_date': fields.date('Creation date', readonly=True),
1651     }
1652     _defaults = {
1653         'name': lambda self,cr,uid,ctx=None: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile', context=ctx) or '/',
1654     }
1655
1656     def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None):
1657         total = 0.0
1658         for rec in self.browse(cr, uid, ids, context=context):
1659             for line in rec.line_partial_ids:
1660                 if line.account_id.currency_id:
1661                     total += line.amount_currency
1662                 else:
1663                     total += (line.debit or 0.0) - (line.credit or 0.0)
1664         if not total:
1665             self.pool.get('account.move.line').write(cr, uid,
1666                 map(lambda x: x.id, rec.line_partial_ids),
1667                 {'reconcile_id': rec.id }
1668             )
1669         return True
1670
1671     def name_get(self, cr, uid, ids, context=None):
1672         if not ids:
1673             return []
1674         result = []
1675         for r in self.browse(cr, uid, ids, context=context):
1676             total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1677             if total:
1678                 name = '%s (%.2f)' % (r.name, total)
1679                 result.append((r.id,name))
1680             else:
1681                 result.append((r.id,r.name))
1682         return result
1683
1684 account_move_reconcile()
1685
1686 #----------------------------------------------------------
1687 # Tax
1688 #----------------------------------------------------------
1689 """
1690 a documenter
1691 child_depend: la taxe depend des taxes filles
1692 """
1693 class account_tax_code(osv.osv):
1694     """
1695     A code for the tax object.
1696
1697     This code is used for some tax declarations.
1698     """
1699     def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1700         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1701         if context.get('based_on', 'invoices') == 'payments':
1702             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1703                     FROM account_move_line AS line, \
1704                         account_move AS move \
1705                         LEFT JOIN account_invoice invoice ON \
1706                             (invoice.move_id = move.id) \
1707                     WHERE line.tax_code_id IN %s '+where+' \
1708                         AND move.id = line.move_id \
1709                         AND ((invoice.state = \'paid\') \
1710                             OR (invoice.id IS NULL)) \
1711                             GROUP BY line.tax_code_id',
1712                                 (parent_ids,) + where_params)
1713         else:
1714             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1715                     FROM account_move_line AS line, \
1716                     account_move AS move \
1717                     WHERE line.tax_code_id IN %s '+where+' \
1718                     AND move.id = line.move_id \
1719                     GROUP BY line.tax_code_id',
1720                        (parent_ids,) + where_params)
1721         res=dict(cr.fetchall())
1722         obj_precision = self.pool.get('decimal.precision')
1723         res2 = {}
1724         for record in self.browse(cr, uid, ids, context=context):
1725             def _rec_get(record):
1726                 amount = res.get(record.id, 0.0)
1727                 for rec in record.child_ids:
1728                     amount += _rec_get(rec) * rec.sign
1729                 return amount
1730             res2[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1731         return res2
1732
1733     def _sum_year(self, cr, uid, ids, name, args, context=None):
1734         if context is None:
1735             context = {}
1736         move_state = ('posted', )
1737         if context.get('state', 'all') == 'all':
1738             move_state = ('draft', 'posted', )
1739         if context.get('fiscalyear_id', False):
1740             fiscalyear_id = [context['fiscalyear_id']]
1741         else:
1742             fiscalyear_id = self.pool.get('account.fiscalyear').finds(cr, uid, exception=False)
1743         where = ''
1744         where_params = ()
1745         if fiscalyear_id:
1746             pids = []
1747             for fy in fiscalyear_id:
1748                 pids += map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fy).period_ids)
1749             if pids:
1750                 where = ' AND line.period_id IN %s AND move.state IN %s '
1751                 where_params = (tuple(pids), move_state)
1752         return self._sum(cr, uid, ids, name, args, context,
1753                 where=where, where_params=where_params)
1754
1755     def _sum_period(self, cr, uid, ids, name, args, context):
1756         if context is None:
1757             context = {}
1758         move_state = ('posted', )
1759         if context.get('state', False) == 'all':
1760             move_state = ('draft', 'posted', )
1761         if context.get('period_id', False):
1762             period_id = context['period_id']
1763         else:
1764             period_id = self.pool.get('account.period').find(cr, uid)
1765             if not period_id:
1766                 return dict.fromkeys(ids, 0.0)
1767             period_id = period_id[0]
1768         return self._sum(cr, uid, ids, name, args, context,
1769                 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1770
1771     _name = 'account.tax.code'
1772     _description = 'Tax Code'
1773     _rec_name = 'code'
1774     _columns = {
1775         'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1776         'code': fields.char('Case Code', size=64),
1777         'info': fields.text('Description'),
1778         'sum': fields.function(_sum_year, string="Year Sum"),
1779         'sum_period': fields.function(_sum_period, string="Period Sum"),
1780         'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1781         'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1782         'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1783         'company_id': fields.many2one('res.company', 'Company', required=True),
1784         'sign': fields.float('Coefficent for parent', required=True, help='You can specify here the coefficient that will be used when consolidating the amount of this case into its parent. For example, set 1/-1 if you want to add/substract it.'),
1785         'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"),
1786         'sequence': fields.integer('Sequence', help="Determine the display order in the report 'Accounting \ Reporting \ Generic Reporting \ Taxes \ Taxes Report'"),
1787     }
1788
1789     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1790         if not args:
1791             args = []
1792         if context is None:
1793             context = {}
1794         ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1795         return self.name_get(cr, user, ids, context)
1796
1797     def name_get(self, cr, uid, ids, context=None):
1798         if isinstance(ids, (int, long)):
1799             ids = [ids]
1800         if not ids:
1801             return []
1802         if isinstance(ids, (int, long)):
1803             ids = [ids]
1804         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1805         return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1806                 for x in reads]
1807
1808     def _default_company(self, cr, uid, context=None):
1809         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1810         if user.company_id:
1811             return user.company_id.id
1812         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1813     _defaults = {
1814         'company_id': _default_company,
1815         'sign': 1.0,
1816         'notprintable': False,
1817     }
1818
1819     def copy(self, cr, uid, id, default=None, context=None):
1820         if default is None:
1821             default = {}
1822         default = default.copy()
1823         default.update({'line_ids': []})
1824         return super(account_tax_code, self).copy(cr, uid, id, default, context)
1825
1826     _check_recursion = check_cycle
1827     _constraints = [
1828         (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1829     ]
1830     _order = 'code'
1831
1832 account_tax_code()
1833
1834 class account_tax(osv.osv):
1835     """
1836     A tax object.
1837
1838     Type: percent, fixed, none, code
1839         PERCENT: tax = price * amount
1840         FIXED: tax = price + amount
1841         NONE: no tax line
1842         CODE: execute python code. localcontext = {'price_unit':pu}
1843             return result in the context
1844             Ex: result=round(price_unit*0.21,4)
1845     """
1846
1847     def get_precision_tax():
1848         def change_digit_tax(cr):
1849             res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account')
1850             return (16, res+2)
1851         return change_digit_tax
1852
1853     _name = 'account.tax'
1854     _description = 'Tax'
1855     _columns = {
1856         'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1857         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the tax lines from the lowest sequences to the higher ones. The order is important if you have a tax with several tax children. In this case, the evaluation order is important."),
1858         'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For taxes of type percentage, enter % ratio between 0-1."),
1859         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the tax without removing it."),
1860         'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1861             help="The computation method for the tax amount."),
1862         'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1863             help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1864         'domain':fields.char('Domain', size=32, help="This field is only used if you develop your own module allowing developers to create specific taxes in a custom domain."),
1865         'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1866         'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1867         'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1868         'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1869         '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."),
1870         'python_compute':fields.text('Python Code'),
1871         'python_compute_inv':fields.text('Python Code (reverse)'),
1872         'python_applicable':fields.text('Python Code'),
1873
1874         #
1875         # Fields used for the VAT declaration
1876         #
1877         'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1878         'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1879         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1880         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1881
1882         # Same fields for refund invoices
1883
1884         'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1885         'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1886         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1887         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1888         '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"),
1889         'company_id': fields.many2one('res.company', 'Company', required=True),
1890         'description': fields.char('Tax Code',size=32),
1891         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1892         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1893
1894     }
1895     _sql_constraints = [
1896         ('name_company_uniq', 'unique(name, company_id)', 'Tax Name must be unique per company!'),
1897     ]
1898
1899     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1900         """
1901         Returns a list of tupples containing id, name, as internally it is called {def name_get}
1902         result format: {[(id, name), (id, name), ...]}
1903
1904         @param cr: A database cursor
1905         @param user: ID of the user currently logged in
1906         @param name: name to search
1907         @param args: other arguments
1908         @param operator: default operator is 'ilike', it can be changed
1909         @param context: context arguments, like lang, time zone
1910         @param limit: Returns first 'n' ids of complete result, default is 80.
1911
1912         @return: Returns a list of tupples containing id and name
1913         """
1914         if not args:
1915             args = []
1916         if context is None:
1917             context = {}
1918         ids = []
1919         if name:
1920             ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context)
1921             if not ids:
1922                 ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context)
1923         else:
1924             ids = self.search(cr, user, args, limit=limit, context=context or {})
1925         return self.name_get(cr, user, ids, context=context)
1926
1927     def write(self, cr, uid, ids, vals, context=None):
1928         if vals.get('type', False) and vals['type'] in ('none', 'code'):
1929             vals.update({'amount': 0.0})
1930         return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1931
1932     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1933         journal_pool = self.pool.get('account.journal')
1934
1935         if context and context.has_key('type'):
1936             if context.get('type') in ('out_invoice','out_refund'):
1937                 args += [('type_tax_use','in',['sale','all'])]
1938             elif context.get('type') in ('in_invoice','in_refund'):
1939                 args += [('type_tax_use','in',['purchase','all'])]
1940
1941         if context and context.has_key('journal_id'):
1942             journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1943             if journal.type in ('sale', 'purchase'):
1944                 args += [('type_tax_use','in',[journal.type,'all'])]
1945
1946         return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1947
1948     def name_get(self, cr, uid, ids, context=None):
1949         if not ids:
1950             return []
1951         res = []
1952         for record in self.read(cr, uid, ids, ['description','name'], context=context):
1953             name = record['description'] and record['description'] or record['name']
1954             res.append((record['id'],name ))
1955         return res
1956
1957     def _default_company(self, cr, uid, context=None):
1958         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1959         if user.company_id:
1960             return user.company_id.id
1961         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1962
1963     _defaults = {
1964         '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''',
1965         'python_compute_inv': '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
1966         'applicable_type': 'true',
1967         'type': 'percent',
1968         'amount': 0,
1969         'price_include': 0,
1970         'active': 1,
1971         'type_tax_use': 'all',
1972         'sequence': 1,
1973         'ref_tax_sign': 1,
1974         'ref_base_sign': 1,
1975         'tax_sign': 1,
1976         'base_sign': 1,
1977         'include_base_amount': False,
1978         'company_id': _default_company,
1979     }
1980     _order = 'sequence'
1981
1982     def _applicable(self, cr, uid, taxes, price_unit, product=None, partner=None):
1983         res = []
1984         for tax in taxes:
1985             if tax.applicable_type=='code':
1986                 localdict = {'price_unit':price_unit, 'product':product, 'partner':partner}
1987                 exec tax.python_applicable in localdict
1988                 if localdict.get('result', False):
1989                     res.append(tax)
1990             else:
1991                 res.append(tax)
1992         return res
1993
1994     def _unit_compute(self, cr, uid, taxes, price_unit, product=None, partner=None, quantity=0):
1995         taxes = self._applicable(cr, uid, taxes, price_unit ,product, partner)
1996         res = []
1997         cur_price_unit=price_unit
1998         for tax in taxes:
1999             # we compute the amount for the current tax object and append it to the result
2000             data = {'id':tax.id,
2001                     'name':tax.description and tax.description + " - " + tax.name or tax.name,
2002                     'account_collected_id':tax.account_collected_id.id,
2003                     'account_paid_id':tax.account_paid_id.id,
2004                     'base_code_id': tax.base_code_id.id,
2005                     'ref_base_code_id': tax.ref_base_code_id.id,
2006                     'sequence': tax.sequence,
2007                     'base_sign': tax.base_sign,
2008                     'tax_sign': tax.tax_sign,
2009                     'ref_base_sign': tax.ref_base_sign,
2010                     'ref_tax_sign': tax.ref_tax_sign,
2011                     'price_unit': cur_price_unit,
2012                     'tax_code_id': tax.tax_code_id.id,
2013                     'ref_tax_code_id': tax.ref_tax_code_id.id,
2014             }
2015             res.append(data)
2016             if tax.type=='percent':
2017                 amount = cur_price_unit * tax.amount
2018                 data['amount'] = amount
2019
2020             elif tax.type=='fixed':
2021                 data['amount'] = tax.amount
2022                 data['tax_amount']=quantity
2023                # data['amount'] = quantity
2024             elif tax.type=='code':
2025                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
2026                 exec tax.python_compute in localdict
2027                 amount = localdict['result']
2028                 data['amount'] = amount
2029             elif tax.type=='balance':
2030                 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2031                 data['balance'] = cur_price_unit
2032
2033             amount2 = data.get('amount', 0.0)
2034             if tax.child_ids:
2035                 if tax.child_depend:
2036                     latest = res.pop()
2037                 amount = amount2
2038                 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, product, partner, quantity)
2039                 res.extend(child_tax)
2040                 if tax.child_depend:
2041                     for r in res:
2042                         for name in ('base','ref_base'):
2043                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2044                                 r[name+'_code_id'] = latest[name+'_code_id']
2045                                 r[name+'_sign'] = latest[name+'_sign']
2046                                 r['price_unit'] = latest['price_unit']
2047                                 latest[name+'_code_id'] = False
2048                         for name in ('tax','ref_tax'):
2049                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2050                                 r[name+'_code_id'] = latest[name+'_code_id']
2051                                 r[name+'_sign'] = latest[name+'_sign']
2052                                 r['amount'] = data['amount']
2053                                 latest[name+'_code_id'] = False
2054             if tax.include_base_amount:
2055                 cur_price_unit+=amount2
2056         return res
2057
2058     def compute_all(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, force_excluded=False):
2059         """
2060         :param force_excluded: boolean used to say that we don't want to consider the value of field price_include of
2061             tax. It's used in encoding by line where you don't matter if you encoded a tax with that boolean to True or
2062             False
2063         RETURN: {
2064                 'total': 0.0,                # Total without taxes
2065                 'total_included: 0.0,        # Total with taxes
2066                 'taxes': []                  # List of taxes, see compute for the format
2067             }
2068         """
2069         precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2070         totalin = totalex = round(price_unit * quantity, precision)
2071         tin = []
2072         tex = []
2073         for tax in taxes:
2074             if not tax.price_include or force_excluded:
2075                 tex.append(tax)
2076             else:
2077                 tin.append(tax)
2078         tin = self.compute_inv(cr, uid, tin, price_unit, quantity, product=product, partner=partner)
2079         for r in tin:
2080             totalex -= r.get('amount', 0.0)
2081         totlex_qty = 0.0
2082         try:
2083             totlex_qty = totalex/quantity
2084         except:
2085             pass
2086         tex = self._compute(cr, uid, tex, totlex_qty, quantity,product=product, partner=partner)
2087         for r in tex:
2088             totalin += r.get('amount', 0.0)
2089         return {
2090             'total': totalex,
2091             'total_included': totalin,
2092             'taxes': tin + tex
2093         }
2094
2095     def compute(self, cr, uid, taxes, price_unit, quantity,  product=None, partner=None):
2096         _logger.warning("Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
2097         return self._compute(cr, uid, taxes, price_unit, quantity, product, partner)
2098
2099     def _compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None):
2100         """
2101         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2102
2103         RETURN:
2104             [ tax ]
2105             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2106             one tax for each tax id in IDS and their children
2107         """
2108         res = self._unit_compute(cr, uid, taxes, price_unit, product, partner, quantity)
2109         total = 0.0
2110         precision_pool = self.pool.get('decimal.precision')
2111         for r in res:
2112             if r.get('balance',False):
2113                 r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
2114             else:
2115                 r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
2116                 total += r['amount']
2117         return res
2118
2119     def _unit_compute_inv(self, cr, uid, taxes, price_unit, product=None, partner=None):
2120         taxes = self._applicable(cr, uid, taxes, price_unit,  product, partner)
2121         res = []
2122         taxes.reverse()
2123         cur_price_unit = price_unit
2124
2125         tax_parent_tot = 0.0
2126         for tax in taxes:
2127             if (tax.type=='percent') and not tax.include_base_amount:
2128                 tax_parent_tot += tax.amount
2129
2130         for tax in taxes:
2131             if (tax.type=='fixed') and not tax.include_base_amount:
2132                 cur_price_unit -= tax.amount
2133
2134         for tax in taxes:
2135             if tax.type=='percent':
2136                 if tax.include_base_amount:
2137                     amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
2138                 else:
2139                     amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
2140
2141             elif tax.type=='fixed':
2142                 amount = tax.amount
2143
2144             elif tax.type=='code':
2145                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
2146                 exec tax.python_compute_inv in localdict
2147                 amount = localdict['result']
2148             elif tax.type=='balance':
2149                 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2150
2151             if tax.include_base_amount:
2152                 cur_price_unit -= amount
2153                 todo = 0
2154             else:
2155                 todo = 1
2156             res.append({
2157                 'id': tax.id,
2158                 'todo': todo,
2159                 'name': tax.name,
2160                 'amount': amount,
2161                 'account_collected_id': tax.account_collected_id.id,
2162                 'account_paid_id': tax.account_paid_id.id,
2163                 'base_code_id': tax.base_code_id.id,
2164                 'ref_base_code_id': tax.ref_base_code_id.id,
2165                 'sequence': tax.sequence,
2166                 'base_sign': tax.base_sign,
2167                 'tax_sign': tax.tax_sign,
2168                 'ref_base_sign': tax.ref_base_sign,
2169                 'ref_tax_sign': tax.ref_tax_sign,
2170                 'price_unit': cur_price_unit,
2171                 'tax_code_id': tax.tax_code_id.id,
2172                 'ref_tax_code_id': tax.ref_tax_code_id.id,
2173             })
2174             if tax.child_ids:
2175                 if tax.child_depend:
2176                     del res[-1]
2177                     amount = price_unit
2178
2179             parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, product, partner)
2180             res.extend(parent_tax)
2181
2182         total = 0.0
2183         for r in res:
2184             if r['todo']:
2185                 total += r['amount']
2186         for r in res:
2187             r['price_unit'] -= total
2188             r['todo'] = 0
2189         return res
2190
2191     def compute_inv(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None):
2192         """
2193         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2194         Price Unit is a VAT included price
2195
2196         RETURN:
2197             [ tax ]
2198             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2199             one tax for each tax id in IDS and their children
2200         """
2201         res = self._unit_compute_inv(cr, uid, taxes, price_unit, product, partner=None)
2202         total = 0.0
2203         obj_precision = self.pool.get('decimal.precision')
2204         for r in res:
2205             prec = obj_precision.precision_get(cr, uid, 'Account')
2206             if r.get('balance',False):
2207                 r['amount'] = round(r['balance'] * quantity, prec) - total
2208             else:
2209                 r['amount'] = round(r['amount'] * quantity, prec)
2210                 total += r['amount']
2211         return res
2212
2213 account_tax()
2214
2215 # ---------------------------------------------------------
2216 # Account Entries Models
2217 # ---------------------------------------------------------
2218
2219 class account_model(osv.osv):
2220     _name = "account.model"
2221     _description = "Account Model"
2222     _columns = {
2223         'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2224         'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2225         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2226         'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2227         'legend': fields.text('Legend', readonly=True, size=100),
2228     }
2229
2230     _defaults = {
2231         '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'),
2232     }
2233     def generate(self, cr, uid, ids, datas={}, context=None):
2234         move_ids = []
2235         entry = {}
2236         account_move_obj = self.pool.get('account.move')
2237         account_move_line_obj = self.pool.get('account.move.line')
2238         pt_obj = self.pool.get('account.payment.term')
2239         period_obj = self.pool.get('account.period')
2240
2241         if context is None:
2242             context = {}
2243
2244         if datas.get('date', False):
2245             context.update({'date': datas['date']})
2246
2247         move_date = context.get('date', time.strftime('%Y-%m-%d'))
2248         move_date = datetime.strptime(move_date,"%Y-%m-%d")
2249         for model in self.browse(cr, uid, ids, context=context):
2250             ctx = context.copy()
2251             ctx.update({'company_id': model.company_id.id})
2252             period_ids = period_obj.find(cr, uid, dt=context.get('date', False), context=ctx)
2253             period_id = period_ids and period_ids[0] or False
2254             ctx.update({'journal_id': model.journal_id.id,'period_id': period_id})
2255             try:
2256                 entry['name'] = model.name%{'year': move_date.strftime('%Y'), 'month': move_date.strftime('%m'), 'date': move_date.strftime('%Y-%m')}
2257             except:
2258                 raise osv.except_osv(_('Wrong model !'), _('You have a wrong expression "%(...)s" in your model !'))
2259             move_id = account_move_obj.create(cr, uid, {
2260                 'ref': entry['name'],
2261                 'period_id': period_id,
2262                 'journal_id': model.journal_id.id,
2263                 'date': context.get('date', fields.date.context_today(self,cr,uid,context=context))
2264             })
2265             move_ids.append(move_id)
2266             for line in model.lines_id:
2267                 analytic_account_id = False
2268                 if line.analytic_account_id:
2269                     if not model.journal_id.analytic_journal_id:
2270                         raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2271                     analytic_account_id = line.analytic_account_id.id
2272                 val = {
2273                     'move_id': move_id,
2274                     'journal_id': model.journal_id.id,
2275                     'period_id': period_id,
2276                     'analytic_account_id': analytic_account_id
2277                 }
2278
2279                 date_maturity = context.get('date',time.strftime('%Y-%m-%d'))
2280                 if line.date_maturity == 'partner':
2281                     if not line.partner_id:
2282                         raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2283                                                                 "\nPlease define partner on it!")%(line.name, model.name))
2284                     if line.partner_id.property_payment_term:
2285                         payment_term_id = line.partner_id.property_payment_term.id
2286                         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2287                         if pterm_list:
2288                             pterm_list = [l[0] for l in pterm_list]
2289                             pterm_list.sort()
2290                             date_maturity = pterm_list[-1]
2291
2292                 val.update({
2293                     'name': line.name,
2294                     'quantity': line.quantity,
2295                     'debit': line.debit,
2296                     'credit': line.credit,
2297                     'account_id': line.account_id.id,
2298                     'move_id': move_id,
2299                     'partner_id': line.partner_id.id,
2300                     'date': context.get('date', fields.date.context_today(self,cr,uid,context=context)),
2301                     'date_maturity': date_maturity
2302                 })
2303                 account_move_line_obj.create(cr, uid, val, context=ctx)
2304
2305         return move_ids
2306
2307 account_model()
2308
2309 class account_model_line(osv.osv):
2310     _name = "account.model.line"
2311     _description = "Account Model Entries"
2312     _columns = {
2313         'name': fields.char('Name', size=64, required=True),
2314         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones."),
2315         'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries."),
2316         'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2317         'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2318         'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2319         'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2320         'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2321         'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2322         'currency_id': fields.many2one('res.currency', 'Currency'),
2323         'partner_id': fields.many2one('res.partner', 'Partner'),
2324         '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."),
2325     }
2326     _order = 'sequence'
2327     _sql_constraints = [
2328         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in model, they must be positive!'),
2329         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model, they must be positive!'),
2330     ]
2331 account_model_line()
2332
2333 # ---------------------------------------------------------
2334 # Account Subscription
2335 # ---------------------------------------------------------
2336
2337
2338 class account_subscription(osv.osv):
2339     _name = "account.subscription"
2340     _description = "Account Subscription"
2341     _columns = {
2342         'name': fields.char('Name', size=64, required=True),
2343         'ref': fields.char('Reference', size=16),
2344         'model_id': fields.many2one('account.model', 'Model', required=True),
2345         'date_start': fields.date('Start Date', required=True),
2346         'period_total': fields.integer('Number of Periods', required=True),
2347         'period_nbr': fields.integer('Period', required=True),
2348         'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2349         'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', required=True, readonly=True),
2350         'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2351     }
2352     _defaults = {
2353         'date_start': fields.date.context_today,
2354         'period_type': 'month',
2355         'period_total': 12,
2356         'period_nbr': 1,
2357         'state': 'draft',
2358     }
2359     def state_draft(self, cr, uid, ids, context=None):
2360         self.write(cr, uid, ids, {'state':'draft'})
2361         return False
2362
2363     def check(self, cr, uid, ids, context=None):
2364         todone = []
2365         for sub in self.browse(cr, uid, ids, context=context):
2366             ok = True
2367             for line in sub.lines_id:
2368                 if not line.move_id.id:
2369                     ok = False
2370                     break
2371             if ok:
2372                 todone.append(sub.id)
2373         if todone:
2374             self.write(cr, uid, todone, {'state':'done'})
2375         return False
2376
2377     def remove_line(self, cr, uid, ids, context=None):
2378         toremove = []
2379         for sub in self.browse(cr, uid, ids, context=context):
2380             for line in sub.lines_id:
2381                 if not line.move_id.id:
2382                     toremove.append(line.id)
2383         if toremove:
2384             self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2385         self.write(cr, uid, ids, {'state':'draft'})
2386         return False
2387
2388     def compute(self, cr, uid, ids, context=None):
2389         for sub in self.browse(cr, uid, ids, context=context):
2390             ds = sub.date_start
2391             for i in range(sub.period_total):
2392                 self.pool.get('account.subscription.line').create(cr, uid, {
2393                     'date': ds,
2394                     'subscription_id': sub.id,
2395                 })
2396                 if sub.period_type=='day':
2397                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2398                 if sub.period_type=='month':
2399                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2400                 if sub.period_type=='year':
2401                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2402         self.write(cr, uid, ids, {'state':'running'})
2403         return True
2404
2405 account_subscription()
2406
2407 class account_subscription_line(osv.osv):
2408     _name = "account.subscription.line"
2409     _description = "Account Subscription Line"
2410     _columns = {
2411         'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2412         'date': fields.date('Date', required=True),
2413         'move_id': fields.many2one('account.move', 'Entry'),
2414     }
2415
2416     def move_create(self, cr, uid, ids, context=None):
2417         tocheck = {}
2418         all_moves = []
2419         obj_model = self.pool.get('account.model')
2420         for line in self.browse(cr, uid, ids, context=context):
2421             datas = {
2422                 'date': line.date,
2423             }
2424             move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2425             tocheck[line.subscription_id.id] = True
2426             self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2427             all_moves.extend(move_ids)
2428         if tocheck:
2429             self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2430         return all_moves
2431
2432     _rec_name = 'date'
2433
2434 account_subscription_line()
2435
2436 #  ---------------------------------------------------------------
2437 #   Account Templates: Account, Tax, Tax Code and chart. + Wizard
2438 #  ---------------------------------------------------------------
2439
2440 class account_tax_template(osv.osv):
2441     _name = 'account.tax.template'
2442 account_tax_template()
2443
2444 class account_account_template(osv.osv):
2445     _order = "code"
2446     _name = "account.account.template"
2447     _description ='Templates for Accounts'
2448
2449     _columns = {
2450         'name': fields.char('Name', size=256, required=True, select=True),
2451         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2452         'code': fields.char('Code', size=64, required=True, select=1),
2453         'type': fields.selection([
2454             ('receivable','Receivable'),
2455             ('payable','Payable'),
2456             ('view','View'),
2457             ('consolidation','Consolidation'),
2458             ('liquidity','Liquidity'),
2459             ('other','Regular'),
2460             ('closed','Closed'),
2461             ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2462             "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2463             "can have children accounts for multi-company consolidations, payable/receivable are for "\
2464             "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2465         'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2466             help="These types are defined according to your country. The type contains more information "\
2467             "about the account and its specificities."),
2468         'financial_report_ids': fields.many2many('account.financial.report', 'account_template_financial_report', 'account_template_id', 'report_line_id', 'Financial Reports'),
2469         'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2470         'shortcut': fields.char('Shortcut', size=12),
2471         'note': fields.text('Note'),
2472         'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2473         'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2474         'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2475         'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2476         '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)."),
2477     }
2478
2479     _defaults = {
2480         'reconcile': False,
2481         'type': 'view',
2482         'nocreate': False,
2483     }
2484
2485     def _check_type(self, cr, uid, ids, context=None):
2486         if context is None:
2487             context = {}
2488         accounts = self.browse(cr, uid, ids, context=context)
2489         for account in accounts:
2490             if account.parent_id and account.parent_id.type != 'view':
2491                 return False
2492         return True
2493
2494     _check_recursion = check_cycle
2495     _constraints = [
2496         (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id']),
2497         (_check_type, 'Configuration Error!\nYou can not define children to an account with internal type different of "View"! ', ['type']),
2498
2499     ]
2500
2501     def name_get(self, cr, uid, ids, context=None):
2502         if not ids:
2503             return []
2504         reads = self.read(cr, uid, ids, ['name','code'], context=context)
2505         res = []
2506         for record in reads:
2507             name = record['name']
2508             if record['code']:
2509                 name = record['code']+' '+name
2510             res.append((record['id'],name ))
2511         return res
2512
2513     def generate_account(self, cr, uid, chart_template_id, tax_template_ref, acc_template_ref, code_digits, company_id, context=None):
2514         """
2515         This method for generating accounts from templates.
2516
2517         :param chart_template_id: id of the chart template chosen in the wizard
2518         :param tax_template_ref: Taxes templates reference for write taxes_id in account_account.
2519         :paramacc_template_ref: dictionary with the mappping between the account templates and the real accounts.
2520         :param code_digits: number of digits got from wizard.multi.charts.accounts, this is use for account code.
2521         :param company_id: company_id selected from wizard.multi.charts.accounts.
2522         :returns: return acc_template_ref for reference purpose.
2523         :rtype: dict
2524         """
2525         if context is None:
2526             context = {}
2527         obj_acc = self.pool.get('account.account')
2528         company_name = self.pool.get('res.company').browse(cr, uid, company_id, context=context).name
2529         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
2530         #deactivate the parent_store functionnality on account_account for rapidity purpose
2531         ctx = context.copy()
2532         ctx.update({'defer_parent_store_computation': True})
2533         level_ref = {}
2534         children_acc_criteria = [('chart_template_id','=', chart_template_id)]
2535         if template.account_root_id.id:
2536             children_acc_criteria = ['|'] + children_acc_criteria + ['&',('parent_id','child_of', [template.account_root_id.id]),('chart_template_id','=', False)]
2537         children_acc_template = self.search(cr, uid, [('nocreate','!=',True)] + children_acc_criteria, order='id')
2538         for account_template in self.browse(cr, uid, children_acc_template, context=context):
2539             # skip the root of COA if it's not the main one
2540             if (template.account_root_id.id == account_template.id) and template.parent_id:
2541                 continue
2542             tax_ids = []
2543             for tax in account_template.tax_ids:
2544                 tax_ids.append(tax_template_ref[tax.id])
2545
2546             code_main = account_template.code and len(account_template.code) or 0
2547             code_acc = account_template.code or ''
2548             if code_main > 0 and code_main <= code_digits and account_template.type != 'view':
2549                 code_acc = str(code_acc) + (str('0'*(code_digits-code_main)))
2550             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
2551             #the level as to be given as well at the creation time, because of the defer_parent_store_computation in
2552             #context. Indeed because of this, the parent_left and parent_right are not computed and thus the child_of
2553             #operator does not return the expected values, with result of having the level field not computed at all.
2554             if parent_id:
2555                 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
2556             else:
2557                 level = 0
2558             vals={
2559                 'name': (template.account_root_id.id == account_template.id) and company_name or account_template.name,
2560                 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2561                 'code': code_acc,
2562                 'type': account_template.type,
2563                 'user_type': account_template.user_type and account_template.user_type.id or False,
2564                 'reconcile': account_template.reconcile,
2565                 'shortcut': account_template.shortcut,
2566                 'note': account_template.note,
2567                 'financial_report_ids': account_template.financial_report_ids and [(6,0,[x.id for x in account_template.financial_report_ids])] or False,
2568                 'parent_id': parent_id,
2569                 'tax_ids': [(6,0,tax_ids)],
2570                 'company_id': company_id,
2571                 'level': level,
2572             }
2573             new_account = obj_acc.create(cr, uid, vals, context=ctx)
2574             acc_template_ref[account_template.id] = new_account
2575             level_ref[new_account] = level
2576
2577         #reactivate the parent_store functionnality on account_account
2578         obj_acc._parent_store_compute(cr)
2579         return acc_template_ref
2580
2581 account_account_template()
2582
2583 class account_add_tmpl_wizard(osv.osv_memory):
2584     """Add one more account from the template.
2585
2586     With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2587     _name = 'account.addtmpl.wizard'
2588
2589     def _get_def_cparent(self, cr, uid, context=None):
2590         acc_obj = self.pool.get('account.account')
2591         tmpl_obj = self.pool.get('account.account.template')
2592         tids = tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2593         if not tids or not tids[0]['parent_id']:
2594             return False
2595         ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2596         res = None
2597         if not ptids or not ptids[0]['code']:
2598             raise osv.except_osv(_('Error !'), _('I can not locate a parent code for the template account!'))
2599             res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2600         return res and res[0] or False
2601
2602     _columns = {
2603         'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2604     }
2605     _defaults = {
2606         'cparent_id': _get_def_cparent,
2607     }
2608
2609     def action_create(self,cr,uid,ids,context=None):
2610         if context is None:
2611             context = {}
2612         acc_obj = self.pool.get('account.account')
2613         tmpl_obj = self.pool.get('account.account.template')
2614         data = self.read(cr, uid, ids)[0]
2615         company_id = acc_obj.read(cr, uid, [data['cparent_id'][0]], ['company_id'])[0]['company_id'][0]
2616         account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2617         vals = {
2618             'name': account_template.name,
2619             'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2620             'code': account_template.code,
2621             'type': account_template.type,
2622             'user_type': account_template.user_type and account_template.user_type.id or False,
2623             'reconcile': account_template.reconcile,
2624             'shortcut': account_template.shortcut,
2625             'note': account_template.note,
2626             'parent_id': data['cparent_id'][0],
2627             'company_id': company_id,
2628             }
2629         acc_obj.create(cr, uid, vals)
2630         return {'type':'state', 'state': 'end' }
2631
2632     def action_cancel(self, cr, uid, ids, context=None):
2633         return { 'type': 'state', 'state': 'end' }
2634
2635 account_add_tmpl_wizard()
2636
2637 class account_tax_code_template(osv.osv):
2638
2639     _name = 'account.tax.code.template'
2640     _description = 'Tax Code Template'
2641     _order = 'code'
2642     _rec_name = 'code'
2643     _columns = {
2644         'name': fields.char('Tax Case Name', size=64, required=True),
2645         'code': fields.char('Case Code', size=64),
2646         'info': fields.text('Description'),
2647         'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2648         'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2649         'sign': fields.float('Sign For Parent', required=True),
2650         'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"),
2651     }
2652
2653     _defaults = {
2654         'sign': 1.0,
2655         'notprintable': False,
2656     }
2657
2658     def generate_tax_code(self, cr, uid, tax_code_root_id, company_id, context=None):
2659         '''
2660         This function generates the tax codes from the templates of tax code that are children of the given one passed
2661         in argument. Then it returns a dictionary with the mappping between the templates and the real objects.
2662
2663         :param tax_code_root_id: id of the root of all the tax code templates to process
2664         :param company_id: id of the company the wizard is running for
2665         :returns: dictionary with the mappping between the templates and the real objects.
2666         :rtype: dict
2667         '''
2668         obj_tax_code_template = self.pool.get('account.tax.code.template')
2669         obj_tax_code = self.pool.get('account.tax.code')
2670         tax_code_template_ref = {}
2671         company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
2672
2673         #find all the children of the tax_code_root_id
2674         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 []
2675         for tax_code_template in obj_tax_code_template.browse(cr, uid, children_tax_code_template, context=context):
2676             vals = {
2677                 'name': (tax_code_root_id == tax_code_template.id) and company.name or tax_code_template.name,
2678                 'code': tax_code_template.code,
2679                 'info': tax_code_template.info,
2680                 '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,
2681                 'company_id': company_id,
2682                 'sign': tax_code_template.sign,
2683             }
2684             #check if this tax code already exists
2685             rec_list = obj_tax_code.search(cr, uid, [('name', '=', vals['name']),('code', '=', vals['code']),('company_id', '=', vals['company_id'])], context=context)
2686             if not rec_list:
2687                 #if not yet, create it
2688                 new_tax_code = obj_tax_code.create(cr, uid, vals)
2689                 #recording the new tax code to do the mapping
2690                 tax_code_template_ref[tax_code_template.id] = new_tax_code
2691         return tax_code_template_ref
2692
2693     def name_get(self, cr, uid, ids, context=None):
2694         if not ids:
2695             return []
2696         if isinstance(ids, (int, long)):
2697             ids = [ids]
2698         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2699         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2700                 for x in reads]
2701
2702     _check_recursion = check_cycle
2703     _constraints = [
2704         (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2705     ]
2706     _order = 'code,name'
2707 account_tax_code_template()
2708
2709
2710 class account_chart_template(osv.osv):
2711     _name="account.chart.template"
2712     _description= "Templates for Account Chart"
2713
2714     _columns={
2715         'name': fields.char('Name', size=64, required=True),
2716         'parent_id': fields.many2one('account.chart.template', 'Parent Chart Template'),
2717         'code_digits': fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"), 
2718         '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."),
2719         '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'),
2720         'account_root_id': fields.many2one('account.account.template', 'Root Account', domain=[('parent_id','=',False)]),
2721         'tax_code_root_id': fields.many2one('account.tax.code.template', 'Root Tax Code', domain=[('parent_id','=',False)]),
2722         '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'),
2723         'bank_account_view_id': fields.many2one('account.account.template', 'Bank Account'),
2724         'property_account_receivable': fields.many2one('account.account.template', 'Receivable Account'),
2725         'property_account_payable': fields.many2one('account.account.template', 'Payable Account'),
2726         'property_account_expense_categ': fields.many2one('account.account.template', 'Expense Category Account'),
2727         'property_account_income_categ': fields.many2one('account.account.template', 'Income Category Account'),
2728         'property_account_expense': fields.many2one('account.account.template', 'Expense Account on Product Template'),
2729         'property_account_income': fields.many2one('account.account.template', 'Income Account on Product Template'),
2730         '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'),
2731         'property_account_income_opening': fields.many2one('account.account.template', 'Opening Entries Income Account'),
2732         'property_account_expense_opening': fields.many2one('account.account.template', 'Opening Entries Expense Account'),
2733     }
2734
2735     _defaults = {
2736         'visible': True,
2737         'code_digits': 6,
2738         'complete_tax_set': True,
2739     }
2740
2741 account_chart_template()
2742
2743 class account_tax_template(osv.osv):
2744
2745     _name = 'account.tax.template'
2746     _description = 'Templates for Taxes'
2747
2748     _columns = {
2749         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2750         'name': fields.char('Tax Name', size=64, required=True),
2751         '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."),
2752         'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2753         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True),
2754         '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."),
2755         '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."),
2756         'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2757         'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2758         'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2759         '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."),
2760         'python_compute':fields.text('Python Code'),
2761         'python_compute_inv':fields.text('Python Code (reverse)'),
2762         'python_applicable':fields.text('Python Code'),
2763
2764         #
2765         # Fields used for the VAT declaration
2766         #
2767         'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2768         'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2769         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2770         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2771
2772         # Same fields for refund invoices
2773
2774         'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2775         'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2776         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2777         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2778         '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."),
2779         'description': fields.char('Internal Name', size=32),
2780         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,),
2781         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
2782     }
2783
2784     def name_get(self, cr, uid, ids, context=None):
2785         if not ids:
2786             return []
2787         res = []
2788         for record in self.read(cr, uid, ids, ['description','name'], context=context):
2789             name = record['description'] and record['description'] or record['name']
2790             res.append((record['id'],name ))
2791         return res
2792
2793     def _default_company(self, cr, uid, context=None):
2794         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2795         if user.company_id:
2796             return user.company_id.id
2797         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2798
2799     _defaults = {
2800         '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''',
2801         'python_compute_inv': lambda *a: '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
2802         'applicable_type': 'true',
2803         'type': 'percent',
2804         'amount': 0,
2805         'sequence': 1,
2806         'ref_tax_sign': 1,
2807         'ref_base_sign': 1,
2808         'tax_sign': 1,
2809         'base_sign': 1,
2810         'include_base_amount': False,
2811         'type_tax_use': 'all',
2812         'price_include': 0,
2813     }
2814     _order = 'sequence'
2815
2816     def _generate_tax(self, cr, uid, tax_templates, tax_code_template_ref, company_id, context=None):
2817         """
2818         This method generate taxes from templates.
2819
2820         :param tax_templates: list of browse record of the tax templates to process
2821         :param tax_code_template_ref: Taxcode templates reference.
2822         :param company_id: id of the company the wizard is running for
2823         :returns:
2824             {
2825             'tax_template_to_tax': mapping between tax template and the newly generated taxes corresponding,
2826             'account_dict': dictionary containing a to-do list with all the accounts to assign on new taxes
2827             }
2828         """
2829         if context is None:
2830             context = {}
2831         res = {}
2832         todo_dict = {}
2833         tax_template_to_tax = {}
2834         for tax in tax_templates:
2835             vals_tax = {
2836                 'name':tax.name,
2837                 'sequence': tax.sequence,
2838                 'amount': tax.amount,
2839                 'type': tax.type,
2840                 'applicable_type': tax.applicable_type,
2841                 'domain': tax.domain,
2842                 '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,
2843                 'child_depend': tax.child_depend,
2844                 'python_compute': tax.python_compute,
2845                 'python_compute_inv': tax.python_compute_inv,
2846                 'python_applicable': tax.python_applicable,
2847                 '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,
2848                 '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,
2849                 'base_sign': tax.base_sign,
2850                 'tax_sign': tax.tax_sign,
2851                 '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,
2852                 '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,
2853                 'ref_base_sign': tax.ref_base_sign,
2854                 'ref_tax_sign': tax.ref_tax_sign,
2855                 'include_base_amount': tax.include_base_amount,
2856                 'description': tax.description,
2857                 'company_id': company_id,
2858                 'type_tax_use': tax.type_tax_use,
2859                 'price_include': tax.price_include
2860             }
2861             new_tax = self.pool.get('account.tax').create(cr, uid, vals_tax)
2862             tax_template_to_tax[tax.id] = new_tax
2863             #as the accounts have not been created yet, we have to wait before filling these fields
2864             todo_dict[new_tax] = {
2865                 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2866                 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2867             }
2868         res.update({'tax_template_to_tax': tax_template_to_tax, 'account_dict': todo_dict})
2869         return res
2870
2871 account_tax_template()
2872
2873 # Fiscal Position Templates
2874
2875 class account_fiscal_position_template(osv.osv):
2876     _name = 'account.fiscal.position.template'
2877     _description = 'Template for Fiscal Position'
2878
2879     _columns = {
2880         'name': fields.char('Fiscal Position Template', size=64, required=True),
2881         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2882         'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2883         'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping'),
2884         'note': fields.text('Notes', translate=True),
2885     }
2886
2887     def generate_fiscal_position(self, cr, uid, chart_temp_id, tax_template_ref, acc_template_ref, company_id, context=None):
2888         """
2889         This method generate Fiscal Position, Fiscal Position Accounts and Fiscal Position Taxes from templates.
2890
2891         :param chart_temp_id: Chart Template Id.
2892         :param taxes_ids: Taxes templates reference for generating account.fiscal.position.tax.
2893         :param acc_template_ref: Account templates reference for generating account.fiscal.position.account.
2894         :param company_id: company_id selected from wizard.multi.charts.accounts.
2895         :returns: True
2896         """
2897         if context is None:
2898             context = {}
2899         obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2900         obj_ac_fp = self.pool.get('account.fiscal.position.account')
2901         obj_fiscal_position = self.pool.get('account.fiscal.position')
2902         fp_ids = self.search(cr, uid, [('chart_template_id', '=', chart_temp_id)])
2903         for position in self.browse(cr, uid, fp_ids, context=context):
2904             new_fp = obj_fiscal_position.create(cr, uid, {'company_id': company_id, 'name': position.name, 'note': position.note})
2905             for tax in position.tax_ids:
2906                 obj_tax_fp.create(cr, uid, {
2907                     'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2908                     'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2909                     'position_id': new_fp
2910                 })
2911             for acc in position.account_ids:
2912                 obj_ac_fp.create(cr, uid, {
2913                     'account_src_id': acc_template_ref[acc.account_src_id.id],
2914                     'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2915                     'position_id': new_fp
2916                 })
2917         return True
2918
2919 account_fiscal_position_template()
2920
2921 class account_fiscal_position_tax_template(osv.osv):
2922     _name = 'account.fiscal.position.tax.template'
2923     _description = 'Template Tax Fiscal Position'
2924     _rec_name = 'position_id'
2925
2926     _columns = {
2927         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2928         'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2929         'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2930     }
2931
2932 account_fiscal_position_tax_template()
2933
2934 class account_fiscal_position_account_template(osv.osv):
2935     _name = 'account.fiscal.position.account.template'
2936     _description = 'Template Account Fiscal Mapping'
2937     _rec_name = 'position_id'
2938     _columns = {
2939         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2940         'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2941         'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2942     }
2943
2944 account_fiscal_position_account_template()
2945
2946 # ---------------------------------------------------------
2947 # Account generation from template wizards
2948 # ---------------------------------------------------------
2949
2950 class wizard_multi_charts_accounts(osv.osv_memory):
2951     """
2952     Create a new account chart for a company.
2953     Wizards ask for:
2954         * a company
2955         * an account chart template
2956         * a number of digits for formatting code of non-view accounts
2957         * a list of bank accounts owned by the company
2958     Then, the wizard:
2959         * generates all accounts from the template and assigns them to the right company
2960         * generates all taxes and tax codes, changing account assignations
2961         * generates all accounting properties and assigns them correctly
2962     """
2963     _name='wizard.multi.charts.accounts'
2964     _inherit = 'res.config'
2965
2966     _columns = {
2967         'company_id':fields.many2one('res.company', 'Company', required=True),
2968         'only_one_chart_template': fields.boolean('Only One Chart Template Available'),
2969         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2970         'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Cash and Banks', required=True),
2971         'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2972         "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
2973         "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
2974         'sale_tax_rate': fields.float('Sales Tax(%)'),
2975         'purchase_tax_rate': fields.float('Purchase Tax(%)'),
2976         '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'),
2977     }
2978     def onchange_tax_rate(self, cr, uid, ids, rate=False, context=None):
2979         return {'value': {'purchase_tax_rate': rate or False}}
2980
2981     def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
2982         res = {}
2983         tax_templ_obj = self.pool.get('account.tax.template')
2984         res['value'] = {'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False}
2985         if chart_template_id:
2986             data = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
2987             res['value'].update({'complete_tax_set': data.complete_tax_set})
2988             if data.complete_tax_set:
2989             # 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
2990                 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
2991                                               , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence, id desc")
2992                 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
2993                                               , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence, id desc")
2994                 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})
2995
2996             if data.code_digits:
2997                res['value'].update({'code_digits': data.code_digits})
2998         return res
2999
3000     def default_get(self, cr, uid, fields, context=None):
3001         res = super(wizard_multi_charts_accounts, self).default_get(cr, uid, fields, context=context) 
3002         tax_templ_obj = self.pool.get('account.tax.template')
3003
3004         if 'bank_accounts_id' in fields:
3005             res.update({'bank_accounts_id': [{'acc_name': _('Cash'), 'account_type': 'cash'},{'acc_name': _('Bank'), 'account_type': 'bank'}]})
3006         if 'company_id' in fields:
3007             res.update({'company_id': self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0].company_id.id})
3008
3009         ids = self.pool.get('account.chart.template').search(cr, uid, [('visible', '=', True)], context=context)
3010         if ids:
3011             if 'chart_template_id' in fields:
3012                 res.update({'only_one_chart_template': len(ids) == 1, 'chart_template_id': ids[0]})
3013             if 'sale_tax' in fields:
3014                 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3015                                               , "=", ids[0]), ('type_tax_use', 'in', ('sale','all'))], order="sequence")
3016                 res.update({'sale_tax': sale_tax_ids and sale_tax_ids[0] or False})
3017             if 'purchase_tax' in fields:
3018                 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3019                                           , "=", ids[0]), ('type_tax_use', 'in', ('purchase','all'))], order="sequence")
3020                 res.update({'purchase_tax': purchase_tax_ids and purchase_tax_ids[0] or False})
3021         res.update({
3022             'purchase_tax_rate': 15.0,
3023             'sale_tax_rate': 15.0,
3024         })
3025         return res
3026
3027     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
3028         if context is None:context = {}
3029         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)
3030         cmp_select = []
3031         acc_template_obj = self.pool.get('account.chart.template')
3032         company_obj = self.pool.get('res.company')
3033
3034         company_ids = company_obj.search(cr, uid, [], context=context)
3035         #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)
3036         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",))
3037         configured_cmp = [r[0] for r in cr.fetchall()]
3038         unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
3039         for field in res['fields']:
3040             if field == 'company_id':
3041                 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
3042                 res['fields'][field]['selection'] = [('', '')]
3043                 if unconfigured_cmp:
3044                     cmp_select = [(line.id, line.name) for line in company_obj.browse(cr, uid, unconfigured_cmp)]
3045                     res['fields'][field]['selection'] = cmp_select
3046         return res
3047
3048     def check_created_journals(self, cr, uid, vals_journal, company_id, context=None):
3049         """
3050         This method used for checking journals already created or not. If not then create new journal.
3051         """
3052         obj_journal = self.pool.get('account.journal')
3053         rec_list = obj_journal.search(cr, uid, [('name','=', vals_journal['name']),('company_id', '=', company_id)], context=context)
3054         if not rec_list:
3055             obj_journal.create(cr, uid, vals_journal, context=context)
3056         return True
3057
3058     def generate_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3059         """
3060         This method is used for creating journals.
3061
3062         :param chart_temp_id: Chart Template Id.
3063         :param acc_template_ref: Account templates reference.
3064         :param company_id: company_id selected from wizard.multi.charts.accounts.
3065         :returns: True
3066         """
3067         journal_data = self._prepare_all_journals(cr, uid, chart_template_id, acc_template_ref, company_id, context=context)
3068         for vals_journal in journal_data:
3069             self.check_created_journals(cr, uid, vals_journal, company_id, context=context)
3070         return True
3071
3072     def _prepare_all_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3073         def _get_analytic_journal(journal_type):
3074             # Get the analytic journal
3075             data = False
3076             if journal_type in ('sale', 'sale_refund'):
3077                 data = obj_data.get_object_reference(cr, uid, 'account', 'analytic_journal_sale') 
3078             elif journal_type in ('purchase', 'purchase_refund'):
3079                 pass
3080             elif journal_type == 'general':
3081                 pass
3082             return data and data[1] or False
3083
3084         def _get_default_account(journal_type, type='debit'):
3085             # Get the default accounts
3086             default_account = False
3087             if journal_type in ('sale', 'sale_refund'):
3088                 default_account = acc_template_ref.get(template.property_account_income_categ.id)
3089             elif journal_type in ('purchase', 'purchase_refund'):
3090                 default_account = acc_template_ref.get(template.property_account_expense_categ.id)
3091             elif journal_type == 'situation':
3092                 if type == 'debit':
3093                     default_account = acc_template_ref.get(template.property_account_expense_opening.id)
3094                 else:
3095                     default_account = acc_template_ref.get(template.property_account_income_opening.id)
3096             return default_account
3097
3098         def _get_view_id(journal_type):
3099             # Get the journal views
3100             if journal_type in ('general', 'situation'):
3101                 data = obj_data.get_object_reference(cr, uid, 'account', 'account_journal_view')
3102             elif journal_type in ('sale_refund', 'purchase_refund'):
3103                 data = obj_data.get_object_reference(cr, uid, 'account', 'account_sp_refund_journal_view') 
3104             else:
3105                 data = obj_data.get_object_reference(cr, uid, 'account', 'account_sp_journal_view')
3106             return data and data[1] or False
3107
3108         journal_names = {
3109             'sale': _('Sales Journal'),
3110             'purchase': _('Purchase Journal'),
3111             'sale_refund': _('Sales Refund Journal'),
3112             'purchase_refund': _('Purchase Refund Journal'),
3113             'general': _('Miscellaneous Journal'),
3114             'situation': _('Opening Entries Journal'),
3115         }
3116         journal_codes = {
3117             'sale': _('SAJ'),
3118             'purchase': _('EXJ'),
3119             'sale_refund': _('SCNJ'),
3120             'purchase_refund': _('ECNJ'),
3121             'general': _('MISC'),
3122             'situation': _('OPEJ'),
3123         }
3124
3125         obj_data = self.pool.get('ir.model.data')
3126         analytic_journal_obj = self.pool.get('account.analytic.journal')
3127         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3128
3129         journal_data = []
3130         for journal_type in ['sale', 'purchase', 'sale_refund', 'purchase_refund', 'general', 'situation']:
3131             vals = {
3132                 'type': journal_type,
3133                 'name': journal_names[journal_type],
3134                 'code': journal_codes[journal_type],
3135                 'company_id': company_id,
3136                 'centralisation': journal_type == 'situation',
3137                 'view_id': _get_view_id(journal_type),
3138                 'analytic_journal_id': _get_analytic_journal(journal_type),
3139                 'default_credit_account_id': _get_default_account(journal_type, 'credit'),
3140                 'default_debit_account_id': _get_default_account(journal_type, 'debit'),
3141             }
3142             journal_data.append(vals)
3143         return journal_data
3144
3145     def generate_properties(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3146         """
3147         This method used for creating properties.
3148
3149         :param chart_template_id: id of the current chart template for which we need to create properties
3150         :param acc_template_ref: Mapping between ids of account templates and real accounts created from them
3151         :param company_id: company_id selected from wizard.multi.charts.accounts.
3152         :returns: True
3153         """
3154         property_obj = self.pool.get('ir.property')
3155         field_obj = self.pool.get('ir.model.fields')
3156         todo_list = [
3157             ('property_account_receivable','res.partner','account.account'),
3158             ('property_account_payable','res.partner','account.account'),
3159             ('property_account_expense_categ','product.category','account.account'),
3160             ('property_account_income_categ','product.category','account.account'),
3161             ('property_account_expense','product.template','account.account'),
3162             ('property_account_income','product.template','account.account'),
3163             ('property_reserve_and_surplus_account','res.company','account.account')
3164         ]
3165         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3166         for record in todo_list:
3167             account = getattr(template, record[0])
3168             value = account and 'account.account,' + str(acc_template_ref[account.id]) or False
3169             if value:
3170                 field = field_obj.search(cr, uid, [('name', '=', record[0]),('model', '=', record[1]),('relation', '=', record[2])], context=context)
3171                 vals = {
3172                     'name': record[0],
3173                     'company_id': company_id,
3174                     'fields_id': field[0],
3175                     'value': value,
3176                 }
3177                 property_ids = property_obj.search(cr, uid, [('name','=', record[0]),('company_id', '=', company_id)], context=context)
3178                 if property_ids:
3179                     #the property exist: modify it
3180                     property_obj.write(cr, uid, property_ids, vals, context=context)
3181                 else:
3182                     #create the property
3183                     property_obj.create(cr, uid, vals, context=context)
3184         return True
3185
3186     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):
3187         '''
3188         This function recursively loads the template objects and create the real objects from them.
3189
3190         :param template_id: id of the chart template to load
3191         :param company_id: id of the company the wizard is running for
3192         :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3193         :param obj_wizard: the current wizard for generating the COA from the templates
3194         :param acc_ref: Mapping between ids of account templates and real accounts created from them
3195         :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3196         :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3197         :returns: return a tuple with a dictionary containing
3198             * the mapping between the account template ids and the ids of the real accounts that have been generated
3199               from them, as first item,
3200             * a similar dictionary for mapping the tax templates and taxes, as second item,
3201             * a last identical containing the mapping of tax code templates and tax codes
3202         :rtype: tuple(dict, dict, dict)
3203         '''
3204         template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3205         if template.parent_id:
3206             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)
3207             acc_ref.update(tmp1)
3208             taxes_ref.update(tmp2)
3209             tax_code_ref.update(tmp3)
3210         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)
3211         acc_ref.update(tmp1)
3212         taxes_ref.update(tmp2)
3213         tax_code_ref.update(tmp3)
3214         return acc_ref, taxes_ref, tax_code_ref
3215
3216     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):
3217         '''
3218         This function generates all the objects from the templates
3219
3220         :param template_id: id of the chart template to load
3221         :param company_id: id of the company the wizard is running for
3222         :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3223         :param obj_wizard: the current wizard for generating the COA from the templates
3224         :param acc_ref: Mapping between ids of account templates and real accounts created from them
3225         :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3226         :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3227         :returns: return a tuple with a dictionary containing
3228             * the mapping between the account template ids and the ids of the real accounts that have been generated
3229               from them, as first item,
3230             * a similar dictionary for mapping the tax templates and taxes, as second item,
3231             * a last identical containing the mapping of tax code templates and tax codes
3232         :rtype: tuple(dict, dict, dict)
3233         '''
3234         template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3235         obj_tax_code_template = self.pool.get('account.tax.code.template')
3236         obj_acc_tax = self.pool.get('account.tax')
3237         obj_tax_temp = self.pool.get('account.tax.template')
3238         obj_acc_template = self.pool.get('account.account.template')
3239         obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
3240
3241         # create all the tax code.
3242         tax_code_ref.update(obj_tax_code_template.generate_tax_code(cr, uid, template.tax_code_root_id.id, company_id, context=context))
3243
3244         # Generate taxes from templates.
3245         tax_templates = [x for x in template.tax_template_ids]
3246         generated_tax_res = obj_tax_temp._generate_tax(cr, uid, tax_templates, tax_code_ref, company_id, context=context)
3247         taxes_ref.update(generated_tax_res['tax_template_to_tax'])
3248
3249         # Generating Accounts from templates.
3250         account_template_ref = obj_acc_template.generate_account(cr, uid, template_id, taxes_ref, account_ref, code_digits, company_id, context=context)
3251         account_ref.update(account_template_ref)
3252
3253         # writing account values on tax after creation of accounts
3254         for key,value in generated_tax_res['account_dict'].items():
3255             if value['account_collected_id'] or value['account_paid_id']:
3256                 obj_acc_tax.write(cr, uid, [key], {
3257                     'account_collected_id': account_ref.get(value['account_collected_id'], False),
3258                     'account_paid_id': account_ref.get(value['account_paid_id'], False),
3259                 })
3260
3261         # Create Journals
3262         self.generate_journals(cr, uid, template_id, account_ref, company_id, context=context)
3263
3264         # generate properties function
3265         self.generate_properties(cr, uid, template_id, account_ref, company_id, context=context)
3266
3267         # Generate Fiscal Position , Fiscal Position Accounts and Fiscal Position Taxes from templates
3268         obj_fiscal_position_template.generate_fiscal_position(cr, uid, template_id, taxes_ref, account_ref, company_id, context=context)
3269
3270         return account_ref, taxes_ref, tax_code_ref
3271
3272     def _create_tax_templates_from_rates(self, cr, uid, obj_wizard, company_id, context=None):
3273         '''
3274         This function checks if the chosen chart template is configured as containing a full set of taxes, and if
3275         it's not the case, it creates the templates for account.tax.code and for account.account.tax objects accordingly
3276         to the provided sale/purchase rates. Then it saves the new tax templates as default taxes to use for this chart
3277         template.
3278
3279         :param obj_wizard: browse record of wizard to generate COA from templates
3280         :param company_id: id of the company for wich the wizard is running
3281         :return: True
3282         '''
3283         obj_tax_code_template = self.pool.get('account.tax.code.template')
3284         obj_tax_temp = self.pool.get('account.tax.template')
3285         chart_template = obj_wizard.chart_template_id
3286         vals = {}
3287         # get the ids of all the parents of the selected account chart template
3288         current_chart_template = chart_template
3289         all_parents = [current_chart_template.id]
3290         while current_chart_template.parent_id:
3291             current_chart_template = current_chart_template.parent_id
3292             all_parents.append(current_chart_template.id)
3293         # create tax templates and tax code templates from purchase_tax_rate and sale_tax_rate fields
3294         if not chart_template.complete_tax_set:
3295             value = obj_wizard.sale_tax_rate
3296             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)
3297             obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Tax %.2f%%') % value})
3298             value = obj_wizard.purchase_tax_rate
3299             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)
3300             obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Purchase Tax %.2f%%') % value})
3301         return True
3302
3303     def execute(self, cr, uid, ids, context=None):
3304         '''
3305         This function is called at the confirmation of the wizard to generate the COA from the templates. It will read
3306         all the provided information to create the accounts, the banks, the journals, the taxes, the tax codes, the
3307         accounting properties... accordingly for the chosen company.
3308         '''
3309         ir_values_obj = self.pool.get('ir.values')
3310         obj_wizard = self.browse(cr, uid, ids[0])
3311         company_id = obj_wizard.company_id.id
3312         # If the floats for sale/purchase rates have been filled, create templates from them
3313         self._create_tax_templates_from_rates(cr, uid, obj_wizard, company_id, context=context)
3314
3315         # Install all the templates objects and generate the real objects
3316         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)
3317
3318         # write values of default taxes for product
3319         if obj_wizard.sale_tax and taxes_ref:
3320             ir_values_obj.set(cr, uid, key='default', key2=False, name="taxes_id", company=company_id,
3321                                 models =[('product.product',False)], value=[taxes_ref[obj_wizard.sale_tax.id]])
3322         if obj_wizard.purchase_tax and taxes_ref:
3323                 ir_values_obj.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=company_id,
3324                                 models =[('product.product',False)], value=[taxes_ref[obj_wizard.purchase_tax.id]])
3325
3326         # Create Bank journals
3327         self._create_bank_journals_from_o2m(cr, uid, obj_wizard, company_id, acc_template_ref, context=context)
3328         action = {
3329             'type': 'ir.actions.act_window',
3330             'view_type': 'form',
3331             'view_mode': 'form',
3332             'res_model': 'board.board',
3333             'view_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'board_account_form')[1],
3334             'menu_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'menu_finance')[1]
3335         }
3336         return action
3337
3338     def _prepare_bank_journal(self, cr, uid, line, current_num, default_account_id, company_id, context=None):
3339         '''
3340         This function prepares the value to use for the creation of a bank journal created through the wizard of 
3341         generating COA from templates.
3342
3343         :param line: dictionary containing the values encoded by the user related to his bank account
3344         :param current_num: integer corresponding to a counter of the already created bank journals through this wizard.
3345         :param default_account_id: id of the default debit.credit account created before for this journal.
3346         :param company_id: id of the company for which the wizard is running
3347         :return: mapping of field names and values
3348         :rtype: dict
3349         '''
3350         obj_data = self.pool.get('ir.model.data')
3351         obj_journal = self.pool.get('account.journal')
3352         # Get the id of journal views
3353         tmp = obj_data.get_object_reference(cr, uid, 'account', 'account_journal_bank_view_multi')
3354         view_id_cur = tmp and tmp[1] or False
3355         tmp = obj_data.get_object_reference(cr, uid, 'account', 'account_journal_bank_view')
3356         view_id_cash = tmp and tmp[1] or False
3357
3358         # we need to loop again to find next number for journal code 
3359         # because we can't rely on the value current_num as,
3360         # its possible that we already have bank journals created (e.g. by the creation of res.partner.bank) 
3361         # and the next number for account code might have been already used before for journal
3362         for num in xrange(current_num, 100):
3363             # journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
3364             journal_code = _('BNK')[:3] + str(num)
3365             ids = obj_journal.search(cr, uid, [('code', '=', journal_code), ('company_id', '=', company_id)], context=context)
3366             if not ids:
3367                 break
3368         else:
3369             raise osv.except_osv(_('Error'), _('Cannot generate an unused journal code.'))
3370
3371         vals = {
3372                 'name': line['acc_name'],
3373                 'code': journal_code,
3374                 'type': line['account_type'] == 'cash' and 'cash' or 'bank',
3375                 'company_id': company_id,
3376                 'analytic_journal_id': False,
3377                 'currency': False,
3378                 'default_credit_account_id': default_account_id,
3379                 'default_debit_account_id': default_account_id,
3380         }
3381         if line['currency_id']:
3382             vals['view_id'] = view_id_cur
3383             vals['currency'] = line['currency_id']
3384         else:
3385             vals['view_id'] = view_id_cash
3386         return vals
3387
3388     def _prepare_bank_account(self, cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=None):
3389         '''
3390         This function prepares the value to use for the creation of the default debit and credit accounts of a
3391         bank journal created through the wizard of generating COA from templates.
3392
3393         :param line: dictionary containing the values encoded by the user related to his bank account
3394         :param new_code: integer corresponding to the next available number to use as account code
3395         :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3396             of the accounts that have been generated from them.
3397         :param ref_acc_bank: browse record of the account template set as root of all bank accounts for the chosen
3398             template
3399         :param company_id: id of the company for which the wizard is running
3400         :return: mapping of field names and values
3401         :rtype: dict
3402         '''
3403         obj_data = self.pool.get('ir.model.data')
3404
3405         # Get the id of the user types fr-or cash and bank
3406         tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_cash')
3407         cash_type = tmp and tmp[1] or False
3408         tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_bank')
3409         bank_type = tmp and tmp[1] or False
3410         return {
3411                 'name': line['acc_name'],
3412                 'currency_id': line['currency_id'],
3413                 'code': new_code,
3414                 'type': 'liquidity',
3415                 'user_type': line['account_type'] == 'cash' and cash_type or bank_type,
3416                 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
3417                 'company_id': company_id,
3418         }
3419
3420     def _create_bank_journals_from_o2m(self, cr, uid, obj_wizard, company_id, acc_template_ref, context=None):
3421         '''
3422         This function creates bank journals and its accounts for each line encoded in the field bank_accounts_id of the
3423         wizard.
3424
3425         :param obj_wizard: the current wizard that generates the COA from the templates.
3426         :param company_id: the id of the company for which the wizard is running.
3427         :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3428             of the accounts that have been generated from them.
3429         :return: True
3430         '''
3431         obj_acc = self.pool.get('account.account')
3432         obj_journal = self.pool.get('account.journal')
3433         code_digits = obj_wizard.code_digits
3434
3435         # Build a list with all the data to process
3436         journal_data = []
3437         if obj_wizard.bank_accounts_id:
3438             for acc in obj_wizard.bank_accounts_id:
3439                 vals = {
3440                     'acc_name': acc.acc_name,
3441                     'account_type': acc.account_type,
3442                     'currency_id': acc.currency_id.id,
3443                 }
3444                 journal_data.append(vals)
3445         ref_acc_bank = obj_wizard.chart_template_id.bank_account_view_id
3446         if journal_data and not ref_acc_bank.code:
3447             raise osv.except_osv(_('Configuration Error !'), _('The bank account defined on the selected chart of accounts hasn\'t a code.'))
3448
3449         current_num = 1
3450         for line in journal_data:
3451             # Seek the next available number for the account code
3452             while True:
3453                 new_code = str(ref_acc_bank.code.ljust(code_digits-len(str(current_num)), '0')) + str(current_num)
3454                 ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)])
3455                 if not ids:
3456                     break
3457                 else:
3458                     current_num += 1
3459             # Create the default debit/credit accounts for this bank journal
3460             vals = self._prepare_bank_account(cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=context)
3461             default_account_id  = obj_acc.create(cr, uid, vals, context=context)
3462
3463             #create the bank journal
3464             vals_journal = self._prepare_bank_journal(cr, uid, line, current_num, default_account_id, company_id, context=context)
3465             obj_journal.create(cr, uid, vals_journal)
3466             current_num += 1
3467         return True
3468
3469 wizard_multi_charts_accounts()
3470
3471 class account_bank_accounts_wizard(osv.osv_memory):
3472     _name='account.bank.accounts.wizard'
3473
3474     _columns = {
3475         'acc_name': fields.char('Account Name.', size=64, required=True),
3476         'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
3477         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
3478         'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
3479     }
3480
3481 account_bank_accounts_wizard()
3482
3483 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: