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