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