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