[IMP]account-option-wizard
[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', help="Set the account that will be set by default on invoice tax lines for invoices. Leave empty to use the expense account."),
1866         'account_paid_id':fields.many2one('account.account', 'Refund Tax Account', help="Set the account that will be set by default on invoice tax lines for refunds. Leave empty to use the expense account."),
1867         'account_analytic_collected_id':fields.many2one('account.analytic.account', 'Invoice Tax Analytic Account', help="Set the analytic account that will be used by default on the invoice tax lines for invoices. Leave empty if you don't want to use an analytic account on the invoice tax lines by default."),
1868         'account_analytic_paid_id':fields.many2one('account.analytic.account', 'Refund Tax Analytic Account', help="Set the analytic account that will be used by default on the invoice tax lines for refunds. Leave empty if you don't want to use an analytic account on the invoice tax lines by default."),
1869         'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1870         'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1871         'child_depend':fields.boolean('Tax on Children', help="Set if the tax computation is based on the computation of child taxes rather than on the total amount."),
1872         'python_compute':fields.text('Python Code'),
1873         'python_compute_inv':fields.text('Python Code (reverse)'),
1874         'python_applicable':fields.text('Python Code'),
1875
1876         #
1877         # Fields used for the VAT declaration
1878         #
1879         'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1880         'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1881         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1882         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1883
1884         # Same fields for refund invoices
1885
1886         'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1887         'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1888         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1889         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1890         'include_base_amount': fields.boolean('Included in base amount', help="Indicates if the amount of tax must be included in the base amount for the computation of the next taxes"),
1891         'company_id': fields.many2one('res.company', 'Company', required=True),
1892         'description': fields.char('Tax Code',size=32),
1893         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1894         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1895
1896     }
1897     _sql_constraints = [
1898         ('name_company_uniq', 'unique(name, company_id)', 'Tax Name must be unique per company!'),
1899     ]
1900
1901     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1902         """
1903         Returns a list of tupples containing id, name, as internally it is called {def name_get}
1904         result format: {[(id, name), (id, name), ...]}
1905
1906         @param cr: A database cursor
1907         @param user: ID of the user currently logged in
1908         @param name: name to search
1909         @param args: other arguments
1910         @param operator: default operator is 'ilike', it can be changed
1911         @param context: context arguments, like lang, time zone
1912         @param limit: Returns first 'n' ids of complete result, default is 80.
1913
1914         @return: Returns a list of tupples containing id and name
1915         """
1916         if not args:
1917             args = []
1918         if context is None:
1919             context = {}
1920         ids = []
1921         if name:
1922             ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context)
1923             if not ids:
1924                 ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context)
1925         else:
1926             ids = self.search(cr, user, args, limit=limit, context=context or {})
1927         return self.name_get(cr, user, ids, context=context)
1928
1929     def write(self, cr, uid, ids, vals, context=None):
1930         if vals.get('type', False) and vals['type'] in ('none', 'code'):
1931             vals.update({'amount': 0.0})
1932         return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1933
1934     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1935         journal_pool = self.pool.get('account.journal')
1936
1937         if context and context.has_key('type'):
1938             if context.get('type') in ('out_invoice','out_refund'):
1939                 args += [('type_tax_use','in',['sale','all'])]
1940             elif context.get('type') in ('in_invoice','in_refund'):
1941                 args += [('type_tax_use','in',['purchase','all'])]
1942
1943         if context and context.has_key('journal_id'):
1944             journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1945             if journal.type in ('sale', 'purchase'):
1946                 args += [('type_tax_use','in',[journal.type,'all'])]
1947
1948         return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1949
1950     def name_get(self, cr, uid, ids, context=None):
1951         if not ids:
1952             return []
1953         res = []
1954         for record in self.read(cr, uid, ids, ['description','name'], context=context):
1955             name = record['description'] and record['description'] or record['name']
1956             res.append((record['id'],name ))
1957         return res
1958
1959     def _default_company(self, cr, uid, context=None):
1960         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1961         if user.company_id:
1962             return user.company_id.id
1963         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1964
1965     _defaults = {
1966         'python_compute': '''# price_unit\n# or False\n# product: product.product object or None\n# partner: res.partner object or None\n\nresult = price_unit * 0.10''',
1967         'python_compute_inv': '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
1968         'applicable_type': 'true',
1969         'type': 'percent',
1970         'amount': 0,
1971         'price_include': 0,
1972         'active': 1,
1973         'type_tax_use': 'all',
1974         'sequence': 1,
1975         'ref_tax_sign': 1,
1976         'ref_base_sign': 1,
1977         'tax_sign': 1,
1978         'base_sign': 1,
1979         'include_base_amount': False,
1980         'company_id': _default_company,
1981     }
1982     _order = 'sequence'
1983
1984     def _applicable(self, cr, uid, taxes, price_unit, product=None, partner=None):
1985         res = []
1986         for tax in taxes:
1987             if tax.applicable_type=='code':
1988                 localdict = {'price_unit':price_unit, 'product':product, 'partner':partner}
1989                 exec tax.python_applicable in localdict
1990                 if localdict.get('result', False):
1991                     res.append(tax)
1992             else:
1993                 res.append(tax)
1994         return res
1995
1996     def _unit_compute(self, cr, uid, taxes, price_unit, product=None, partner=None, quantity=0):
1997         taxes = self._applicable(cr, uid, taxes, price_unit ,product, partner)
1998         res = []
1999         cur_price_unit=price_unit
2000         for tax in taxes:
2001             # we compute the amount for the current tax object and append it to the result
2002             data = {'id':tax.id,
2003                     'name':tax.description and tax.description + " - " + tax.name or tax.name,
2004                     'account_collected_id':tax.account_collected_id.id,
2005                     'account_paid_id':tax.account_paid_id.id,
2006                     'account_analytic_collected_id': tax.account_analytic_collected_id.id,
2007                     'account_analytic_paid_id': tax.account_analytic_paid_id.id,
2008                     'base_code_id': tax.base_code_id.id,
2009                     'ref_base_code_id': tax.ref_base_code_id.id,
2010                     'sequence': tax.sequence,
2011                     'base_sign': tax.base_sign,
2012                     'tax_sign': tax.tax_sign,
2013                     'ref_base_sign': tax.ref_base_sign,
2014                     'ref_tax_sign': tax.ref_tax_sign,
2015                     'price_unit': cur_price_unit,
2016                     'tax_code_id': tax.tax_code_id.id,
2017                     'ref_tax_code_id': tax.ref_tax_code_id.id,
2018             }
2019             res.append(data)
2020             if tax.type=='percent':
2021                 amount = cur_price_unit * tax.amount
2022                 data['amount'] = amount
2023
2024             elif tax.type=='fixed':
2025                 data['amount'] = tax.amount
2026                 data['tax_amount']=quantity
2027                # data['amount'] = quantity
2028             elif tax.type=='code':
2029                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
2030                 exec tax.python_compute in localdict
2031                 amount = localdict['result']
2032                 data['amount'] = amount
2033             elif tax.type=='balance':
2034                 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2035                 data['balance'] = cur_price_unit
2036
2037             amount2 = data.get('amount', 0.0)
2038             if tax.child_ids:
2039                 if tax.child_depend:
2040                     latest = res.pop()
2041                 amount = amount2
2042                 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, product, partner, quantity)
2043                 res.extend(child_tax)
2044                 if tax.child_depend:
2045                     for r in res:
2046                         for name in ('base','ref_base'):
2047                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2048                                 r[name+'_code_id'] = latest[name+'_code_id']
2049                                 r[name+'_sign'] = latest[name+'_sign']
2050                                 r['price_unit'] = latest['price_unit']
2051                                 latest[name+'_code_id'] = False
2052                         for name in ('tax','ref_tax'):
2053                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
2054                                 r[name+'_code_id'] = latest[name+'_code_id']
2055                                 r[name+'_sign'] = latest[name+'_sign']
2056                                 r['amount'] = data['amount']
2057                                 latest[name+'_code_id'] = False
2058             if tax.include_base_amount:
2059                 cur_price_unit+=amount2
2060         return res
2061
2062     def compute_all(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, force_excluded=False):
2063         """
2064         :param force_excluded: boolean used to say that we don't want to consider the value of field price_include of
2065             tax. It's used in encoding by line where you don't matter if you encoded a tax with that boolean to True or
2066             False
2067         RETURN: {
2068                 'total': 0.0,                # Total without taxes
2069                 'total_included: 0.0,        # Total with taxes
2070                 'taxes': []                  # List of taxes, see compute for the format
2071             }
2072         """
2073
2074         # By default, for each tax, tax amount will first be computed
2075         # and rounded at the 'Account' decimal precision for each
2076         # PO/SO/invoice line and then these rounded amounts will be
2077         # summed, leading to the total amount for that tax. But, if the
2078         # company has tax_calculation_rounding_method = round_globally,
2079         # we still follow the same method, but we use a much larger
2080         # precision when we round the tax amount for each line (we use
2081         # the 'Account' decimal precision + 5), and that way it's like
2082         # rounding after the sum of the tax amounts of each line
2083         precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2084         tax_compute_precision = precision
2085         if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally':
2086             tax_compute_precision += 5
2087         totalin = totalex = round(price_unit * quantity, precision)
2088         tin = []
2089         tex = []
2090         for tax in taxes:
2091             if not tax.price_include or force_excluded:
2092                 tex.append(tax)
2093             else:
2094                 tin.append(tax)
2095         tin = self.compute_inv(cr, uid, tin, price_unit, quantity, product=product, partner=partner, precision=tax_compute_precision)
2096         for r in tin:
2097             totalex -= r.get('amount', 0.0)
2098         totlex_qty = 0.0
2099         try:
2100             totlex_qty = totalex/quantity
2101         except:
2102             pass
2103         tex = self._compute(cr, uid, tex, totlex_qty, quantity, product=product, partner=partner, precision=tax_compute_precision)
2104         for r in tex:
2105             totalin += r.get('amount', 0.0)
2106         return {
2107             'total': totalex,
2108             'total_included': totalin,
2109             'taxes': tin + tex
2110         }
2111
2112     def compute(self, cr, uid, taxes, price_unit, quantity,  product=None, partner=None):
2113         _logger.warning("Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
2114         return self._compute(cr, uid, taxes, price_unit, quantity, product, partner)
2115
2116     def _compute(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
2117         """
2118         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2119
2120         RETURN:
2121             [ tax ]
2122             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2123             one tax for each tax id in IDS and their children
2124         """
2125         if not precision:
2126             precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2127         res = self._unit_compute(cr, uid, taxes, price_unit, product, partner, quantity)
2128         total = 0.0
2129         for r in res:
2130             if r.get('balance',False):
2131                 r['amount'] = round(r.get('balance', 0.0) * quantity, precision) - total
2132             else:
2133                 r['amount'] = round(r.get('amount', 0.0) * quantity, precision)
2134                 total += r['amount']
2135         return res
2136
2137     def _unit_compute_inv(self, cr, uid, taxes, price_unit, product=None, partner=None):
2138         taxes = self._applicable(cr, uid, taxes, price_unit,  product, partner)
2139         res = []
2140         taxes.reverse()
2141         cur_price_unit = price_unit
2142
2143         tax_parent_tot = 0.0
2144         for tax in taxes:
2145             if (tax.type=='percent') and not tax.include_base_amount:
2146                 tax_parent_tot += tax.amount
2147
2148         for tax in taxes:
2149             if (tax.type=='fixed') and not tax.include_base_amount:
2150                 cur_price_unit -= tax.amount
2151
2152         for tax in taxes:
2153             if tax.type=='percent':
2154                 if tax.include_base_amount:
2155                     amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
2156                 else:
2157                     amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
2158
2159             elif tax.type=='fixed':
2160                 amount = tax.amount
2161
2162             elif tax.type=='code':
2163                 localdict = {'price_unit':cur_price_unit, 'product':product, 'partner':partner}
2164                 exec tax.python_compute_inv in localdict
2165                 amount = localdict['result']
2166             elif tax.type=='balance':
2167                 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2168
2169             if tax.include_base_amount:
2170                 cur_price_unit -= amount
2171                 todo = 0
2172             else:
2173                 todo = 1
2174             res.append({
2175                 'id': tax.id,
2176                 'todo': todo,
2177                 'name': tax.name,
2178                 'amount': amount,
2179                 'account_collected_id': tax.account_collected_id.id,
2180                 'account_paid_id': tax.account_paid_id.id,
2181                 'account_analytic_collected_id': tax.account_analytic_collected_id.id,
2182                 'account_analytic_paid_id': tax.account_analytic_paid_id.id,
2183                 'base_code_id': tax.base_code_id.id,
2184                 'ref_base_code_id': tax.ref_base_code_id.id,
2185                 'sequence': tax.sequence,
2186                 'base_sign': tax.base_sign,
2187                 'tax_sign': tax.tax_sign,
2188                 'ref_base_sign': tax.ref_base_sign,
2189                 'ref_tax_sign': tax.ref_tax_sign,
2190                 'price_unit': cur_price_unit,
2191                 'tax_code_id': tax.tax_code_id.id,
2192                 'ref_tax_code_id': tax.ref_tax_code_id.id,
2193             })
2194             if tax.child_ids:
2195                 if tax.child_depend:
2196                     del res[-1]
2197                     amount = price_unit
2198
2199             parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, product, partner)
2200             res.extend(parent_tax)
2201
2202         total = 0.0
2203         for r in res:
2204             if r['todo']:
2205                 total += r['amount']
2206         for r in res:
2207             r['price_unit'] -= total
2208             r['todo'] = 0
2209         return res
2210
2211     def compute_inv(self, cr, uid, taxes, price_unit, quantity, product=None, partner=None, precision=None):
2212         """
2213         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2214         Price Unit is a VAT included price
2215
2216         RETURN:
2217             [ tax ]
2218             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2219             one tax for each tax id in IDS and their children
2220         """
2221         if not precision:
2222             precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
2223         res = self._unit_compute_inv(cr, uid, taxes, price_unit, product, partner=None)
2224         total = 0.0
2225         for r in res:
2226             if r.get('balance',False):
2227                 r['amount'] = round(r['balance'] * quantity, precision) - total
2228             else:
2229                 r['amount'] = round(r['amount'] * quantity, precision)
2230                 total += r['amount']
2231         return res
2232
2233 account_tax()
2234
2235 # ---------------------------------------------------------
2236 # Account Entries Models
2237 # ---------------------------------------------------------
2238
2239 class account_model(osv.osv):
2240     _name = "account.model"
2241     _description = "Account Model"
2242     _columns = {
2243         'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2244         'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2245         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2246         'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2247         'legend': fields.text('Legend', readonly=True, size=100),
2248     }
2249
2250     _defaults = {
2251         'legend': lambda self, cr, uid, context:_('You can specify year, month and date in the name of the model using the following labels:\n\n%(year)s: To Specify Year \n%(month)s: To Specify Month \n%(date)s: Current Date\n\ne.g. My model on %(date)s'),
2252     }
2253     def generate(self, cr, uid, ids, datas={}, context=None):
2254         move_ids = []
2255         entry = {}
2256         account_move_obj = self.pool.get('account.move')
2257         account_move_line_obj = self.pool.get('account.move.line')
2258         pt_obj = self.pool.get('account.payment.term')
2259         period_obj = self.pool.get('account.period')
2260
2261         if context is None:
2262             context = {}
2263
2264         if datas.get('date', False):
2265             context.update({'date': datas['date']})
2266
2267         move_date = context.get('date', time.strftime('%Y-%m-%d'))
2268         move_date = datetime.strptime(move_date,"%Y-%m-%d")
2269         for model in self.browse(cr, uid, ids, context=context):
2270             ctx = context.copy()
2271             ctx.update({'company_id': model.company_id.id})
2272             period_ids = period_obj.find(cr, uid, dt=context.get('date', False), context=ctx)
2273             period_id = period_ids and period_ids[0] or False
2274             ctx.update({'journal_id': model.journal_id.id,'period_id': period_id})
2275             try:
2276                 entry['name'] = model.name%{'year': move_date.strftime('%Y'), 'month': move_date.strftime('%m'), 'date': move_date.strftime('%Y-%m')}
2277             except:
2278                 raise osv.except_osv(_('Wrong model !'), _('You have a wrong expression "%(...)s" in your model !'))
2279             move_id = account_move_obj.create(cr, uid, {
2280                 'ref': entry['name'],
2281                 'period_id': period_id,
2282                 'journal_id': model.journal_id.id,
2283                 'date': context.get('date', fields.date.context_today(self,cr,uid,context=context))
2284             })
2285             move_ids.append(move_id)
2286             for line in model.lines_id:
2287                 analytic_account_id = False
2288                 if line.analytic_account_id:
2289                     if not model.journal_id.analytic_journal_id:
2290                         raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2291                     analytic_account_id = line.analytic_account_id.id
2292                 val = {
2293                     'move_id': move_id,
2294                     'journal_id': model.journal_id.id,
2295                     'period_id': period_id,
2296                     'analytic_account_id': analytic_account_id
2297                 }
2298
2299                 date_maturity = context.get('date',time.strftime('%Y-%m-%d'))
2300                 if line.date_maturity == 'partner':
2301                     if not line.partner_id:
2302                         raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2303                                                                 "\nPlease define partner on it!")%(line.name, model.name))
2304                     if line.partner_id.property_payment_term:
2305                         payment_term_id = line.partner_id.property_payment_term.id
2306                         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2307                         if pterm_list:
2308                             pterm_list = [l[0] for l in pterm_list]
2309                             pterm_list.sort()
2310                             date_maturity = pterm_list[-1]
2311
2312                 val.update({
2313                     'name': line.name,
2314                     'quantity': line.quantity,
2315                     'debit': line.debit,
2316                     'credit': line.credit,
2317                     'account_id': line.account_id.id,
2318                     'move_id': move_id,
2319                     'partner_id': line.partner_id.id,
2320                     'date': context.get('date', fields.date.context_today(self,cr,uid,context=context)),
2321                     'date_maturity': date_maturity
2322                 })
2323                 account_move_line_obj.create(cr, uid, val, context=ctx)
2324
2325         return move_ids
2326
2327 account_model()
2328
2329 class account_model_line(osv.osv):
2330     _name = "account.model.line"
2331     _description = "Account Model Entries"
2332     _columns = {
2333         'name': fields.char('Name', size=64, required=True),
2334         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones."),
2335         'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries."),
2336         'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2337         'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2338         'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2339         'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2340         'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2341         'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2342         'currency_id': fields.many2one('res.currency', 'Currency'),
2343         'partner_id': fields.many2one('res.partner', 'Partner'),
2344         '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."),
2345     }
2346     _order = 'sequence'
2347     _sql_constraints = [
2348         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in model, they must be positive!'),
2349         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model, they must be positive!'),
2350     ]
2351 account_model_line()
2352
2353 # ---------------------------------------------------------
2354 # Account Subscription
2355 # ---------------------------------------------------------
2356
2357
2358 class account_subscription(osv.osv):
2359     _name = "account.subscription"
2360     _description = "Account Subscription"
2361     _columns = {
2362         'name': fields.char('Name', size=64, required=True),
2363         'ref': fields.char('Reference', size=16),
2364         'model_id': fields.many2one('account.model', 'Model', required=True),
2365         'date_start': fields.date('Start Date', required=True),
2366         'period_total': fields.integer('Number of Periods', required=True),
2367         'period_nbr': fields.integer('Period', required=True),
2368         'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2369         'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', required=True, readonly=True),
2370         'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2371     }
2372     _defaults = {
2373         'date_start': fields.date.context_today,
2374         'period_type': 'month',
2375         'period_total': 12,
2376         'period_nbr': 1,
2377         'state': 'draft',
2378     }
2379     def state_draft(self, cr, uid, ids, context=None):
2380         self.write(cr, uid, ids, {'state':'draft'})
2381         return False
2382
2383     def check(self, cr, uid, ids, context=None):
2384         todone = []
2385         for sub in self.browse(cr, uid, ids, context=context):
2386             ok = True
2387             for line in sub.lines_id:
2388                 if not line.move_id.id:
2389                     ok = False
2390                     break
2391             if ok:
2392                 todone.append(sub.id)
2393         if todone:
2394             self.write(cr, uid, todone, {'state':'done'})
2395         return False
2396
2397     def remove_line(self, cr, uid, ids, context=None):
2398         toremove = []
2399         for sub in self.browse(cr, uid, ids, context=context):
2400             for line in sub.lines_id:
2401                 if not line.move_id.id:
2402                     toremove.append(line.id)
2403         if toremove:
2404             self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2405         self.write(cr, uid, ids, {'state':'draft'})
2406         return False
2407
2408     def compute(self, cr, uid, ids, context=None):
2409         for sub in self.browse(cr, uid, ids, context=context):
2410             ds = sub.date_start
2411             for i in range(sub.period_total):
2412                 self.pool.get('account.subscription.line').create(cr, uid, {
2413                     'date': ds,
2414                     'subscription_id': sub.id,
2415                 })
2416                 if sub.period_type=='day':
2417                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2418                 if sub.period_type=='month':
2419                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2420                 if sub.period_type=='year':
2421                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2422         self.write(cr, uid, ids, {'state':'running'})
2423         return True
2424
2425 account_subscription()
2426
2427 class account_subscription_line(osv.osv):
2428     _name = "account.subscription.line"
2429     _description = "Account Subscription Line"
2430     _columns = {
2431         'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2432         'date': fields.date('Date', required=True),
2433         'move_id': fields.many2one('account.move', 'Entry'),
2434     }
2435
2436     def move_create(self, cr, uid, ids, context=None):
2437         tocheck = {}
2438         all_moves = []
2439         obj_model = self.pool.get('account.model')
2440         for line in self.browse(cr, uid, ids, context=context):
2441             datas = {
2442                 'date': line.date,
2443             }
2444             move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2445             tocheck[line.subscription_id.id] = True
2446             self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2447             all_moves.extend(move_ids)
2448         if tocheck:
2449             self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2450         return all_moves
2451
2452     _rec_name = 'date'
2453
2454 account_subscription_line()
2455
2456 #  ---------------------------------------------------------------
2457 #   Account Templates: Account, Tax, Tax Code and chart. + Wizard
2458 #  ---------------------------------------------------------------
2459
2460 class account_tax_template(osv.osv):
2461     _name = 'account.tax.template'
2462 account_tax_template()
2463
2464 class account_account_template(osv.osv):
2465     _order = "code"
2466     _name = "account.account.template"
2467     _description ='Templates for Accounts'
2468
2469     _columns = {
2470         'name': fields.char('Name', size=256, required=True, select=True),
2471         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2472         'code': fields.char('Code', size=64, required=True, select=1),
2473         'type': fields.selection([
2474             ('receivable','Receivable'),
2475             ('payable','Payable'),
2476             ('view','View'),
2477             ('consolidation','Consolidation'),
2478             ('liquidity','Liquidity'),
2479             ('other','Regular'),
2480             ('closed','Closed'),
2481             ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2482             "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2483             "can have children accounts for multi-company consolidations, payable/receivable are for "\
2484             "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2485         'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2486             help="These types are defined according to your country. The type contains more information "\
2487             "about the account and its specificities."),
2488         'financial_report_ids': fields.many2many('account.financial.report', 'account_template_financial_report', 'account_template_id', 'report_line_id', 'Financial Reports'),
2489         'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2490         'shortcut': fields.char('Shortcut', size=12),
2491         'note': fields.text('Note'),
2492         'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2493         'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2494         'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2495         'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2496         '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)."),
2497     }
2498
2499     _defaults = {
2500         'reconcile': False,
2501         'type': 'view',
2502         'nocreate': False,
2503     }
2504
2505     def _check_type(self, cr, uid, ids, context=None):
2506         if context is None:
2507             context = {}
2508         accounts = self.browse(cr, uid, ids, context=context)
2509         for account in accounts:
2510             if account.parent_id and account.parent_id.type != 'view':
2511                 return False
2512         return True
2513
2514     _check_recursion = check_cycle
2515     _constraints = [
2516         (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id']),
2517         (_check_type, 'Configuration Error!\nYou can not define children to an account with internal type different of "View"! ', ['type']),
2518
2519     ]
2520
2521     def name_get(self, cr, uid, ids, context=None):
2522         if not ids:
2523             return []
2524         reads = self.read(cr, uid, ids, ['name','code'], context=context)
2525         res = []
2526         for record in reads:
2527             name = record['name']
2528             if record['code']:
2529                 name = record['code']+' '+name
2530             res.append((record['id'],name ))
2531         return res
2532
2533     def generate_account(self, cr, uid, chart_template_id, tax_template_ref, acc_template_ref, code_digits, company_id, context=None):
2534         """
2535         This method for generating accounts from templates.
2536
2537         :param chart_template_id: id of the chart template chosen in the wizard
2538         :param tax_template_ref: Taxes templates reference for write taxes_id in account_account.
2539         :paramacc_template_ref: dictionary with the mappping between the account templates and the real accounts.
2540         :param code_digits: number of digits got from wizard.multi.charts.accounts, this is use for account code.
2541         :param company_id: company_id selected from wizard.multi.charts.accounts.
2542         :returns: return acc_template_ref for reference purpose.
2543         :rtype: dict
2544         """
2545         if context is None:
2546             context = {}
2547         obj_acc = self.pool.get('account.account')
2548         company_name = self.pool.get('res.company').browse(cr, uid, company_id, context=context).name
2549         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
2550         #deactivate the parent_store functionnality on account_account for rapidity purpose
2551         ctx = context.copy()
2552         ctx.update({'defer_parent_store_computation': True})
2553         level_ref = {}
2554         children_acc_criteria = [('chart_template_id','=', chart_template_id)]
2555         if template.account_root_id.id:
2556             children_acc_criteria = ['|'] + children_acc_criteria + ['&',('parent_id','child_of', [template.account_root_id.id]),('chart_template_id','=', False)]
2557         children_acc_template = self.search(cr, uid, [('nocreate','!=',True)] + children_acc_criteria, order='id')
2558         for account_template in self.browse(cr, uid, children_acc_template, context=context):
2559             # skip the root of COA if it's not the main one
2560             if (template.account_root_id.id == account_template.id) and template.parent_id:
2561                 continue
2562             tax_ids = []
2563             for tax in account_template.tax_ids:
2564                 tax_ids.append(tax_template_ref[tax.id])
2565
2566             code_main = account_template.code and len(account_template.code) or 0
2567             code_acc = account_template.code or ''
2568             if code_main > 0 and code_main <= code_digits and account_template.type != 'view':
2569                 code_acc = str(code_acc) + (str('0'*(code_digits-code_main)))
2570             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
2571             #the level as to be given as well at the creation time, because of the defer_parent_store_computation in
2572             #context. Indeed because of this, the parent_left and parent_right are not computed and thus the child_of
2573             #operator does not return the expected values, with result of having the level field not computed at all.
2574             if parent_id:
2575                 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
2576             else:
2577                 level = 0
2578             vals={
2579                 'name': (template.account_root_id.id == account_template.id) and company_name or account_template.name,
2580                 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2581                 'code': code_acc,
2582                 'type': account_template.type,
2583                 'user_type': account_template.user_type and account_template.user_type.id or False,
2584                 'reconcile': account_template.reconcile,
2585                 'shortcut': account_template.shortcut,
2586                 'note': account_template.note,
2587                 'financial_report_ids': account_template.financial_report_ids and [(6,0,[x.id for x in account_template.financial_report_ids])] or False,
2588                 'parent_id': parent_id,
2589                 'tax_ids': [(6,0,tax_ids)],
2590                 'company_id': company_id,
2591                 'level': level,
2592             }
2593             new_account = obj_acc.create(cr, uid, vals, context=ctx)
2594             acc_template_ref[account_template.id] = new_account
2595             level_ref[new_account] = level
2596
2597         #reactivate the parent_store functionnality on account_account
2598         obj_acc._parent_store_compute(cr)
2599         return acc_template_ref
2600
2601 account_account_template()
2602
2603 class account_add_tmpl_wizard(osv.osv_memory):
2604     """Add one more account from the template.
2605
2606     With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2607     _name = 'account.addtmpl.wizard'
2608
2609     def _get_def_cparent(self, cr, uid, context=None):
2610         acc_obj = self.pool.get('account.account')
2611         tmpl_obj = self.pool.get('account.account.template')
2612         tids = tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2613         if not tids or not tids[0]['parent_id']:
2614             return False
2615         ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2616         res = None
2617         if not ptids or not ptids[0]['code']:
2618             raise osv.except_osv(_('Error !'), _('I can not locate a parent code for the template account!'))
2619             res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2620         return res and res[0] or False
2621
2622     _columns = {
2623         'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2624     }
2625     _defaults = {
2626         'cparent_id': _get_def_cparent,
2627     }
2628
2629     def action_create(self,cr,uid,ids,context=None):
2630         if context is None:
2631             context = {}
2632         acc_obj = self.pool.get('account.account')
2633         tmpl_obj = self.pool.get('account.account.template')
2634         data = self.read(cr, uid, ids)[0]
2635         company_id = acc_obj.read(cr, uid, [data['cparent_id'][0]], ['company_id'])[0]['company_id'][0]
2636         account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2637         vals = {
2638             'name': account_template.name,
2639             'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2640             'code': account_template.code,
2641             'type': account_template.type,
2642             'user_type': account_template.user_type and account_template.user_type.id or False,
2643             'reconcile': account_template.reconcile,
2644             'shortcut': account_template.shortcut,
2645             'note': account_template.note,
2646             'parent_id': data['cparent_id'][0],
2647             'company_id': company_id,
2648             }
2649         acc_obj.create(cr, uid, vals)
2650         return {'type':'state', 'state': 'end' }
2651
2652     def action_cancel(self, cr, uid, ids, context=None):
2653         return { 'type': 'state', 'state': 'end' }
2654
2655 account_add_tmpl_wizard()
2656
2657 class account_tax_code_template(osv.osv):
2658
2659     _name = 'account.tax.code.template'
2660     _description = 'Tax Code Template'
2661     _order = 'code'
2662     _rec_name = 'code'
2663     _columns = {
2664         'name': fields.char('Tax Case Name', size=64, required=True),
2665         'code': fields.char('Case Code', size=64),
2666         'info': fields.text('Description'),
2667         'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2668         'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2669         'sign': fields.float('Sign For Parent', required=True),
2670         '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"),
2671     }
2672
2673     _defaults = {
2674         'sign': 1.0,
2675         'notprintable': False,
2676     }
2677
2678     def generate_tax_code(self, cr, uid, tax_code_root_id, company_id, context=None):
2679         '''
2680         This function generates the tax codes from the templates of tax code that are children of the given one passed
2681         in argument. Then it returns a dictionary with the mappping between the templates and the real objects.
2682
2683         :param tax_code_root_id: id of the root of all the tax code templates to process
2684         :param company_id: id of the company the wizard is running for
2685         :returns: dictionary with the mappping between the templates and the real objects.
2686         :rtype: dict
2687         '''
2688         obj_tax_code_template = self.pool.get('account.tax.code.template')
2689         obj_tax_code = self.pool.get('account.tax.code')
2690         tax_code_template_ref = {}
2691         company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
2692
2693         #find all the children of the tax_code_root_id
2694         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 []
2695         for tax_code_template in obj_tax_code_template.browse(cr, uid, children_tax_code_template, context=context):
2696             vals = {
2697                 'name': (tax_code_root_id == tax_code_template.id) and company.name or tax_code_template.name,
2698                 'code': tax_code_template.code,
2699                 'info': tax_code_template.info,
2700                 '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,
2701                 'company_id': company_id,
2702                 'sign': tax_code_template.sign,
2703             }
2704             #check if this tax code already exists
2705             rec_list = obj_tax_code.search(cr, uid, [('name', '=', vals['name']),('code', '=', vals['code']),('company_id', '=', vals['company_id'])], context=context)
2706             if not rec_list:
2707                 #if not yet, create it
2708                 new_tax_code = obj_tax_code.create(cr, uid, vals)
2709                 #recording the new tax code to do the mapping
2710                 tax_code_template_ref[tax_code_template.id] = new_tax_code
2711         return tax_code_template_ref
2712
2713     def name_get(self, cr, uid, ids, context=None):
2714         if not ids:
2715             return []
2716         if isinstance(ids, (int, long)):
2717             ids = [ids]
2718         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2719         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2720                 for x in reads]
2721
2722     _check_recursion = check_cycle
2723     _constraints = [
2724         (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2725     ]
2726     _order = 'code,name'
2727 account_tax_code_template()
2728
2729
2730 class account_chart_template(osv.osv):
2731     _name="account.chart.template"
2732     _description= "Templates for Account Chart"
2733
2734     _columns={
2735         'name': fields.char('Name', size=64, required=True),
2736         'parent_id': fields.many2one('account.chart.template', 'Parent Chart Template'),
2737         'code_digits': fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"), 
2738         '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."),
2739         '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'),
2740         'account_root_id': fields.many2one('account.account.template', 'Root Account', domain=[('parent_id','=',False)]),
2741         'tax_code_root_id': fields.many2one('account.tax.code.template', 'Root Tax Code', domain=[('parent_id','=',False)]),
2742         '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'),
2743         'bank_account_view_id': fields.many2one('account.account.template', 'Bank Account'),
2744         'property_account_receivable': fields.many2one('account.account.template', 'Receivable Account'),
2745         'property_account_payable': fields.many2one('account.account.template', 'Payable Account'),
2746         'property_account_expense_categ': fields.many2one('account.account.template', 'Expense Category Account'),
2747         'property_account_income_categ': fields.many2one('account.account.template', 'Income Category Account'),
2748         'property_account_expense': fields.many2one('account.account.template', 'Expense Account on Product Template'),
2749         'property_account_income': fields.many2one('account.account.template', 'Income Account on Product Template'),
2750         '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'),
2751         'property_account_income_opening': fields.many2one('account.account.template', 'Opening Entries Income Account'),
2752         'property_account_expense_opening': fields.many2one('account.account.template', 'Opening Entries Expense Account'),
2753     }
2754
2755     _defaults = {
2756         'visible': True,
2757         'code_digits': 6,
2758         'complete_tax_set': True,
2759     }
2760
2761 account_chart_template()
2762
2763 class account_tax_template(osv.osv):
2764
2765     _name = 'account.tax.template'
2766     _description = 'Templates for Taxes'
2767
2768     _columns = {
2769         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2770         'name': fields.char('Tax Name', size=64, required=True),
2771         '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."),
2772         'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2773         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True),
2774         '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."),
2775         '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."),
2776         'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2777         'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2778         'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2779         '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."),
2780         'python_compute':fields.text('Python Code'),
2781         'python_compute_inv':fields.text('Python Code (reverse)'),
2782         'python_applicable':fields.text('Python Code'),
2783
2784         #
2785         # Fields used for the VAT declaration
2786         #
2787         'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2788         'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2789         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2790         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2791
2792         # Same fields for refund invoices
2793
2794         'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2795         'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2796         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2797         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2798         '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."),
2799         'description': fields.char('Internal Name', size=32),
2800         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,),
2801         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
2802     }
2803
2804     def name_get(self, cr, uid, ids, context=None):
2805         if not ids:
2806             return []
2807         res = []
2808         for record in self.read(cr, uid, ids, ['description','name'], context=context):
2809             name = record['description'] and record['description'] or record['name']
2810             res.append((record['id'],name ))
2811         return res
2812
2813     def _default_company(self, cr, uid, context=None):
2814         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2815         if user.company_id:
2816             return user.company_id.id
2817         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2818
2819     _defaults = {
2820         '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''',
2821         'python_compute_inv': lambda *a: '''# price_unit\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
2822         'applicable_type': 'true',
2823         'type': 'percent',
2824         'amount': 0,
2825         'sequence': 1,
2826         'ref_tax_sign': 1,
2827         'ref_base_sign': 1,
2828         'tax_sign': 1,
2829         'base_sign': 1,
2830         'include_base_amount': False,
2831         'type_tax_use': 'all',
2832         'price_include': 0,
2833     }
2834     _order = 'sequence'
2835
2836     def _generate_tax(self, cr, uid, tax_templates, tax_code_template_ref, company_id, context=None):
2837         """
2838         This method generate taxes from templates.
2839
2840         :param tax_templates: list of browse record of the tax templates to process
2841         :param tax_code_template_ref: Taxcode templates reference.
2842         :param company_id: id of the company the wizard is running for
2843         :returns:
2844             {
2845             'tax_template_to_tax': mapping between tax template and the newly generated taxes corresponding,
2846             'account_dict': dictionary containing a to-do list with all the accounts to assign on new taxes
2847             }
2848         """
2849         if context is None:
2850             context = {}
2851         res = {}
2852         todo_dict = {}
2853         tax_template_to_tax = {}
2854         for tax in tax_templates:
2855             vals_tax = {
2856                 'name':tax.name,
2857                 'sequence': tax.sequence,
2858                 'amount': tax.amount,
2859                 'type': tax.type,
2860                 'applicable_type': tax.applicable_type,
2861                 'domain': tax.domain,
2862                 '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,
2863                 'child_depend': tax.child_depend,
2864                 'python_compute': tax.python_compute,
2865                 'python_compute_inv': tax.python_compute_inv,
2866                 'python_applicable': tax.python_applicable,
2867                 '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,
2868                 '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,
2869                 'base_sign': tax.base_sign,
2870                 'tax_sign': tax.tax_sign,
2871                 '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,
2872                 '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,
2873                 'ref_base_sign': tax.ref_base_sign,
2874                 'ref_tax_sign': tax.ref_tax_sign,
2875                 'include_base_amount': tax.include_base_amount,
2876                 'description': tax.description,
2877                 'company_id': company_id,
2878                 'type_tax_use': tax.type_tax_use,
2879                 'price_include': tax.price_include
2880             }
2881             new_tax = self.pool.get('account.tax').create(cr, uid, vals_tax)
2882             tax_template_to_tax[tax.id] = new_tax
2883             #as the accounts have not been created yet, we have to wait before filling these fields
2884             todo_dict[new_tax] = {
2885                 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2886                 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2887             }
2888         res.update({'tax_template_to_tax': tax_template_to_tax, 'account_dict': todo_dict})
2889         return res
2890
2891 account_tax_template()
2892
2893 # Fiscal Position Templates
2894
2895 class account_fiscal_position_template(osv.osv):
2896     _name = 'account.fiscal.position.template'
2897     _description = 'Template for Fiscal Position'
2898
2899     _columns = {
2900         'name': fields.char('Fiscal Position Template', size=64, required=True),
2901         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2902         'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2903         'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping'),
2904         'note': fields.text('Notes', translate=True),
2905     }
2906
2907     def generate_fiscal_position(self, cr, uid, chart_temp_id, tax_template_ref, acc_template_ref, company_id, context=None):
2908         """
2909         This method generate Fiscal Position, Fiscal Position Accounts and Fiscal Position Taxes from templates.
2910
2911         :param chart_temp_id: Chart Template Id.
2912         :param taxes_ids: Taxes templates reference for generating account.fiscal.position.tax.
2913         :param acc_template_ref: Account templates reference for generating account.fiscal.position.account.
2914         :param company_id: company_id selected from wizard.multi.charts.accounts.
2915         :returns: True
2916         """
2917         if context is None:
2918             context = {}
2919         obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2920         obj_ac_fp = self.pool.get('account.fiscal.position.account')
2921         obj_fiscal_position = self.pool.get('account.fiscal.position')
2922         fp_ids = self.search(cr, uid, [('chart_template_id', '=', chart_temp_id)])
2923         for position in self.browse(cr, uid, fp_ids, context=context):
2924             new_fp = obj_fiscal_position.create(cr, uid, {'company_id': company_id, 'name': position.name, 'note': position.note})
2925             for tax in position.tax_ids:
2926                 obj_tax_fp.create(cr, uid, {
2927                     'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2928                     'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2929                     'position_id': new_fp
2930                 })
2931             for acc in position.account_ids:
2932                 obj_ac_fp.create(cr, uid, {
2933                     'account_src_id': acc_template_ref[acc.account_src_id.id],
2934                     'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2935                     'position_id': new_fp
2936                 })
2937         return True
2938
2939 account_fiscal_position_template()
2940
2941 class account_fiscal_position_tax_template(osv.osv):
2942     _name = 'account.fiscal.position.tax.template'
2943     _description = 'Template Tax Fiscal Position'
2944     _rec_name = 'position_id'
2945
2946     _columns = {
2947         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2948         'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2949         'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2950     }
2951
2952 account_fiscal_position_tax_template()
2953
2954 class account_fiscal_position_account_template(osv.osv):
2955     _name = 'account.fiscal.position.account.template'
2956     _description = 'Template Account Fiscal Mapping'
2957     _rec_name = 'position_id'
2958     _columns = {
2959         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2960         'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2961         'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2962     }
2963
2964 account_fiscal_position_account_template()
2965
2966 # ---------------------------------------------------------
2967 # Account generation from template wizards
2968 # ---------------------------------------------------------
2969
2970 class wizard_multi_charts_accounts(osv.osv_memory):
2971     """
2972     Create a new account chart for a company.
2973     Wizards ask for:
2974         * a company
2975         * an account chart template
2976         * a number of digits for formatting code of non-view accounts
2977         * a list of bank accounts owned by the company
2978     Then, the wizard:
2979         * generates all accounts from the template and assigns them to the right company
2980         * generates all taxes and tax codes, changing account assignations
2981         * generates all accounting properties and assigns them correctly
2982     """
2983     _name='wizard.multi.charts.accounts'
2984     _inherit = 'res.config'
2985
2986     _columns = {
2987         'company_id':fields.many2one('res.company', 'Company', required=True),
2988         'currency_id': fields.many2one('res.currency', 'Currency', help="Currency as per company's country."),
2989         'only_one_chart_template': fields.boolean('Only One Chart Template Available'),
2990         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2991         'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Cash and Banks', required=True),
2992         'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2993         "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
2994         "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
2995         'sale_tax_rate': fields.float('Sales Tax(%)'),
2996         'purchase_tax_rate': fields.float('Purchase Tax(%)'),
2997         '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'),
2998     }
2999     def _default_company(self, cr, uid, context=None):
3000         user_company = self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0].company_id.id
3001         return user_company or False
3002
3003     def _default_currency(self, cr, uid, context=None):
3004         company_id=self._default_company(cr, uid, context=context)
3005         currency= self.pool.get('res.company').browse(cr, uid, company_id, context=context)
3006         return currency.currency_id and currency.currency_id.id or False
3007
3008     _defaults = {
3009         'company_id': _default_company,
3010         'currency_id':_default_currency,
3011     }
3012
3013     def onchange_company_id(self, cr, uid, ids, company_id):
3014         if company_id:
3015             company = self.pool.get('res.company').browse(cr, uid, company_id)
3016             return {'value':{'currency_id':company.currency_id and company.currency_id.id}}
3017         else :
3018             return False
3019
3020     def onchange_tax_rate(self, cr, uid, ids, rate=False, context=None):
3021         return {'value': {'purchase_tax_rate': rate or False}}
3022
3023     def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
3024         res = {}
3025         tax_templ_obj = self.pool.get('account.tax.template')
3026         res['value'] = {'complete_tax_set': False, 'sale_tax': False, 'purchase_tax': False}
3027         if chart_template_id:
3028             data = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3029             res['value'].update({'complete_tax_set': data.complete_tax_set})
3030             if data.complete_tax_set:
3031             # 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
3032                 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3033                                               , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence, id desc")
3034                 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3035                                               , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence, id desc")
3036                 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})
3037
3038             if data.code_digits:
3039                res['value'].update({'code_digits': data.code_digits})
3040         return res
3041
3042     def default_get(self, cr, uid, fields, context=None):
3043         res = super(wizard_multi_charts_accounts, self).default_get(cr, uid, fields, context=context) 
3044         tax_templ_obj = self.pool.get('account.tax.template')
3045
3046         if 'bank_accounts_id' in fields:
3047             res.update({'bank_accounts_id': [{'acc_name': _('Cash'), 'account_type': 'cash'},{'acc_name': _('Bank'), 'account_type': 'bank'}]})
3048         if 'company_id' in fields:
3049             res.update({'company_id': self.pool.get('res.users').browse(cr, uid, [uid], context=context)[0].company_id.id})
3050
3051         ids = self.pool.get('account.chart.template').search(cr, uid, [('visible', '=', True)], context=context)
3052         if ids:
3053             if 'chart_template_id' in fields:
3054                 res.update({'only_one_chart_template': len(ids) == 1, 'chart_template_id': ids[0]})
3055             if 'sale_tax' in fields:
3056                 sale_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3057                                               , "=", ids[0]), ('type_tax_use', 'in', ('sale','all'))], order="sequence")
3058                 res.update({'sale_tax': sale_tax_ids and sale_tax_ids[0] or False})
3059             if 'purchase_tax' in fields:
3060                 purchase_tax_ids = tax_templ_obj.search(cr, uid, [("chart_template_id"
3061                                           , "=", ids[0]), ('type_tax_use', 'in', ('purchase','all'))], order="sequence")
3062                 res.update({'purchase_tax': purchase_tax_ids and purchase_tax_ids[0] or False})
3063         res.update({
3064             'purchase_tax_rate': 15.0,
3065             'sale_tax_rate': 15.0,
3066         })
3067         return res
3068
3069     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
3070         if context is None:context = {}
3071         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)
3072         cmp_select = []
3073         acc_template_obj = self.pool.get('account.chart.template')
3074         company_obj = self.pool.get('res.company')
3075
3076         company_ids = company_obj.search(cr, uid, [], context=context)
3077         #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)
3078         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",))
3079         configured_cmp = [r[0] for r in cr.fetchall()]
3080         unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
3081         for field in res['fields']:
3082             if field == 'company_id':
3083                 res['fields'][field]['domain'] = [('id','in',unconfigured_cmp)]
3084                 res['fields'][field]['selection'] = [('', '')]
3085                 if unconfigured_cmp:
3086                     cmp_select = [(line.id, line.name) for line in company_obj.browse(cr, uid, unconfigured_cmp)]
3087                     res['fields'][field]['selection'] = cmp_select
3088         return res
3089
3090     def check_created_journals(self, cr, uid, vals_journal, company_id, context=None):
3091         """
3092         This method used for checking journals already created or not. If not then create new journal.
3093         """
3094         obj_journal = self.pool.get('account.journal')
3095         rec_list = obj_journal.search(cr, uid, [('name','=', vals_journal['name']),('company_id', '=', company_id)], context=context)
3096         if not rec_list:
3097             obj_journal.create(cr, uid, vals_journal, context=context)
3098         return True
3099
3100     def generate_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3101         """
3102         This method is used for creating journals.
3103
3104         :param chart_temp_id: Chart Template Id.
3105         :param acc_template_ref: Account templates reference.
3106         :param company_id: company_id selected from wizard.multi.charts.accounts.
3107         :returns: True
3108         """
3109         journal_data = self._prepare_all_journals(cr, uid, chart_template_id, acc_template_ref, company_id, context=context)
3110         for vals_journal in journal_data:
3111             self.check_created_journals(cr, uid, vals_journal, company_id, context=context)
3112         return True
3113
3114     def _prepare_all_journals(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3115         def _get_analytic_journal(journal_type):
3116             # Get the analytic journal
3117             data = False
3118             if journal_type in ('sale', 'sale_refund'):
3119                 data = obj_data.get_object_reference(cr, uid, 'account', 'analytic_journal_sale') 
3120             elif journal_type in ('purchase', 'purchase_refund'):
3121                 pass
3122             elif journal_type == 'general':
3123                 pass
3124             return data and data[1] or False
3125
3126         def _get_default_account(journal_type, type='debit'):
3127             # Get the default accounts
3128             default_account = False
3129             if journal_type in ('sale', 'sale_refund'):
3130                 default_account = acc_template_ref.get(template.property_account_income_categ.id)
3131             elif journal_type in ('purchase', 'purchase_refund'):
3132                 default_account = acc_template_ref.get(template.property_account_expense_categ.id)
3133             elif journal_type == 'situation':
3134                 if type == 'debit':
3135                     default_account = acc_template_ref.get(template.property_account_expense_opening.id)
3136                 else:
3137                     default_account = acc_template_ref.get(template.property_account_income_opening.id)
3138             return default_account
3139
3140         def _get_view_id(journal_type):
3141             # Get the journal views
3142             if journal_type in ('general', 'situation'):
3143                 data = obj_data.get_object_reference(cr, uid, 'account', 'account_journal_view')
3144             elif journal_type in ('sale_refund', 'purchase_refund'):
3145                 data = obj_data.get_object_reference(cr, uid, 'account', 'account_sp_refund_journal_view') 
3146             else:
3147                 data = obj_data.get_object_reference(cr, uid, 'account', 'account_sp_journal_view')
3148             return data and data[1] or False
3149
3150         journal_names = {
3151             'sale': _('Sales Journal'),
3152             'purchase': _('Purchase Journal'),
3153             'sale_refund': _('Sales Refund Journal'),
3154             'purchase_refund': _('Purchase Refund Journal'),
3155             'general': _('Miscellaneous Journal'),
3156             'situation': _('Opening Entries Journal'),
3157         }
3158         journal_codes = {
3159             'sale': _('SAJ'),
3160             'purchase': _('EXJ'),
3161             'sale_refund': _('SCNJ'),
3162             'purchase_refund': _('ECNJ'),
3163             'general': _('MISC'),
3164             'situation': _('OPEJ'),
3165         }
3166
3167         obj_data = self.pool.get('ir.model.data')
3168         analytic_journal_obj = self.pool.get('account.analytic.journal')
3169         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3170
3171         journal_data = []
3172         for journal_type in ['sale', 'purchase', 'sale_refund', 'purchase_refund', 'general', 'situation']:
3173             vals = {
3174                 'type': journal_type,
3175                 'name': journal_names[journal_type],
3176                 'code': journal_codes[journal_type],
3177                 'company_id': company_id,
3178                 'centralisation': journal_type == 'situation',
3179                 'view_id': _get_view_id(journal_type),
3180                 'analytic_journal_id': _get_analytic_journal(journal_type),
3181                 'default_credit_account_id': _get_default_account(journal_type, 'credit'),
3182                 'default_debit_account_id': _get_default_account(journal_type, 'debit'),
3183             }
3184             journal_data.append(vals)
3185         return journal_data
3186
3187     def generate_properties(self, cr, uid, chart_template_id, acc_template_ref, company_id, context=None):
3188         """
3189         This method used for creating properties.
3190
3191         :param chart_template_id: id of the current chart template for which we need to create properties
3192         :param acc_template_ref: Mapping between ids of account templates and real accounts created from them
3193         :param company_id: company_id selected from wizard.multi.charts.accounts.
3194         :returns: True
3195         """
3196         property_obj = self.pool.get('ir.property')
3197         field_obj = self.pool.get('ir.model.fields')
3198         todo_list = [
3199             ('property_account_receivable','res.partner','account.account'),
3200             ('property_account_payable','res.partner','account.account'),
3201             ('property_account_expense_categ','product.category','account.account'),
3202             ('property_account_income_categ','product.category','account.account'),
3203             ('property_account_expense','product.template','account.account'),
3204             ('property_account_income','product.template','account.account'),
3205             ('property_reserve_and_surplus_account','res.company','account.account')
3206         ]
3207         template = self.pool.get('account.chart.template').browse(cr, uid, chart_template_id, context=context)
3208         for record in todo_list:
3209             account = getattr(template, record[0])
3210             value = account and 'account.account,' + str(acc_template_ref[account.id]) or False
3211             if value:
3212                 field = field_obj.search(cr, uid, [('name', '=', record[0]),('model', '=', record[1]),('relation', '=', record[2])], context=context)
3213                 vals = {
3214                     'name': record[0],
3215                     'company_id': company_id,
3216                     'fields_id': field[0],
3217                     'value': value,
3218                 }
3219                 property_ids = property_obj.search(cr, uid, [('name','=', record[0]),('company_id', '=', company_id)], context=context)
3220                 if property_ids:
3221                     #the property exist: modify it
3222                     property_obj.write(cr, uid, property_ids, vals, context=context)
3223                 else:
3224                     #create the property
3225                     property_obj.create(cr, uid, vals, context=context)
3226         return True
3227
3228     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):
3229         '''
3230         This function recursively loads the template objects and create the real objects from them.
3231
3232         :param template_id: id of the chart template to load
3233         :param company_id: id of the company the wizard is running for
3234         :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3235         :param obj_wizard: the current wizard for generating the COA from the templates
3236         :param acc_ref: Mapping between ids of account templates and real accounts created from them
3237         :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3238         :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3239         :returns: return a tuple with a dictionary containing
3240             * the mapping between the account template ids and the ids of the real accounts that have been generated
3241               from them, as first item,
3242             * a similar dictionary for mapping the tax templates and taxes, as second item,
3243             * a last identical containing the mapping of tax code templates and tax codes
3244         :rtype: tuple(dict, dict, dict)
3245         '''
3246         template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3247         if template.parent_id:
3248             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)
3249             acc_ref.update(tmp1)
3250             taxes_ref.update(tmp2)
3251             tax_code_ref.update(tmp3)
3252         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)
3253         acc_ref.update(tmp1)
3254         taxes_ref.update(tmp2)
3255         tax_code_ref.update(tmp3)
3256         return acc_ref, taxes_ref, tax_code_ref
3257
3258     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):
3259         '''
3260         This function generates all the objects from the templates
3261
3262         :param template_id: id of the chart template to load
3263         :param company_id: id of the company the wizard is running for
3264         :param code_digits: integer that depicts the number of digits the accounts code should have in the COA
3265         :param obj_wizard: the current wizard for generating the COA from the templates
3266         :param acc_ref: Mapping between ids of account templates and real accounts created from them
3267         :param taxes_ref: Mapping between ids of tax templates and real taxes created from them
3268         :param tax_code_ref: Mapping between ids of tax code templates and real tax codes created from them
3269         :returns: return a tuple with a dictionary containing
3270             * the mapping between the account template ids and the ids of the real accounts that have been generated
3271               from them, as first item,
3272             * a similar dictionary for mapping the tax templates and taxes, as second item,
3273             * a last identical containing the mapping of tax code templates and tax codes
3274         :rtype: tuple(dict, dict, dict)
3275         '''
3276         template = self.pool.get('account.chart.template').browse(cr, uid, template_id, context=context)
3277         obj_tax_code_template = self.pool.get('account.tax.code.template')
3278         obj_acc_tax = self.pool.get('account.tax')
3279         obj_tax_temp = self.pool.get('account.tax.template')
3280         obj_acc_template = self.pool.get('account.account.template')
3281         obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
3282
3283         # create all the tax code.
3284         tax_code_ref.update(obj_tax_code_template.generate_tax_code(cr, uid, template.tax_code_root_id.id, company_id, context=context))
3285
3286         # Generate taxes from templates.
3287         tax_templates = [x for x in template.tax_template_ids]
3288         generated_tax_res = obj_tax_temp._generate_tax(cr, uid, tax_templates, tax_code_ref, company_id, context=context)
3289         taxes_ref.update(generated_tax_res['tax_template_to_tax'])
3290
3291         # Generating Accounts from templates.
3292         account_template_ref = obj_acc_template.generate_account(cr, uid, template_id, taxes_ref, account_ref, code_digits, company_id, context=context)
3293         account_ref.update(account_template_ref)
3294
3295         # writing account values on tax after creation of accounts
3296         for key,value in generated_tax_res['account_dict'].items():
3297             if value['account_collected_id'] or value['account_paid_id']:
3298                 obj_acc_tax.write(cr, uid, [key], {
3299                     'account_collected_id': account_ref.get(value['account_collected_id'], False),
3300                     'account_paid_id': account_ref.get(value['account_paid_id'], False),
3301                 })
3302
3303         # Create Journals
3304         self.generate_journals(cr, uid, template_id, account_ref, company_id, context=context)
3305
3306         # generate properties function
3307         self.generate_properties(cr, uid, template_id, account_ref, company_id, context=context)
3308
3309         # Generate Fiscal Position , Fiscal Position Accounts and Fiscal Position Taxes from templates
3310         obj_fiscal_position_template.generate_fiscal_position(cr, uid, template_id, taxes_ref, account_ref, company_id, context=context)
3311
3312         return account_ref, taxes_ref, tax_code_ref
3313
3314     def _create_tax_templates_from_rates(self, cr, uid, obj_wizard, company_id, context=None):
3315         '''
3316         This function checks if the chosen chart template is configured as containing a full set of taxes, and if
3317         it's not the case, it creates the templates for account.tax.code and for account.account.tax objects accordingly
3318         to the provided sale/purchase rates. Then it saves the new tax templates as default taxes to use for this chart
3319         template.
3320
3321         :param obj_wizard: browse record of wizard to generate COA from templates
3322         :param company_id: id of the company for wich the wizard is running
3323         :return: True
3324         '''
3325         obj_tax_code_template = self.pool.get('account.tax.code.template')
3326         obj_tax_temp = self.pool.get('account.tax.template')
3327         chart_template = obj_wizard.chart_template_id
3328         vals = {}
3329         # get the ids of all the parents of the selected account chart template
3330         current_chart_template = chart_template
3331         all_parents = [current_chart_template.id]
3332         while current_chart_template.parent_id:
3333             current_chart_template = current_chart_template.parent_id
3334             all_parents.append(current_chart_template.id)
3335         # create tax templates and tax code templates from purchase_tax_rate and sale_tax_rate fields
3336         if not chart_template.complete_tax_set:
3337             value = obj_wizard.sale_tax_rate
3338             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)
3339             obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Tax %.2f%%') % value})
3340             value = obj_wizard.purchase_tax_rate
3341             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)
3342             obj_tax_temp.write(cr, uid, ref_tax_ids, {'amount': value/100.0, 'name': _('Purchase Tax %.2f%%') % value})
3343         return True
3344
3345     def execute(self, cr, uid, ids, context=None):
3346         '''
3347         This function is called at the confirmation of the wizard to generate the COA from the templates. It will read
3348         all the provided information to create the accounts, the banks, the journals, the taxes, the tax codes, the
3349         accounting properties... accordingly for the chosen company.
3350         '''
3351         ir_values_obj = self.pool.get('ir.values')
3352         obj_wizard = self.browse(cr, uid, ids[0])
3353         company_id = obj_wizard.company_id.id
3354         # If the floats for sale/purchase rates have been filled, create templates from them
3355         self._create_tax_templates_from_rates(cr, uid, obj_wizard, company_id, context=context)
3356
3357         # Install all the templates objects and generate the real objects
3358         acc_template_ref, taxes_ref, tax_code_ref = self._install_template(cr, uid, obj_wizard.chart_template_id.id, company_id, code_digits=obj_wizard.code_digits, obj_wizard=obj_wizard, context=context)
3359
3360         # write values of default taxes for product
3361         if obj_wizard.sale_tax and taxes_ref:
3362             ir_values_obj.set(cr, uid, key='default', key2=False, name="taxes_id", company=company_id,
3363                                 models =[('product.product',False)], value=[taxes_ref[obj_wizard.sale_tax.id]])
3364         if obj_wizard.purchase_tax and taxes_ref:
3365                 ir_values_obj.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=company_id,
3366                                 models =[('product.product',False)], value=[taxes_ref[obj_wizard.purchase_tax.id]])
3367
3368         # Create Bank journals
3369         self._create_bank_journals_from_o2m(cr, uid, obj_wizard, company_id, acc_template_ref, context=context)
3370         action = {
3371             'type': 'ir.actions.act_window',
3372             'view_type': 'form',
3373             'view_mode': 'form',
3374             'res_model': 'board.board',
3375             'view_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'board_account_form')[1],
3376             'menu_id': self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'menu_finance')[1]
3377         }
3378         return action
3379
3380     def _prepare_bank_journal(self, cr, uid, line, current_num, default_account_id, company_id, context=None):
3381         '''
3382         This function prepares the value to use for the creation of a bank journal created through the wizard of 
3383         generating COA from templates.
3384
3385         :param line: dictionary containing the values encoded by the user related to his bank account
3386         :param current_num: integer corresponding to a counter of the already created bank journals through this wizard.
3387         :param default_account_id: id of the default debit.credit account created before for this journal.
3388         :param company_id: id of the company for which the wizard is running
3389         :return: mapping of field names and values
3390         :rtype: dict
3391         '''
3392         obj_data = self.pool.get('ir.model.data')
3393         obj_journal = self.pool.get('account.journal')
3394         # Get the id of journal views
3395         tmp = obj_data.get_object_reference(cr, uid, 'account', 'account_journal_bank_view_multi')
3396         view_id_cur = tmp and tmp[1] or False
3397         tmp = obj_data.get_object_reference(cr, uid, 'account', 'account_journal_bank_view')
3398         view_id_cash = tmp and tmp[1] or False
3399
3400         # we need to loop again to find next number for journal code 
3401         # because we can't rely on the value current_num as,
3402         # its possible that we already have bank journals created (e.g. by the creation of res.partner.bank) 
3403         # and the next number for account code might have been already used before for journal
3404         for num in xrange(current_num, 100):
3405             # journal_code has a maximal size of 5, hence we can enforce the boundary num < 100
3406             journal_code = _('BNK')[:3] + str(num)
3407             ids = obj_journal.search(cr, uid, [('code', '=', journal_code), ('company_id', '=', company_id)], context=context)
3408             if not ids:
3409                 break
3410         else:
3411             raise osv.except_osv(_('Error'), _('Cannot generate an unused journal code.'))
3412
3413         vals = {
3414                 'name': line['acc_name'],
3415                 'code': journal_code,
3416                 'type': line['account_type'] == 'cash' and 'cash' or 'bank',
3417                 'company_id': company_id,
3418                 'analytic_journal_id': False,
3419                 'currency': False,
3420                 'default_credit_account_id': default_account_id,
3421                 'default_debit_account_id': default_account_id,
3422         }
3423         if line['currency_id']:
3424             vals['view_id'] = view_id_cur
3425             vals['currency'] = line['currency_id']
3426         else:
3427             vals['view_id'] = view_id_cash
3428         return vals
3429
3430     def _prepare_bank_account(self, cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=None):
3431         '''
3432         This function prepares the value to use for the creation of the default debit and credit accounts of a
3433         bank journal created through the wizard of generating COA from templates.
3434
3435         :param line: dictionary containing the values encoded by the user related to his bank account
3436         :param new_code: integer corresponding to the next available number to use as account code
3437         :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3438             of the accounts that have been generated from them.
3439         :param ref_acc_bank: browse record of the account template set as root of all bank accounts for the chosen
3440             template
3441         :param company_id: id of the company for which the wizard is running
3442         :return: mapping of field names and values
3443         :rtype: dict
3444         '''
3445         obj_data = self.pool.get('ir.model.data')
3446
3447         # Get the id of the user types fr-or cash and bank
3448         tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_cash')
3449         cash_type = tmp and tmp[1] or False
3450         tmp = obj_data.get_object_reference(cr, uid, 'account', 'data_account_type_bank')
3451         bank_type = tmp and tmp[1] or False
3452         return {
3453                 'name': line['acc_name'],
3454                 'currency_id': line['currency_id'],
3455                 'code': new_code,
3456                 'type': 'liquidity',
3457                 'user_type': line['account_type'] == 'cash' and cash_type or bank_type,
3458                 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
3459                 'company_id': company_id,
3460         }
3461
3462     def _create_bank_journals_from_o2m(self, cr, uid, obj_wizard, company_id, acc_template_ref, context=None):
3463         '''
3464         This function creates bank journals and its accounts for each line encoded in the field bank_accounts_id of the
3465         wizard.
3466
3467         :param obj_wizard: the current wizard that generates the COA from the templates.
3468         :param company_id: the id of the company for which the wizard is running.
3469         :param acc_template_ref: the dictionary containing the mapping between the ids of account templates and the ids
3470             of the accounts that have been generated from them.
3471         :return: True
3472         '''
3473         obj_acc = self.pool.get('account.account')
3474         obj_journal = self.pool.get('account.journal')
3475         code_digits = obj_wizard.code_digits
3476
3477         # Build a list with all the data to process
3478         journal_data = []
3479         if obj_wizard.bank_accounts_id:
3480             for acc in obj_wizard.bank_accounts_id:
3481                 vals = {
3482                     'acc_name': acc.acc_name,
3483                     'account_type': acc.account_type,
3484                     'currency_id': acc.currency_id.id,
3485                 }
3486                 journal_data.append(vals)
3487         ref_acc_bank = obj_wizard.chart_template_id.bank_account_view_id
3488         if journal_data and not ref_acc_bank.code:
3489             raise osv.except_osv(_('Configuration Error !'), _('The bank account defined on the selected chart of accounts hasn\'t a code.'))
3490
3491         current_num = 1
3492         for line in journal_data:
3493             # Seek the next available number for the account code
3494             while True:
3495                 new_code = str(ref_acc_bank.code.ljust(code_digits-len(str(current_num)), '0')) + str(current_num)
3496                 ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)])
3497                 if not ids:
3498                     break
3499                 else:
3500                     current_num += 1
3501             # Create the default debit/credit accounts for this bank journal
3502             vals = self._prepare_bank_account(cr, uid, line, new_code, acc_template_ref, ref_acc_bank, company_id, context=context)
3503             default_account_id  = obj_acc.create(cr, uid, vals, context=context)
3504
3505             #create the bank journal
3506             vals_journal = self._prepare_bank_journal(cr, uid, line, current_num, default_account_id, company_id, context=context)
3507             obj_journal.create(cr, uid, vals_journal)
3508             current_num += 1
3509         return True
3510
3511 wizard_multi_charts_accounts()
3512
3513 class account_bank_accounts_wizard(osv.osv_memory):
3514     _name='account.bank.accounts.wizard'
3515
3516     _columns = {
3517         'acc_name': fields.char('Account Name.', size=64, required=True),
3518         'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
3519         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
3520         'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
3521     }
3522
3523 account_bank_accounts_wizard()
3524
3525 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: