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