[MERGE] merge with main branch
[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 import time
22 from datetime import datetime
23 from dateutil.relativedelta import relativedelta
24 from operator import itemgetter
25
26 import netsvc
27 import pooler
28 from osv import fields, osv
29 import decimal_precision as dp
30 from tools.misc import currency
31 from tools.translate import _
32 from tools import config
33
34 def check_cycle(self, cr, uid, ids):
35     """ climbs the ``self._table.parent_id`` chains for 100 levels or
36     until it can't find any more parent(s)
37
38     Returns true if it runs out of parents (no cycle), false if
39     it can recurse 100 times without ending all chains
40     """
41     level = 100
42     while len(ids):
43         cr.execute('SELECT DISTINCT parent_id '\
44                     'FROM '+self._table+' '\
45                     'WHERE id IN %s '\
46                     'AND parent_id IS NOT NULL',(tuple(ids),))
47         ids = map(itemgetter(0), cr.fetchall())
48         if not level:
49             return False
50         level -= 1
51     return True
52
53 class account_payment_term(osv.osv):
54     _name = "account.payment.term"
55     _description = "Payment Term"
56     _columns = {
57         'name': fields.char('Payment Term', size=64, translate=True, required=True),
58         'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the payment term without removing it."),
59         'note': fields.text('Description', translate=True),
60         'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
61     }
62     _defaults = {
63         'active': lambda *a: 1,
64     }
65     _order = "name"
66
67     def compute(self, cr, uid, id, value, date_ref=False, context={}):
68         if not date_ref:
69             date_ref = datetime.now().strftime('%Y-%m-%d')
70         pt = self.browse(cr, uid, id, context)
71         amount = value
72         result = []
73         for line in pt.line_ids:
74             prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
75             if line.value == 'fixed':
76                 amt = round(line.value_amount, prec)
77             elif line.value == 'procent':
78                 amt = round(value * line.value_amount, prec)
79             elif line.value == 'balance':
80                 amt = round(amount, prec)
81             if amt:
82                 next_date = datetime.strptime(date_ref, '%Y-%m-%d') + relativedelta(days=line.days)
83                 if line.days2 < 0:
84                     next_date += relativedelta(day=31)
85                 if line.days2 > 0:
86                     next_date += relativedelta(day=line.days2, months=1)
87                 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
88                 amount -= amt
89         return result
90
91 account_payment_term()
92
93 class account_payment_term_line(osv.osv):
94     _name = "account.payment.term.line"
95     _description = "Payment Term Line"
96     _columns = {
97         'name': fields.char('Line Name', size=32, required=True),
98         '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"),
99         'value': fields.selection([('procent', 'Percent'),
100                                    ('balance', 'Balance'),
101                                    ('fixed', 'Fixed Amount')], 'Value',
102                                    required=True, help="""Example: 14 days 2%, 30 days net
103 1. Line 1: percent 0.02 14 days
104 2. Line 2: balance 30 days"""),
105
106         'value_amount': fields.float('Value Amount', help="For Value percent enter % ratio between 0-1."),
107         'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
108             "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
109         'days2': fields.integer('Day of the Month', required=True, help="Day of the month, set -1 for the last day of the current month. If it's positive, it gives the day of the next month. Set 0 for net days (otherwise it's based on the beginning of the month)."),
110         'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
111     }
112     _defaults = {
113         'value': lambda *a: 'balance',
114         'sequence': lambda *a: 5,
115         'days2': lambda *a: 0,
116     }
117     _order = "sequence"
118
119     def _check_percent(self, cr, uid, ids, context={}):
120         obj = self.browse(cr, uid, ids[0])
121         if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
122             return False
123         return True
124
125     _constraints = [
126         (_check_percent, _('Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% '), ['value_amount']),
127     ]
128
129 account_payment_term_line()
130
131
132 class account_account_type(osv.osv):
133     _name = "account.account.type"
134     _description = "Account Type"
135     _columns = {
136         'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
137         'code': fields.char('Code', size=32, required=True),
138         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of account types."),
139         'close_method': fields.selection([('none', 'None'), ('balance', 'Balance'), ('detail', 'Detail'), ('unreconciled', 'Unreconciled')], 'Deferral Method', required=True),
140         'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign on Reports', required=True, help='Allows you to change the sign of the balance amount displayed in the reports, so that you can see positive figures instead of negative ones in expenses accounts.'),
141         'report_type':fields.selection([
142             ('none','/'),
143             ('income','Profit & Loss (Income Accounts)'),
144             ('expense','Profit & Loss (Expense Accounts)'),
145             ('asset','Balance Sheet (Assets Accounts)'),
146             ('liability','Balance Sheet (Liability Accounts)')
147         ],'Type Heads', select=True, readonly=False, help="According value related accounts will be display on respective reports (Balance Sheet Profit & Loss Account)"),
148         'parent_id':fields.many2one('account.account.type', 'Parent Type', required=False),
149         'child_ids':fields.one2many('account.account.type', 'parent_id', 'Child Types', required=False),
150         'note': fields.text('Description'),
151     }
152     _defaults = {
153         'close_method': lambda *a: 'none',
154         'sequence': lambda *a: 5,
155         'sign': lambda *a: 1,
156     }
157     _order = "sequence"
158
159     def _check_recursion(self, cr, uid, ids):
160         #TODO: Need to check for recusrion
161         return True
162
163     _constraints = [
164         (_check_recursion, 'Error ! You can not create recursive types.', ['parent_id'])
165     ]
166
167 account_account_type()
168
169 def _code_get(self, cr, uid, context={}):
170     acc_type_obj = self.pool.get('account.account.type')
171     ids = acc_type_obj.search(cr, uid, [])
172     res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context)
173     return [(r['code'], r['name']) for r in res]
174
175 #----------------------------------------------------------
176 # Accounts
177 #----------------------------------------------------------
178
179 class account_tax(osv.osv):
180     _name = 'account.tax'
181 account_tax()
182
183 class account_account(osv.osv):
184     _order = "parent_left"
185     _parent_order = "code"
186     _name = "account.account"
187     _description = "Account"
188     _parent_store = True
189     logger = netsvc.Logger()
190
191     def _get_children_and_consol(self, cr, uid, ids, context={}):
192         ids2=[]
193         temp=[]
194         read_data= self.read(cr, uid, ids,['id','child_id'], context)
195         for data in read_data:
196             ids2.append(data['id'])
197             if data['child_id']:
198                 temp=[]
199                 for x in data['child_id']:
200                     temp.append(x)
201                 ids2 += self._get_children_and_consol(cr, uid, temp, context)
202         return ids2
203
204     def search(self, cr, uid, args, offset=0, limit=None, order=None,
205             context=None, count=False):
206         if context is None:
207             context = {}
208         pos = 0
209
210         while pos < len(args):
211
212             if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
213                 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
214             if args[pos][0] == 'journal_id':
215                 if not args[pos][2]:
216                     del args[pos]
217                     continue
218                 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
219                 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
220                     args[pos] = ('type','not in',('consolidation','view'))
221                     continue
222                 ids3 = map(lambda x: x.id, jour.type_control_ids)
223                 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
224                 ids1 += map(lambda x: x.id, jour.account_control_ids)
225                 args[pos] = ('id', 'in', ids1)
226             pos += 1
227
228         if context and context.has_key('consolidate_childs'): #add consolidated childs of accounts
229             ids = super(account_account, self).search(cr, uid, args, offset, limit,
230                 order, context=context, count=count)
231             for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
232                 ids.append(consolidate_child.id)
233             return ids
234
235         return super(account_account, self).search(cr, uid, args, offset, limit,
236                 order, context=context, count=count)
237
238     def _get_children_and_consol(self, cr, uid, ids, context={}):
239         #this function search for all the children and all consolidated children (recursively) of the given account ids
240         ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
241         ids3 = []
242         for rec in self.browse(cr, uid, ids2, context=context):
243             for child in rec.child_consol_ids:
244                 ids3.append(child.id)
245         if ids3:
246             ids3 = self._get_children_and_consol(cr, uid, ids3, context)
247         return ids2 + ids3
248
249     def __compute(self, cr, uid, ids, field_names, arg=None, context=None,
250                   query='', query_params=()):
251             """ compute the balance, debit and/or credit for the provided
252             account ids
253             Arguments:
254             `ids`: account ids
255             `field_names`: the fields to compute (a list of any of
256                            'balance', 'debit' and 'credit')
257             `arg`: unused fields.function stuff
258             `query`: additional query filter (as a string)
259             `query_params`: parameters for the provided query string
260                             (__compute will handle their escaping) as a
261                             tuple
262             """
263             mapping = {
264                 'balance': "COALESCE(SUM(l.debit),0) " \
265                            "- COALESCE(SUM(l.credit), 0) as balance",
266                 'debit': "COALESCE(SUM(l.debit), 0) as debit",
267                 'credit': "COALESCE(SUM(l.credit), 0) as credit"
268             }
269             #get all the necessary accounts
270             children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
271             #compute for each account the balance/debit/credit from the move lines
272             accounts = {}
273             if children_and_consolidated:
274                 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
275
276                 wheres = [""]
277                 if query.strip():
278                     wheres.append(query.strip())
279                 if aml_query.strip():
280                     wheres.append(aml_query.strip())
281                 filters = " AND ".join(wheres)
282                 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
283                                           'Filters: %s'%filters)
284                 # IN might not work ideally in case there are too many
285                 # children_and_consolidated, in that case join on a
286                 # values() e.g.:
287                 # SELECT l.account_id as id FROM account_move_line l
288                 # INNER JOIN (VALUES (id1), (id2), (id3), ...) AS tmp (id)
289                 # ON l.account_id = tmp.id
290                 # or make _get_children_and_consol return a query and join on that
291                 request = ("SELECT l.account_id as id, " +\
292                            ' , '.join(map(mapping.__getitem__, field_names)) +
293                            " FROM account_move_line l" \
294                            " WHERE l.account_id IN %s " \
295                                 + filters +
296                            " GROUP BY l.account_id")
297                 params = (tuple(children_and_consolidated),) + query_params
298                 cr.execute(request, params)
299                 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
300                                           'Status: %s'%cr.statusmessage)
301
302                 for res in cr.dictfetchall():
303                     accounts[res['id']] = res
304
305             # consolidate accounts with direct children
306             children_and_consolidated.reverse()
307             brs = list(self.browse(cr, uid, children_and_consolidated, context=context))
308             sums = {}
309             while brs:
310                 current = brs[0]
311                 can_compute = True
312                 for child in current.child_id:
313                     if child.id not in sums:
314                         can_compute = False
315                         try:
316                             brs.insert(0, brs.pop(brs.index(child)))
317                         except ValueError:
318                             brs.insert(0, child)
319                 if can_compute:
320                     brs.pop(0)
321                     for fn in field_names:
322                         sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
323                         if current.child_id:
324                             sums[current.id][fn] += sum(sums[child.id][fn] for child in current.child_id)
325             res = {}
326             null_result = dict((fn, 0.0) for fn in field_names)
327             for id in ids:
328                 res[id] = sums.get(id, null_result)
329             return res
330
331     def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
332         result = {}
333         for rec in self.browse(cr, uid, ids, context):
334             result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
335         return result
336
337     def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
338         result = {}
339         for record in self.browse(cr, uid, ids, context):
340             if record.child_parent_ids:
341                 result[record.id] = [x.id for x in record.child_parent_ids]
342             else:
343                 result[record.id] = []
344
345             if record.child_consol_ids:
346                 for acc in record.child_consol_ids:
347                     if acc.id not in result[record.id]:
348                         result[record.id].append(acc.id)
349
350         return result
351
352     def _get_level(self, cr, uid, ids, field_name, arg, context={}):
353         res={}
354         accounts = self.browse(cr, uid, ids)
355         for account in accounts:
356             level = 0
357             if account.parent_id :
358                 obj = self.browse(cr, uid, account.parent_id.id)
359                 level = obj.level + 1
360             res[account.id] = level
361         return res
362
363     _columns = {
364         'name': fields.char('Name', size=128, required=True, select=True),
365         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
366         'code': fields.char('Code', size=64, required=True),
367         'type': fields.selection([
368             ('view', 'View'),
369             ('other', 'Regular'),
370             ('receivable', 'Receivable'),
371             ('payable', 'Payable'),
372             ('consolidation', 'Consolidation'),
373             ('closed', 'Closed'),
374         ], 'Internal Type', required=True, help="This type is used to differentiate types with "\
375             "special effects in Open ERP: view can not have entries, consolidation are accounts that "\
376             "can have children accounts for multi-company consolidations, payable/receivable are for "\
377             "partners accounts (for debit/credit computations), closed for depreciated accounts."),
378         'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
379             help="These types are defined according to your country. The type contains more information "\
380             "about the account and its specificities."),
381         'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
382         'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
383         'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
384         'child_id': fields.function(_get_child_ids, method=True, type='many2many', relation="account.account", string="Child Accounts"),
385         'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Balance', multi='balance'),
386         'credit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Credit', multi='balance'),
387         'debit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Debit', multi='balance'),
388         'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."),
389         'shortcut': fields.char('Shortcut', size=12),
390         'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
391             'account_id', 'tax_id', 'Default Taxes'),
392         'note': fields.text('Note'),
393         'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
394         'company_id': fields.many2one('res.company', 'Company', required=True),
395         'active': fields.boolean('Active', select=2, help="If the active field is set to true, it will allow you to hide the account without removing it."),
396
397         'parent_left': fields.integer('Parent Left', select=1),
398         'parent_right': fields.integer('Parent Right', select=1),
399         'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
400             help=
401             'This will select how the current currency rate for outgoing transactions is computed. '\
402             'In most countries the legal method is "average" but only a few software systems are able to '\
403             'manage this. So if you import from another software system you may have to use the rate at date. ' \
404             'Incoming transactions always use the rate at date.', \
405             required=True),
406         'check_history': fields.boolean('Display History',
407             help="Check this box if you want to print all entries when printing the General Ledger, "\
408             "otherwise it will only print its balance."),
409         'level': fields.function(_get_level, string='Level', method=True, store=True, type='integer'),
410     }
411
412     def _default_company(self, cr, uid, context={}):
413         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
414         if user.company_id:
415             return user.company_id.id
416         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
417
418     _defaults = {
419         'type': lambda *a : 'view',
420         'reconcile': lambda *a: False,
421         'company_id': _default_company,
422         'active': lambda *a: True,
423         'check_history': lambda *a: True,
424         'currency_mode': lambda *a: 'current',
425         'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
426     }
427
428     def _check_recursion(self, cr, uid, ids):
429         obj_self = self.browse(cr, uid, ids[0])
430         p_id = obj_self.parent_id and obj_self.parent_id.id
431         if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
432             return False
433         while(ids):
434             cr.execute('SELECT DISTINCT child_id '\
435                        'FROM account_account_consol_rel '\
436                        'WHERE parent_id IN %s', (tuple(ids),))
437             child_ids = map(itemgetter(0), cr.fetchall())
438             c_ids = child_ids
439             if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
440                 return False
441             while len(c_ids):
442                 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
443                 if p_id and (p_id in s_ids):
444                     return False
445                 c_ids = s_ids
446             ids = child_ids
447         return True
448
449     _constraints = [
450         (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
451     ]
452     _sql_constraints = [
453         ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
454     ]
455     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
456         if not args:
457             args = []
458         if not context:
459             context = {}
460         args = args[:]
461         ids = []
462         try:
463             if name and str(name).startswith('partner:'):
464                 part_id = int(name.split(':')[1])
465                 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
466                 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
467                 name = False
468             if name and str(name).startswith('type:'):
469                 type = name.split(':')[1]
470                 args += [('type', '=', type)]
471                 name = False
472         except:
473             pass
474         if name:
475             ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
476             if not ids:
477                 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
478             if not ids:
479                 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
480         else:
481             ids = self.search(cr, user, args, context=context, limit=limit)
482         return self.name_get(cr, user, ids, context=context)
483
484     def name_get(self, cr, uid, ids, context={}):
485         if not len(ids):
486             return []
487         reads = self.read(cr, uid, ids, ['name', 'code'], context)
488         res = []
489         for record in reads:
490             name = record['name']
491             if record['code']:
492                 name = record['code'] + ' '+name
493             res.append((record['id'], name))
494         return res
495
496     def copy(self, cr, uid, id, default={}, context={}, done_list=[], local=False):
497         account = self.browse(cr, uid, id, context=context)
498         new_child_ids = []
499         if not default:
500             default = {}
501         default = default.copy()
502         default['code'] = (account['code'] or '') + '(copy)'
503         if not local:
504             done_list = []
505         if account.id in done_list:
506             return False
507         done_list.append(account.id)
508         if account:
509             for child in account.child_id:
510                 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
511                 if child_ids:
512                     new_child_ids.append(child_ids)
513             default['child_parent_ids'] = [(6, 0, new_child_ids)]
514         else:
515             default['child_parent_ids'] = False
516         return super(account_account, self).copy(cr, uid, id, default, context=context)
517
518     def _check_moves(self, cr, uid, ids, method, context):
519         line_obj = self.pool.get('account.move.line')
520         account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
521
522         if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
523             if method == 'write':
524                 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
525             elif method == 'unlink':
526                 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
527         #Checking whether the account is set as a property to any Partner or not
528         value = 'account.account,' + str(ids[0])
529         partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
530         if partner_prop_acc:
531             raise osv.except_osv(_('Warning !'), _('You cannot remove/deactivate an account which is set as a property to any Partner.'))
532         return True
533
534     def _check_allow_type_change(self, cr, uid, ids, new_type, context):
535         group1 = ['payable', 'receivable', 'other']
536         group2 = ['consolidation','view']
537         line_obj = self.pool.get('account.move.line')
538         for account in self.browse(cr, uid, ids, context=context):
539             old_type = account.type
540             account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
541             if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
542                 #Check for 'Closed' type
543                 if old_type == 'closed' and new_type !='closed':
544                     raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains account entries!"))
545                 #Check for change From group1 to group2 and vice versa
546                 if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
547                     raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from '%s' to '%s' type as it contains account entries!") % (old_type,new_type,))
548         return True
549
550     def write(self, cr, uid, ids, vals, context=None):
551         if context is None:
552             context = {}
553
554         if 'company_id' in vals:
555             move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
556             if move_lines:
557                 raise osv.except_osv(_('Warning !'), _('You cannot modify Company of account as its related record exist in Entry Lines'))
558         if 'active' in vals and not vals['active']:
559             self._check_moves(cr, uid, ids, "write", context=context)
560         if 'type' in vals.keys():
561             self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
562         return super(account_account, self).write(cr, uid, ids, vals, context=context)
563
564     def unlink(self, cr, uid, ids, context={}):
565         self._check_moves(cr, uid, ids, "unlink", context)
566         return super(account_account, self).unlink(cr, uid, ids, context)
567
568 account_account()
569
570 class account_journal_view(osv.osv):
571     _name = "account.journal.view"
572     _description = "Journal View"
573     _columns = {
574         'name': fields.char('Journal View', size=64, required=True),
575         'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
576     }
577     _order = "name"
578 account_journal_view()
579
580
581 class account_journal_column(osv.osv):
582     def _col_get(self, cr, user, context={}):
583         result = []
584         cols = self.pool.get('account.move.line')._columns
585         for col in cols:
586             if col in ('period_id', 'journal_id'):
587                 continue
588
589             result.append( (col, cols[col].string) )
590         result.sort()
591         return result
592     _name = "account.journal.column"
593     _description = "Journal Column"
594     _columns = {
595         'name': fields.char('Column Name', size=64, required=True),
596         'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
597         'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
598         'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column."),
599         'required': fields.boolean('Required'),
600         'readonly': fields.boolean('Readonly'),
601     }
602     _order = "sequence"
603 account_journal_column()
604
605 class account_journal(osv.osv):
606     _name = "account.journal"
607     _description = "Journal"
608     _columns = {
609         'name': fields.char('Journal Name', size=64, required=True, translate=True,help="Name of the journal"),
610         'code': fields.char('Code', size=16,required=True,help="Code of the journal"),
611         'type': fields.selection([('sale', 'Sale'),('sale_refund','Sale Refund'), ('purchase', 'Purchase'), ('purchase_refund','Purchase Refund'),('expense', 'Expense'), ('cash', 'Cash'), ('bank', 'Bank'), ('general', 'General'), ('situation', 'Situation')], 'Type', size=32, required=True,
612                                  help="Select 'Sale' for Sale journal to be used at the time of making invoice."\
613                                  " Select 'Purchase' for Purchase Journal to be used at the time of approving purchase order."\
614                                  " Select 'Cash' to be used at the time of making payment."\
615                                  " Select 'General' to be used at the time of stock input/output."\
616                                  " Select 'Situation' to be used at the time of making vouchers."),
617         'refund_journal': fields.boolean('Refund Journal', help='Fill this if the journal is to be used for refunds of invoices.'),
618         'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
619         'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
620         '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 Open ERP 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."),
621         'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
622         'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
623         '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."),
624         '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"),
625         '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."),
626         'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals", required=False),
627         'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
628         'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
629         'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
630         'entry_posted': fields.boolean('Skip \'Draft\' State for Created Entries', help='Check this box if you don\'t want new account moves to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation.'),
631         'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
632         'invoice_sequence_id': fields.many2one('ir.sequence', 'Invoice Sequence', \
633             help="The sequence used for invoice numbers in this journal."),
634         'allow_date':fields.boolean('Check Date not in the Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'),
635     }
636
637     _defaults = {
638         'user_id': lambda self,cr,uid,context: uid,
639         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
640     }
641     
642     def write(self, cr, uid, ids, vals, context=None):
643         obj=[]
644         if 'company_id' in vals:
645             move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
646             if move_lines:
647                 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this journal as its related record exist in Entry Lines'))
648         return super(account_journal, self).write(cr, uid, ids, vals, context=context)
649
650     def create_sequence(self, cr, uid, ids, context={}):
651         """
652         Create new entry sequence for every new Joural
653         @param cr: cursor to database
654         @param user: id of current user
655         @param ids: list of record ids to be process
656         @param context: context arguments, like lang, time zone
657         @return: return a result
658         """
659         
660         seq_pool = self.pool.get('ir.sequence')
661         seq_typ_pool = self.pool.get('ir.sequence.type')
662         
663         result = True
664         
665         journal = self.browse(cr, uid, ids[0], context)
666         code = journal.code.lower()     
667         types = {
668             'name':journal.name,
669             'code':code
670         }
671         type_id = seq_typ_pool.create(cr, uid, types)
672         
673         seq = {
674             'name':journal.name,
675             'code':code,
676             'active':True,
677             'prefix':journal.code + "/%(year)s/",
678             'padding':4,
679             'number_increment':1
680         }
681         seq_id = seq_pool.create(cr, uid, seq)
682         
683         res = {}
684         if not journal.sequence_id:
685             res.update({
686                 'sequence_id':seq_id
687             })
688         
689         if not journal.invoice_sequence_id:
690             res.update({
691                 'invoice_sequence_id':seq_id
692             })
693         
694         result = self.write(cr, uid, [journal.id], res)
695             
696         return result
697         
698     def create(self, cr, uid, vals, context={}):
699         journal_id = super(account_journal, self).create(cr, uid, vals, context)
700         self.create_sequence(cr, uid, [journal_id], context)
701
702 #       journal_name = self.browse(cr, uid, [journal_id])[0].code
703 #       periods = self.pool.get('account.period')
704 #       ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
705 #       for period in periods.browse(cr, uid, ids):
706 #           self.pool.get('account.journal.period').create(cr, uid, {
707 #               'name': (journal_name or '')+':'+(period.code or ''),
708 #               'journal_id': journal_id,
709 #               'period_id': period.id
710 #           })
711         return journal_id
712
713     def name_search(self, cr, user, name, args=None, operator='ilike', context={}, limit=100):
714         if not args:
715             args = []
716         ids = []
717         if name:
718             ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit, context=context)
719         if not ids:
720             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
721         return self.name_get(cr, user, ids, context=context)
722
723     def onchange_type(self, cr, uid, ids, type, currency):
724         data_pool = self.pool.get('ir.model.data')
725         user_pool = self.pool.get('res.users')
726         
727         type_map = {
728             'sale':'account_sp_journal_view',
729             'sale_refund':'account_sp_refund_journal_view',
730             'purchase':'account_sp_journal_view',
731             'purchase_refund':'account_sp_refund_journal_view',
732             'expense':'account_sp_journal_view',
733             'cash':'account_journal_bank_view',
734             'bank':'account_journal_bank_view',
735             'general':'account_journal_view',
736             'situation':'account_journal_view'
737         }
738         
739         res = {}
740         
741         view_id = type_map.get(type, 'general')
742         
743         user = user_pool.browse(cr, uid, uid)
744         if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
745             view_id = 'account_journal_bank_view_multi'
746         
747         data_id = data_pool.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
748         data = data_pool.browse(cr, uid, data_id[0])
749     
750         res.update({
751             'centralisation':type == 'situation',
752             'view_id':data.res_id,
753         })
754         
755         return {
756             'value':res
757         }
758
759 account_journal()
760
761 class account_fiscalyear(osv.osv):
762     _name = "account.fiscalyear"
763     _description = "Fiscal Year"
764     _columns = {
765         'name': fields.char('Fiscal Year', size=64, required=True),
766         'code': fields.char('Code', size=6, required=True),
767         'company_id': fields.many2one('res.company', 'Company', required=True),
768         'date_start': fields.date('Start Date', required=True),
769         'date_stop': fields.date('End Date', required=True),
770         'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
771         'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', readonly=True,
772                                   help='When fiscal year is created. The state is \'Draft\'. At the end of the year it is in \'Done\' state.'),
773     }
774
775     _defaults = {
776         'state': lambda *a: 'draft',
777         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
778     }
779     _order = "date_start"
780
781     def _check_duration(self,cr,uid,ids):
782         obj_fy=self.browse(cr,uid,ids[0])
783         if obj_fy.date_stop < obj_fy.date_start:
784             return False
785         return True
786
787     _constraints = [
788         (_check_duration, 'Error ! The duration of the Fiscal Year is invalid. ', ['date_stop'])
789     ]
790
791     def create_period3(self,cr, uid, ids, context={}):
792         return self.create_period(cr, uid, ids, context, 3)
793
794     def create_period(self,cr, uid, ids, context={}, interval=1):
795         for fy in self.browse(cr, uid, ids, context):
796             ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
797             while ds.strftime('%Y-%m-%d')<fy.date_stop:
798                 de = ds + relativedelta(months=interval, days=-1)
799
800                 if de.strftime('%Y-%m-%d')>fy.date_stop:
801                     de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
802
803                 self.pool.get('account.period').create(cr, uid, {
804                     'name': ds.strftime('%m/%Y'),
805                     'code': ds.strftime('%m/%Y'),
806                     'date_start': ds.strftime('%Y-%m-%d'),
807                     'date_stop': de.strftime('%Y-%m-%d'),
808                     'fiscalyear_id': fy.id,
809                 })
810                 ds = ds + relativedelta(months=interval)
811         return True
812
813     def find(self, cr, uid, dt=None, exception=True, context={}):
814         if not dt:
815             dt = time.strftime('%Y-%m-%d')
816         ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
817         if not ids:
818             if exception:
819                 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
820             else:
821                 return False
822         return ids[0]
823
824     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
825         if args is None:
826             args = []
827         if context is None:
828             context = {}
829         ids = []
830         if name:
831             ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
832         if not ids:
833             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
834         return self.name_get(cr, user, ids, context=context)
835
836 account_fiscalyear()
837
838 class account_period(osv.osv):
839     _name = "account.period"
840     _description = "Account period"
841     _columns = {
842         'name': fields.char('Period Name', size=64, required=True),
843         'code': fields.char('Code', size=12),
844         'special': fields.boolean('Opening/Closing Period', size=12,
845             help="These periods can overlap."),
846         'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
847         'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
848         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
849         'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', readonly=True,
850                                   help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
851         'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company'),
852     }
853     _defaults = {
854         'state': lambda *a: 'draft',
855         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
856     }
857     _order = "date_start"
858
859     def _check_duration(self,cr,uid,ids,context={}):
860         obj_period=self.browse(cr,uid,ids[0])
861         if obj_period.date_stop < obj_period.date_start:
862             return False
863         return True
864
865     def _check_year_limit(self,cr,uid,ids,context={}):
866         for obj_period in self.browse(cr,uid,ids):
867             if obj_period.special:
868                 continue
869
870             if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
871                obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
872                obj_period.fiscalyear_id.date_start > obj_period.date_start or \
873                obj_period.fiscalyear_id.date_start > obj_period.date_stop:
874                 return False
875
876             pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
877             for period in self.browse(cr, uid, pids):
878                 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
879                     return False
880         return True
881
882     _constraints = [
883         (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
884         (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
885     ]
886
887     def next(self, cr, uid, period, step, context={}):
888         ids = self.search(cr, uid, [('date_start','>',period.date_start)])
889         if len(ids)>=step:
890             return ids[step-1]
891         return False
892
893     def find(self, cr, uid, dt=None, context={}):
894         if not dt:
895             dt = time.strftime('%Y-%m-%d')
896 #CHECKME: shouldn't we check the state of the period?
897         ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
898         if not ids:
899             raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create a fiscal year.')%dt)
900         return ids
901
902     def action_draft(self, cr, uid, ids, *args):
903         users_roles = self.pool.get('res.users').browse(cr, uid, uid).roles_id
904         for role in users_roles:
905             if role.name=='Period':
906                 mode = 'draft'
907                 for id in ids:
908                     cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
909                     cr.execute('update account_period set state=%s where id=%s', (mode, id))
910         return True
911
912     def name_search(self, cr, user, name, args=None, operator='ilike', context={}, limit=80):
913         if args is None:
914             args = []
915         if context is None:
916             context = {}
917         ids = []
918         if name:
919             ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
920         if not ids:
921             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
922         return self.name_get(cr, user, ids, context=context)
923
924     def write(self, cr, uid, ids, vals, context={}):
925         obj=[]
926         if 'company_id' in vals:
927             move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
928             if move_lines:
929                 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines'))
930         return super(account_period, self).write(cr, uid, ids, vals, context=context)
931
932 account_period()
933
934 class account_journal_period(osv.osv):
935     _name = "account.journal.period"
936     _description = "Journal Period"
937
938     def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
939         result = {}.fromkeys(ids, 'STOCK_NEW')
940         for r in self.read(cr, uid, ids, ['state']):
941             result[r['id']] = {
942                 'draft': 'STOCK_NEW',
943                 'printed': 'STOCK_PRINT_PREVIEW',
944                 'done': 'STOCK_DIALOG_AUTHENTICATION',
945             }.get(r['state'], 'STOCK_NEW')
946         return result
947
948     _columns = {
949         'name': fields.char('Journal-Period Name', size=64, required=True),
950         'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
951         'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
952         'icon': fields.function(_icon_get, method=True, string='Icon', type='char', size=32),
953         'active': fields.boolean('Active', required=True, help="If the active field is set to true, it will allow you to hide the journal period without removing it."),
954         'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
955                                   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.'),
956         'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
957         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company')
958     }
959
960     def _check(self, cr, uid, ids, context={}):
961         for obj in self.browse(cr, uid, ids, context):
962             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))
963             res = cr.fetchall()
964             if res:
965                 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
966         return True
967
968     def write(self, cr, uid, ids, vals, context={}):
969         self._check(cr, uid, ids, context)
970         return super(account_journal_period, self).write(cr, uid, ids, vals, context)
971
972     def create(self, cr, uid, vals, context={}):
973         period_id=vals.get('period_id',False)
974         if period_id:
975             period = self.pool.get('account.period').browse(cr, uid,period_id)
976             vals['state']=period.state
977         return super(account_journal_period, self).create(cr, uid, vals, context)
978
979     def unlink(self, cr, uid, ids, context={}):
980         self._check(cr, uid, ids, context)
981         return super(account_journal_period, self).unlink(cr, uid, ids, context)
982
983     _defaults = {
984         'state': lambda *a: 'draft',
985         'active': lambda *a: True,
986         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
987     }
988     _order = "period_id"
989
990 account_journal_period()
991
992 class account_fiscalyear(osv.osv):
993     _inherit = "account.fiscalyear"
994     _description = "Fiscal Year"
995     _columns = {
996         'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
997     }
998
999 account_fiscalyear()
1000 #----------------------------------------------------------
1001 # Entries
1002 #----------------------------------------------------------
1003 class account_move(osv.osv):
1004     _name = "account.move"
1005     _description = "Account Entry"
1006     _order = 'id desc'
1007
1008     def name_get(self, cursor, user, ids, context=None):
1009         if not len(ids):
1010             return []
1011         res=[]
1012         data_move = self.pool.get('account.move').browse(cursor,user,ids)
1013         for move in data_move:
1014             if move.state=='draft':
1015                 name = '*' + str(move.id)
1016             else:
1017                 name = move.name
1018             res.append((move.id, name))
1019         return res
1020
1021
1022     def _get_period(self, cr, uid, context):
1023         periods = self.pool.get('account.period').find(cr, uid)
1024         if periods:
1025             return periods[0]
1026         else:
1027             return False
1028
1029     def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1030         if not ids: return {}
1031         cr.execute( 'SELECT move_id, SUM(debit) '\
1032                     'FROM account_move_line '\
1033                     'WHERE move_id IN %s '\
1034                     'GROUP BY move_id', (tuple(ids),))
1035         result = dict(cr.fetchall())
1036         for id in ids:
1037             result.setdefault(id, 0.0)
1038         return result
1039
1040     def _search_amount(self, cr, uid, obj, name, args, context):
1041         ids = []
1042         cr.execute('select move_id,sum(debit) from account_move_line group by move_id')
1043         result = dict(cr.fetchall())
1044
1045         for item in args:
1046             if item[1] == '>=':
1047                 res = [('id', 'in', [k for k,v in result.iteritems() if v >= item[2]])]
1048             else:
1049                 res = [('id', 'in', [k for k,v in result.iteritems() if v <= item[2]])]
1050             ids += res
1051
1052         if not ids:
1053             return [('id', '>', '0')]
1054
1055         return ids
1056
1057     _columns = {
1058         'name': fields.char('Number', size=64, required=True),
1059         'ref': fields.char('Reference', size=64),
1060         'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1061         'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1062         'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'State', required=True, readonly=True,
1063                                   help='When new account move is created the state will be \'Draft\'. When all the payments are done it will be in \'Posted\' state.'),
1064         'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1065         'to_check': fields.boolean('To Be Verified'),
1066         'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner"),
1067         'amount': fields.function(_amount_compute, method=True, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1068         'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}),
1069         'type': fields.selection([
1070             ('pay_voucher','Cash Payment'),
1071             ('bank_pay_voucher','Bank Payment'),
1072             ('rec_voucher','Cash Receipt'),
1073             ('bank_rec_voucher','Bank Receipt'),
1074             ('cont_voucher','Contra'),
1075             ('journal_sale_vou','Journal Sale'),
1076             ('journal_pur_voucher','Journal Purchase'),
1077             ('journal_voucher','Journal Voucher'),
1078             ],'Entry Type', select=True , size=128, readonly=True, states={'draft':[('readonly',False)]}),
1079         'narration':fields.text('Narration', readonly=True, select=True, states={'draft':[('readonly',False)]}),
1080         'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1081     }
1082     _defaults = {
1083         'name': lambda *a: '/',
1084         'state': lambda *a: 'draft',
1085         'period_id': _get_period,
1086         'type' : lambda *a : 'journal_voucher',
1087         'date': lambda *a:time.strftime('%Y-%m-%d'),
1088         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1089     }
1090
1091     def _check_centralisation(self, cursor, user, ids):
1092         for move in self.browse(cursor, user, ids):
1093             if move.journal_id.centralisation:
1094                 move_ids = self.search(cursor, user, [
1095                     ('period_id', '=', move.period_id.id),
1096                     ('journal_id', '=', move.journal_id.id),
1097                     ])
1098                 if len(move_ids) > 1:
1099                     return False
1100         return True
1101
1102     def _check_period_journal(self, cursor, user, ids):
1103         for move in self.browse(cursor, user, ids):
1104             for line in move.line_id:
1105                 if line.period_id.id != move.period_id.id:
1106                     return False
1107                 if line.journal_id.id != move.journal_id.id:
1108                     return False
1109         return True
1110
1111     _constraints = [
1112         (_check_centralisation,
1113             'You cannot create more than one move per period on centralized journal',
1114             ['journal_id']),
1115         (_check_period_journal,
1116             'You cannot create entries on different periods/journals in the same move',
1117             ['line_id']),
1118     ]
1119     def post(self, cr, uid, ids, context=None):
1120         if self.validate(cr, uid, ids, context) and len(ids):
1121             for move in self.browse(cr, uid, ids):
1122                 if move.name =='/':
1123                     new_name = False
1124                     journal = move.journal_id
1125                     if journal.sequence_id:
1126                         c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1127                         new_name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id, context=c)
1128                     else:
1129                         raise osv.except_osv(_('Error'), _('No sequence defined in the journal !'))
1130                     if new_name:
1131                         self.write(cr, uid, [move.id], {'name':new_name})
1132
1133             cr.execute('UPDATE account_move '\
1134                        'SET state=%s '\
1135                        'WHERE id IN %s',
1136                        ('posted', tuple(ids),))
1137         else:
1138             raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non-balanced entry !\nMake sure you have configured Payment Term properly !\nIt should contain atleast one Payment Term Line with type "Balance" !'))
1139         return True
1140
1141     def button_validate(self, cursor, user, ids, context=None):
1142         return self.post(cursor, user, ids, context=context)
1143
1144     def button_cancel(self, cr, uid, ids, context={}):
1145         for line in self.browse(cr, uid, ids, context):
1146             if not line.journal_id.update_posted:
1147                 raise osv.except_osv(_('Error !'), _('You can not modify a posted entry of this journal !\nYou should set the journal to allow cancelling entries if you want to do that.'))
1148         if len(ids):
1149             cr.execute('UPDATE account_move '\
1150                        'SET state=%s '\
1151                        'WHERE id IN %s', ('draft', tuple(ids),))
1152         return True
1153
1154     def write(self, cr, uid, ids, vals, context={}):
1155         c = context.copy()
1156         c['novalidate'] = True
1157         result = super(osv.osv, self).write(cr, uid, ids, vals, c)
1158         self.validate(cr, uid, ids, context)
1159         return result
1160
1161     #
1162     # TODO: Check if period is closed !
1163     #
1164     def create(self, cr, uid, vals, context={}):
1165         if 'line_id' in vals:
1166             if 'journal_id' in vals:
1167                 for l in vals['line_id']:
1168                     if not l[0]:
1169                         l[2]['journal_id'] = vals['journal_id']
1170                 context['journal_id'] = vals['journal_id']
1171             if 'period_id' in vals:
1172                 for l in vals['line_id']:
1173                     if not l[0]:
1174                         l[2]['period_id'] = vals['period_id']
1175                 context['period_id'] = vals['period_id']
1176             else:
1177                 default_period = self._get_period(cr, uid, context)
1178                 for l in vals['line_id']:
1179                     if not l[0]:
1180                         l[2]['period_id'] = default_period
1181                 context['period_id'] = default_period
1182
1183         if 'line_id' in vals:
1184             c = context.copy()
1185             c['novalidate'] = True
1186             result = super(account_move, self).create(cr, uid, vals, c)
1187             self.validate(cr, uid, [result], context)
1188         else:
1189             result = super(account_move, self).create(cr, uid, vals, context)
1190         return result
1191
1192     def copy(self, cr, uid, id, default=None, context=None):
1193         if default is None:
1194             default = {}
1195         default = default.copy()
1196         default.update({'state':'draft', 'name':'/',})
1197         return super(account_move, self).copy(cr, uid, id, default, context)
1198
1199     def unlink(self, cr, uid, ids, context={}, check=True):
1200         toremove = []
1201         for move in self.browse(cr, uid, ids, context):
1202             if move['state'] != 'draft':
1203                 raise osv.except_osv(_('UserError'),
1204                         _('You can not delete posted movement: "%s"!') % \
1205                                 move['name'])
1206             line_ids = map(lambda x: x.id, move.line_id)
1207             context['journal_id'] = move.journal_id.id
1208             context['period_id'] = move.period_id.id
1209             self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
1210             self.pool.get('account.move.line').unlink(cr, uid, line_ids, context=context)
1211             toremove.append(move.id)
1212         result = super(account_move, self).unlink(cr, uid, toremove, context)
1213         return result
1214
1215     def _compute_balance(self, cr, uid, id, context={}):
1216         move = self.browse(cr, uid, [id])[0]
1217         amount = 0
1218         for line in move.line_id:
1219             amount+= (line.debit - line.credit)
1220         return amount
1221
1222     def _centralise(self, cr, uid, move, mode, context=None):
1223         assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1224         if context is None:
1225             context = {}
1226
1227         if mode=='credit':
1228             account_id = move.journal_id.default_debit_account_id.id
1229             mode2 = 'debit'
1230             if not account_id:
1231                 raise osv.except_osv(_('UserError'),
1232                         _('There is no default default debit account defined \n' \
1233                                 'on journal "%s"') % move.journal_id.name)
1234         else:
1235             account_id = move.journal_id.default_credit_account_id.id
1236             mode2 = 'credit'
1237             if not account_id:
1238                 raise osv.except_osv(_('UserError'),
1239                         _('There is no default default credit account defined \n' \
1240                                 'on journal "%s"') % move.journal_id.name)
1241
1242         # find the first line of this move with the current mode
1243         # or create it if it doesn't exist
1244         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1245         res = cr.fetchone()
1246         if res:
1247             line_id = res[0]
1248         else:
1249             context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1250             line_id = self.pool.get('account.move.line').create(cr, uid, {
1251                 'name': _(mode.capitalize()+' Centralisation'),
1252                 'centralisation': mode,
1253                 'account_id': account_id,
1254                 'move_id': move.id,
1255                 'journal_id': move.journal_id.id,
1256                 'period_id': move.period_id.id,
1257                 'date': move.period_id.date_stop,
1258                 'debit': 0.0,
1259                 'credit': 0.0,
1260             }, context)
1261
1262         # find the first line of this move with the other mode
1263         # so that we can exclude it from our calculation
1264         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1265         res = cr.fetchone()
1266         if res:
1267             line_id2 = res[0]
1268         else:
1269             line_id2 = 0
1270
1271         cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1272         result = cr.fetchone()[0] or 0.0
1273         cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1274         return True
1275
1276     #
1277     # Validate a balanced move. If it is a centralised journal, create a move.
1278     #
1279
1280     def validate(self, cr, uid, ids, context={}):
1281         if context and ('__last_update' in context):
1282             del context['__last_update']
1283         
1284         valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1285
1286         for move in self.browse(cr, uid, ids, context):
1287             # Unlink old analytic lines on move_lines
1288             for obj_line in move.line_id:
1289                 for obj in obj_line.analytic_lines:
1290                     self.pool.get('account.analytic.line').unlink(cr,uid,obj.id)
1291
1292             journal = move.journal_id
1293             amount = 0
1294             line_ids = []
1295             line_draft_ids = []
1296             company_id = None
1297             for line in move.line_id:
1298                 amount += line.debit - line.credit
1299                 line_ids.append(line.id)
1300                 if line.state=='draft':
1301                     line_draft_ids.append(line.id)
1302
1303                 if not company_id:
1304                     company_id = line.account_id.company_id.id
1305                 if not company_id == line.account_id.company_id.id:
1306                     raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1307
1308                 if line.account_id.currency_id:
1309                     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 or line.currency_id):
1310                         raise osv.except_osv(_('Error'), _("""Couldn't create move with currency different from the secondary currency of the account "%s - %s". Clear the secondary currency field of the account definition if you want to accept all currencies.""" % (line.account_id.code, line.account_id.name)))
1311
1312             if abs(amount) < 10 ** -4:
1313                 # If the move is balanced
1314                 # Add to the list of valid moves
1315                 # (analytic lines will be created later for valid moves)
1316                 valid_moves.append(move)
1317
1318                 # Check whether the move lines are confirmed
1319                 
1320                 if not len(line_draft_ids):
1321                     continue
1322                 # Update the move lines (set them as valid)
1323
1324                 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1325                     'journal_id': move.journal_id.id,
1326                     'period_id': move.period_id.id,
1327                     'state': 'valid'
1328                 }, context, check=False)
1329
1330                 account = {}
1331                 account2 = {}
1332                 
1333                 if journal.type in ('purchase','sale'):
1334                     for line in move.line_id:
1335                         code = amount = 0
1336                         key = (line.account_id.id, line.tax_code_id.id)
1337                         if key in account2:
1338                             code = account2[key][0]
1339                             amount = account2[key][1] * (line.debit + line.credit)
1340                         elif line.account_id.id in account:
1341                             code = account[line.account_id.id][0]
1342                             amount = account[line.account_id.id][1] * (line.debit + line.credit)
1343                         if (code or amount) and not (line.tax_code_id or line.tax_amount):
1344                             self.pool.get('account.move.line').write(cr, uid, [line.id], {
1345                                 'tax_code_id': code,
1346                                 'tax_amount': amount
1347                             }, context, check=False)
1348             elif journal.centralisation:
1349                 # If the move is not balanced, it must be centralised...
1350
1351                 # Add to the list of valid moves
1352                 # (analytic lines will be created later for valid moves)
1353                 valid_moves.append(move)
1354
1355                 #
1356                 # Update the move lines (set them as valid)
1357                 #
1358                 self._centralise(cr, uid, move, 'debit', context=context)
1359                 self._centralise(cr, uid, move, 'credit', context=context)
1360                 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1361                     'state': 'valid'
1362                 }, context, check=False)
1363             else:
1364                 # We can't validate it (it's unbalanced)
1365                 # Setting the lines as draft
1366                 self.pool.get('account.move.line').write(cr, uid, line_ids, {
1367                     'journal_id': move.journal_id.id,
1368                     'period_id': move.period_id.id,
1369                     #'tax_code_id': False,
1370                     #'tax_amount': False,
1371                     'state': 'draft'
1372                 }, context, check=False)
1373         # Create analytic lines for the valid moves
1374         for record in valid_moves:
1375             self.pool.get('account.move.line').create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1376
1377         return len(valid_moves) > 0
1378
1379 account_move()
1380
1381 class account_move_reconcile(osv.osv):
1382     _name = "account.move.reconcile"
1383     _description = "Account Reconciliation"
1384     _columns = {
1385         'name': fields.char('Name', size=64, required=True),
1386         'type': fields.char('Type', size=16, required=True),
1387         'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1388         'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1389         'create_date': fields.date('Creation date', readonly=True),
1390     }
1391     _defaults = {
1392         'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1393     }
1394     def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1395         total = 0.0
1396         for rec in self.browse(cr, uid, ids, context):
1397             for line in rec.line_partial_ids:
1398                 total += (line.debit or 0.0) - (line.credit or 0.0)
1399         if not total:
1400             self.pool.get('account.move.line').write(cr, uid,
1401                 map(lambda x: x.id, rec.line_partial_ids),
1402                 {'reconcile_id': rec.id }
1403             )
1404         return True
1405
1406     def name_get(self, cr, uid, ids, context=None):
1407         if not len(ids):
1408             return []
1409         result = []
1410         for r in self.browse(cr, uid, ids, context):
1411             total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1412             if total:
1413                 name = '%s (%.2f)' % (r.name, total)
1414                 result.append((r.id,name))
1415             else:
1416                 result.append((r.id,r.name))
1417         return result
1418
1419
1420 account_move_reconcile()
1421
1422 #----------------------------------------------------------
1423 # Tax
1424 #----------------------------------------------------------
1425 """
1426 a documenter
1427 child_depend: la taxe depend des taxes filles
1428 """
1429 class account_tax_code(osv.osv):
1430     """
1431     A code for the tax object.
1432
1433     This code is used for some tax declarations.
1434     """
1435     def _sum(self, cr, uid, ids, name, args, context,where ='', where_params=()):
1436         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1437         if context.get('based_on', 'invoices') == 'payments':
1438             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1439                     FROM account_move_line AS line, \
1440                         account_move AS move \
1441                         LEFT JOIN account_invoice invoice ON \
1442                             (invoice.move_id = move.id) \
1443                     WHERE line.tax_code_id IN %s '+where+' \
1444                         AND move.id = line.move_id \
1445                         AND ((invoice.state = \'paid\') \
1446                             OR (invoice.id IS NULL)) \
1447                             GROUP BY line.tax_code_id',
1448                                 (parent_ids,)+where_params)
1449         else:
1450             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1451                     FROM account_move_line AS line \
1452                     WHERE line.tax_code_id IN %s '+where+' \
1453                     GROUP BY line.tax_code_id',
1454                        (parent_ids,)+where_params)
1455         res=dict(cr.fetchall())
1456         for record in self.browse(cr, uid, ids, context):
1457             def _rec_get(record):
1458                 amount = res.get(record.id, 0.0)
1459                 for rec in record.child_ids:
1460                     amount += _rec_get(rec) * rec.sign
1461                 return amount
1462             res[record.id] = round(_rec_get(record), self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
1463         return res
1464
1465     def _sum_year(self, cr, uid, ids, name, args, context):
1466         if 'fiscalyear_id' in context and context['fiscalyear_id']:
1467             fiscalyear_id = context['fiscalyear_id']
1468         else:
1469             fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1470         where = ''
1471         where_params = ()
1472         if fiscalyear_id:
1473             pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1474             if pids:
1475                 where = ' and period_id IN %s'
1476                 where_params = (tuple(pids),)
1477         return self._sum(cr, uid, ids, name, args, context,
1478                 where=where, where_params=where_params)
1479
1480     def _sum_period(self, cr, uid, ids, name, args, context):
1481         if 'period_id' in context and context['period_id']:
1482             period_id = context['period_id']
1483         else:
1484             period_id = self.pool.get('account.period').find(cr, uid)
1485             if not len(period_id):
1486                 return dict.fromkeys(ids, 0.0)
1487             period_id = period_id[0]
1488         return self._sum(cr, uid, ids, name, args, context,
1489                 where=' and line.period_id=%s', where_params=(period_id,))
1490
1491     _name = 'account.tax.code'
1492     _description = 'Tax Code'
1493     _rec_name = 'code'
1494     _columns = {
1495         'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1496         'code': fields.char('Case Code', size=64),
1497         'info': fields.text('Description'),
1498         'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1499         'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1500         'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1501         'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1502         'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1503         'company_id': fields.many2one('res.company', 'Company', required=True),
1504         'sign': fields.float('Sign for parent', required=True),
1505         'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"),
1506     }
1507
1508
1509     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1510         if not args:
1511             args = []
1512         if context is None:
1513             context = {}
1514         ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1515         return self.name_get(cr, user, ids, context)
1516
1517
1518     def name_get(self, cr, uid, ids, context=None):
1519         if not len(ids):
1520             return []
1521         if isinstance(ids, (int, long)):
1522             ids = [ids]
1523         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1524         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1525                 for x in reads]
1526
1527     def _default_company(self, cr, uid, context={}):
1528         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1529         if user.company_id:
1530             return user.company_id.id
1531         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1532     _defaults = {
1533         'company_id': _default_company,
1534         'sign': lambda *args: 1.0,
1535         'notprintable': lambda *a: False,
1536     }
1537
1538     def copy(self, cr, uid, id, default=None, context=None):
1539         if default is None:
1540             default = {}
1541         default = default.copy()
1542         default.update({'line_ids': []})
1543         return super(account_tax_code, self).copy(cr, uid, id, default, context)
1544
1545     _check_recursion = check_cycle
1546     _constraints = [
1547         (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1548     ]
1549     _order = 'code,name'
1550 account_tax_code()
1551
1552 class account_tax(osv.osv):
1553     """
1554     A tax object.
1555
1556     Type: percent, fixed, none, code
1557         PERCENT: tax = price * amount
1558         FIXED: tax = price + amount
1559         NONE: no tax line
1560         CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1561             return result in the context
1562             Ex: result=round(price_unit*0.21,4)
1563     """
1564     _name = 'account.tax'
1565     _description = 'Tax'
1566     _columns = {
1567         'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1568         '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."),
1569         'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
1570         'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the tax without removing it."),
1571         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'),('balance','Balance')], 'Tax Type', required=True,
1572             help="The computation method for the tax amount."),
1573         'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True,
1574             help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1575         '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."),
1576         'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1577         'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1578         'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1579         'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1580         '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."),
1581         'python_compute':fields.text('Python Code'),
1582         'python_compute_inv':fields.text('Python Code (reverse)'),
1583         'python_applicable':fields.text('Python Code'),
1584         'tax_group': fields.selection([('vat','VAT'),('other','Other')], 'Tax Group', help="If a default tax is given in the partner it only overrides taxes from accounts (or products) in the same group."),
1585
1586         #
1587         # Fields used for the VAT declaration
1588         #
1589         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1590         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1591         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1592         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1593
1594         # Same fields for refund invoices
1595
1596         'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1597         'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1598         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1599         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1600         '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"),
1601         'company_id': fields.many2one('res.company', 'Company', required=True),
1602         'description': fields.char('Tax Code',size=32),
1603         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1604         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1605
1606     }
1607     def search(self, cr, uid, args, offset=0, limit=None, order=None,
1608             context=None, count=False):
1609         if context and context.has_key('type'):
1610             if context['type'] in ('out_invoice','out_refund'):
1611                 args.append(('type_tax_use','in',['sale','all']))
1612             elif context['type'] in ('in_invoice','in_refund'):
1613                 args.append(('type_tax_use','in',['purchase','all']))
1614         return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1615
1616     def name_get(self, cr, uid, ids, context={}):
1617         if not len(ids):
1618             return []
1619         res = []
1620         for record in self.read(cr, uid, ids, ['description','name'], context):
1621             name = record['description'] and record['description'] or record['name']
1622             res.append((record['id'],name ))
1623         return res
1624
1625     def _default_company(self, cr, uid, context={}):
1626         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1627         if user.company_id:
1628             return user.company_id.id
1629         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1630     _defaults = {
1631         'python_compute': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n# product : product.product object or None\n# partner : res.partner object or None\n\nresult = price_unit * 0.10''',
1632         'python_compute_inv': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n# product : product.product object or False\n\nresult = price_unit * 0.10''',
1633         'applicable_type': lambda *a: 'true',
1634         'type': lambda *a: 'percent',
1635         'amount': lambda *a: 0,
1636         'price_include': lambda *a: 0,
1637         'active': lambda *a: 1,
1638         'type_tax_use': lambda *a: 'all',
1639         'sequence': lambda *a: 1,
1640         'tax_group': lambda *a: 'vat',
1641         'ref_tax_sign': lambda *a: 1,
1642         'ref_base_sign': lambda *a: 1,
1643         'tax_sign': lambda *a: 1,
1644         'base_sign': lambda *a: 1,
1645         'include_base_amount': lambda *a: False,
1646         'company_id': _default_company,
1647     }
1648     _order = 'sequence'
1649
1650     def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1651         res = []
1652         for tax in taxes:
1653             if tax.applicable_type=='code':
1654                 localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id), 'product':product, 'partner':partner}
1655                 exec tax.python_applicable in localdict
1656                 if localdict.get('result', False):
1657                     res.append(tax)
1658             else:
1659                 res.append(tax)
1660         return res
1661
1662     def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1663         taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1664
1665         res = []
1666         cur_price_unit=price_unit
1667         for tax in taxes:
1668             # we compute the amount for the current tax object and append it to the result
1669
1670             data = {'id':tax.id,
1671                             'name':tax.description and tax.description + " - " + tax.name or tax.name,
1672                             'account_collected_id':tax.account_collected_id.id,
1673                             'account_paid_id':tax.account_paid_id.id,
1674                             'base_code_id': tax.base_code_id.id,
1675                             'ref_base_code_id': tax.ref_base_code_id.id,
1676                             'sequence': tax.sequence,
1677                             'base_sign': tax.base_sign,
1678                             'tax_sign': tax.tax_sign,
1679                             'ref_base_sign': tax.ref_base_sign,
1680                             'ref_tax_sign': tax.ref_tax_sign,
1681                             'price_unit': cur_price_unit,
1682                             'tax_code_id': tax.tax_code_id.id,
1683                             'ref_tax_code_id': tax.ref_tax_code_id.id,
1684             }
1685             res.append(data)
1686             if tax.type=='percent':
1687                 amount = cur_price_unit * tax.amount
1688                 data['amount'] = amount
1689
1690             elif tax.type=='fixed':
1691                 data['amount'] = tax.amount
1692                 data['tax_amount']=quantity
1693                # data['amount'] = quantity
1694             elif tax.type=='code':
1695                 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1696                 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1697                 exec tax.python_compute in localdict
1698                 amount = localdict['result']
1699                 data['amount'] = amount
1700             elif tax.type=='balance':
1701                 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1702                 data['balance'] = cur_price_unit
1703
1704             amount2 = data['amount']
1705             if len(tax.child_ids):
1706                 if tax.child_depend:
1707                     latest = res.pop()
1708                 amount = amount2
1709                 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1710                 res.extend(child_tax)
1711                 if tax.child_depend:
1712                     for r in res:
1713                         for name in ('base','ref_base'):
1714                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1715                                 r[name+'_code_id'] = latest[name+'_code_id']
1716                                 r[name+'_sign'] = latest[name+'_sign']
1717                                 r['price_unit'] = latest['price_unit']
1718                                 latest[name+'_code_id'] = False
1719                         for name in ('tax','ref_tax'):
1720                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1721                                 r[name+'_code_id'] = latest[name+'_code_id']
1722                                 r[name+'_sign'] = latest[name+'_sign']
1723                                 r['amount'] = data['amount']
1724                                 latest[name+'_code_id'] = False
1725             if tax.include_base_amount:
1726                 cur_price_unit+=amount2
1727         return res
1728
1729     def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1730         """
1731         RETURN: {
1732                 'total': 0.0,                # Total without taxes
1733                 'total_included: 0.0,        # Total with taxes
1734                 'taxes': []                  # List of taxes, see compute for the format
1735             }
1736         """
1737         precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1738         totalin = totalex = round(price_unit * quantity, precision)
1739         tin = []
1740         tex = []
1741         for tax in taxes:
1742             if tax.price_include:
1743                 tin.append(tax)
1744             else:
1745                 tex.append(tax)
1746         tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
1747         for r in tin:
1748             totalex -= r['amount']
1749         tex = self._compute(cr, uid, tex, totalex/quantity, quantity, address_id=address_id, product=product, partner=partner)
1750         for r in tex:
1751             totalin += r['amount']
1752         return {
1753             'total': totalex,
1754             'total_included': totalin,
1755             'taxes': tin + tex
1756         }
1757
1758     def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1759         logger = netsvc.Logger()
1760         logger.notifyChannel("warning", netsvc.LOG_WARNING,
1761             "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
1762         return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
1763
1764     def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1765         """
1766         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1767
1768         RETURN:
1769             [ tax ]
1770             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1771             one tax for each tax id in IDS and their childs
1772         """
1773         res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1774         total = 0.0
1775         for r in res:
1776             if r.get('balance',False):
1777                 r['amount'] = round(r['balance'] * quantity, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')) - total
1778             else:
1779                 r['amount'] = round(r['amount'] * quantity, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
1780                 total += r['amount']
1781         return res
1782
1783     def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1784         taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1785
1786         res = []
1787         taxes.reverse()
1788         cur_price_unit = price_unit
1789
1790         tax_parent_tot = 0.0
1791         for tax in taxes:
1792             if (tax.type=='percent') and not tax.include_base_amount:
1793                 tax_parent_tot += tax.amount
1794
1795         for tax in taxes:
1796             if (tax.type=='fixed') and not tax.include_base_amount:
1797                 cur_price_unit -= tax.amount
1798
1799         for tax in taxes:
1800             if tax.type=='percent':
1801                 if tax.include_base_amount:
1802                     amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1803                 else:
1804                     amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1805
1806             elif tax.type=='fixed':
1807                 amount = tax.amount
1808
1809             elif tax.type=='code':
1810                 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1811                 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1812                 exec tax.python_compute_inv in localdict
1813                 amount = localdict['result']
1814             elif tax.type=='balance':
1815                 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1816 #                data['balance'] = cur_price_unit
1817
1818
1819             if tax.include_base_amount:
1820                 cur_price_unit -= amount
1821                 todo = 0
1822             else:
1823                 todo = 1
1824             res.append({
1825                 'id': tax.id,
1826                 'todo': todo,
1827                 'name': tax.name,
1828                 'amount': amount,
1829                 'account_collected_id': tax.account_collected_id.id,
1830                 'account_paid_id': tax.account_paid_id.id,
1831                 'base_code_id': tax.base_code_id.id,
1832                 'ref_base_code_id': tax.ref_base_code_id.id,
1833                 'sequence': tax.sequence,
1834                 'base_sign': tax.base_sign,
1835                 'tax_sign': tax.tax_sign,
1836                 'ref_base_sign': tax.ref_base_sign,
1837                 'ref_tax_sign': tax.ref_tax_sign,
1838                 'price_unit': cur_price_unit,
1839                 'tax_code_id': tax.tax_code_id.id,
1840                 'ref_tax_code_id': tax.ref_tax_code_id.id,
1841             })
1842             if len(tax.child_ids):
1843                 if tax.child_depend:
1844                     del res[-1]
1845                     amount = price_unit
1846
1847             parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1848             res.extend(parent_tax)
1849
1850         total = 0.0
1851         for r in res:
1852             if r['todo']:
1853                 total += r['amount']
1854         for r in res:
1855             r['price_unit'] -= total
1856             r['todo'] = 0
1857         return res
1858
1859     def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1860         """
1861         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1862         Price Unit is a VAT included price
1863
1864         RETURN:
1865             [ tax ]
1866             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1867             one tax for each tax id in IDS and their childs
1868         """
1869         res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1870         total = 0.0
1871         for r in res:
1872             prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1873             if r.get('balance',False):
1874                 r['amount'] = round(r['balance'] * quantity, prec) - total
1875             else:
1876                 r['amount'] = round(r['amount'] * quantity, prec)
1877                 total += r['amount']
1878         return res
1879 account_tax()
1880
1881 # ---------------------------------------------------------
1882 # Account Entries Models
1883 # ---------------------------------------------------------
1884
1885 class account_model(osv.osv):
1886     _name = "account.model"
1887     _description = "Account Model"
1888     _columns = {
1889         'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
1890         'ref': fields.char('Reference', size=64),
1891         'journal_id': fields.many2one('account.journal', 'Journal', required=True),
1892         'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
1893         'legend' :fields.text('Legend', readonly=True, size=100),
1894     }
1895
1896     _defaults = {
1897         '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'),
1898     }
1899     def generate(self, cr, uid, ids, datas={}, context={}):
1900         move_ids = []
1901         for model in self.browse(cr, uid, ids, context):
1902             context.update({'date':datas['date']})
1903             period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
1904             if not period_id:
1905                 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
1906             period_id = period_id[0]
1907             move_id = self.pool.get('account.move').create(cr, uid, {
1908                 'ref': model.ref,
1909                 'period_id': period_id,
1910                 'journal_id': model.journal_id.id,
1911                 'date': context.get('date',time.strftime('%Y-%m-%d'))
1912             })
1913             move_ids.append(move_id)
1914             for line in model.lines_id:
1915                 val = {
1916                     'move_id': move_id,
1917                     'journal_id': model.journal_id.id,
1918                     'period_id': period_id
1919                 }
1920                 val.update({
1921                     'name': line.name,
1922                     'quantity': line.quantity,
1923                     'debit': line.debit,
1924                     'credit': line.credit,
1925                     'account_id': line.account_id.id,
1926                     'move_id': move_id,
1927                     'ref': line.ref,
1928                     'partner_id': line.partner_id.id,
1929                     'date': context.get('date',time.strftime('%Y-%m-%d')),
1930                     'date_maturity': time.strftime('%Y-%m-%d')
1931                 })
1932                 c = context.copy()
1933                 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
1934                 self.pool.get('account.move.line').create(cr, uid, val, context=c)
1935         return move_ids
1936 account_model()
1937
1938 class account_model_line(osv.osv):
1939     _name = "account.model.line"
1940     _description = "Account Model Entries"
1941     _columns = {
1942         'name': fields.char('Name', size=64, required=True),
1943         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
1944         'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries"),
1945         'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
1946         'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
1947
1948         'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
1949
1950         'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
1951
1952         'ref': fields.char('Reference', size=16),
1953
1954         'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
1955         'currency_id': fields.many2one('res.currency', 'Currency'),
1956
1957         'partner_id': fields.many2one('res.partner', 'Partner'),
1958         '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."),
1959         'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
1960     }
1961     _defaults = {
1962         'date': lambda *a: 'today'
1963     }
1964     _order = 'sequence'
1965     _sql_constraints = [
1966         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
1967         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
1968     ]
1969 account_model_line()
1970
1971 # ---------------------------------------------------------
1972 # Account Subscription
1973 # ---------------------------------------------------------
1974
1975
1976 class account_subscription(osv.osv):
1977     _name = "account.subscription"
1978     _description = "Account Subscription"
1979     _columns = {
1980         'name': fields.char('Name', size=64, required=True),
1981         'ref': fields.char('Reference', size=16),
1982         'model_id': fields.many2one('account.model', 'Model', required=True),
1983
1984         'date_start': fields.date('Start Date', required=True),
1985         'period_total': fields.integer('Number of Periods', required=True),
1986         'period_nbr': fields.integer('Period', required=True),
1987         'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
1988         'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
1989
1990         'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
1991     }
1992     _defaults = {
1993         'date_start': lambda *a: time.strftime('%Y-%m-%d'),
1994         'period_type': lambda *a: 'month',
1995         'period_total': lambda *a: 12,
1996         'period_nbr': lambda *a: 1,
1997         'state': lambda *a: 'draft',
1998     }
1999     def state_draft(self, cr, uid, ids, context={}):
2000         self.write(cr, uid, ids, {'state':'draft'})
2001         return False
2002
2003     def check(self, cr, uid, ids, context={}):
2004         todone = []
2005         for sub in self.browse(cr, uid, ids, context):
2006             ok = True
2007             for line in sub.lines_id:
2008                 if not line.move_id.id:
2009                     ok = False
2010                     break
2011             if ok:
2012                 todone.append(sub.id)
2013         if len(todone):
2014             self.write(cr, uid, todone, {'state':'done'})
2015         return False
2016
2017     def remove_line(self, cr, uid, ids, context={}):
2018         toremove = []
2019         for sub in self.browse(cr, uid, ids, context):
2020             for line in sub.lines_id:
2021                 if not line.move_id.id:
2022                     toremove.append(line.id)
2023         if len(toremove):
2024             self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2025         self.write(cr, uid, ids, {'state':'draft'})
2026         return False
2027
2028     def compute(self, cr, uid, ids, context={}):
2029         for sub in self.browse(cr, uid, ids, context):
2030             ds = sub.date_start
2031             for i in range(sub.period_total):
2032                 self.pool.get('account.subscription.line').create(cr, uid, {
2033                     'date': ds,
2034                     'subscription_id': sub.id,
2035                 })
2036                 if sub.period_type=='day':
2037                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2038                 if sub.period_type=='month':
2039                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2040                 if sub.period_type=='year':
2041                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2042         self.write(cr, uid, ids, {'state':'running'})
2043         return True
2044 account_subscription()
2045
2046 class account_subscription_line(osv.osv):
2047     _name = "account.subscription.line"
2048     _description = "Account Subscription Line"
2049     _columns = {
2050         'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2051         'date': fields.date('Date', required=True),
2052         'move_id': fields.many2one('account.move', 'Entry'),
2053     }
2054     _defaults = {
2055     }
2056     def move_create(self, cr, uid, ids, context={}):
2057         tocheck = {}
2058         for line in self.browse(cr, uid, ids, context):
2059             datas = {
2060                 'date': line.date,
2061             }
2062             ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2063             tocheck[line.subscription_id.id] = True
2064             self.write(cr, uid, [line.id], {'move_id':ids[0]})
2065         if tocheck:
2066             self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2067         return True
2068     _rec_name = 'date'
2069 account_subscription_line()
2070
2071 #  ---------------------------------------------------------------
2072 #   Account Templates : Account, Tax, Tax Code and chart. + Wizard
2073 #  ---------------------------------------------------------------
2074
2075 class account_tax_template(osv.osv):
2076     _name = 'account.tax.template'
2077 account_tax_template()
2078
2079 class account_account_template(osv.osv):
2080     _order = "code"
2081     _name = "account.account.template"
2082     _description ='Templates for Accounts'
2083
2084     _columns = {
2085         'name': fields.char('Name', size=128, required=True, select=True),
2086         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2087         'code': fields.char('Code', size=64),
2088         'type': fields.selection([
2089             ('receivable','Receivable'),
2090             ('payable','Payable'),
2091             ('view','View'),
2092             ('consolidation','Consolidation'),
2093             ('other','Others'),
2094             ('closed','Closed'),
2095             ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2096             "special effects in Open ERP: view can not have entries, consolidation are accounts that "\
2097             "can have children accounts for multi-company consolidations, payable/receivable are for "\
2098             "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2099         'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2100             help="These types are defined according to your country. The type contains more information "\
2101             "about the account and its specificities."),
2102         'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2103         'shortcut': fields.char('Shortcut', size=12),
2104         'note': fields.text('Note'),
2105         'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2106         'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2107         'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2108         'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2109     }
2110
2111     _defaults = {
2112         'reconcile': lambda *a: False,
2113         'type' : lambda *a :'view',
2114         'nocreate': lambda *a: False,
2115     }
2116
2117     _check_recursion = check_cycle
2118     _constraints = [
2119         (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
2120     ]
2121
2122
2123     def name_get(self, cr, uid, ids, context={}):
2124         if not len(ids):
2125             return []
2126         reads = self.read(cr, uid, ids, ['name','code'], context)
2127         res = []
2128         for record in reads:
2129             name = record['name']
2130             if record['code']:
2131                 name = record['code']+' '+name
2132             res.append((record['id'],name ))
2133         return res
2134
2135 account_account_template()
2136
2137 class account_add_tmpl_wizard(osv.osv_memory):
2138     """Add one more account from the template.
2139
2140     With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2141     _name = 'account.addtmpl.wizard'
2142
2143     def _get_def_cparent(self, cr, uid, context):
2144         acc_obj=self.pool.get('account.account')
2145         tmpl_obj=self.pool.get('account.account.template')
2146         tids=tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2147         if not tids or not tids[0]['parent_id']:
2148             return False
2149         ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2150         res = None
2151         if not ptids or not ptids[0]['code']:
2152             raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
2153             res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2154
2155         return res and res[0] or False
2156
2157     _columns = {
2158         'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2159     }
2160     _defaults = {
2161         'cparent_id': _get_def_cparent,
2162     }
2163
2164     def action_create(self,cr,uid,ids,context=None):
2165         acc_obj=self.pool.get('account.account')
2166         tmpl_obj=self.pool.get('account.account.template')
2167         data= self.read(cr, uid, ids)
2168         company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2169         account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2170         #tax_ids = []
2171         #for tax in account_template.tax_ids:
2172         #    tax_ids.append(tax_template_ref[tax.id])
2173         vals={
2174             'name': account_template.name,
2175             #'sign': account_template.sign,
2176             'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2177             'code': account_template.code,
2178             'type': account_template.type,
2179             'user_type': account_template.user_type and account_template.user_type.id or False,
2180             'reconcile': account_template.reconcile,
2181             'shortcut': account_template.shortcut,
2182             'note': account_template.note,
2183             'parent_id': data[0]['cparent_id'],
2184             # 'tax_ids': [(6,0,tax_ids)], todo!!
2185             'company_id': company_id,
2186             }
2187         new_account = acc_obj.create(cr, uid, vals)
2188         return {'type':'state', 'state': 'end' }
2189
2190     def action_cancel(self, cr, uid, ids, context=None):
2191         return { 'type': 'state', 'state': 'end' }
2192
2193 account_add_tmpl_wizard()
2194
2195 class account_tax_code_template(osv.osv):
2196
2197     _name = 'account.tax.code.template'
2198     _description = 'Tax Code Template'
2199     _order = 'code'
2200     _rec_name = 'code'
2201     _columns = {
2202         'name': fields.char('Tax Case Name', size=64, required=True),
2203         'code': fields.char('Case Code', size=64),
2204         'info': fields.text('Description'),
2205         'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2206         'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2207         'sign': fields.float('Sign for parent', required=True, help="Choose 1.00 to add the total to the parent account or -1.00 to subtract it"),
2208         'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"),
2209     }
2210
2211     _defaults = {
2212         'sign': lambda *args: 1.0,
2213         'notprintable': lambda *a: False,
2214     }
2215
2216     def name_get(self, cr, uid, ids, context=None):
2217         if not len(ids):
2218             return []
2219         if isinstance(ids, (int, long)):
2220             ids = [ids]
2221         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2222         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2223                 for x in reads]
2224
2225     _check_recursion = check_cycle
2226     _constraints = [
2227         (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2228     ]
2229     _order = 'code,name'
2230 account_tax_code_template()
2231
2232
2233 class account_chart_template(osv.osv):
2234     _name="account.chart.template"
2235     _description= "Templates for Account Chart"
2236
2237     _columns={
2238         'name': fields.char('Name', size=64, required=True),
2239         'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)], help=""),
2240         'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
2241         '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'),
2242         'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
2243         'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2244         'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2245         'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2246         'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2247         'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2248         'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2249     }
2250
2251 account_chart_template()
2252
2253 class account_tax_template(osv.osv):
2254
2255     _name = 'account.tax.template'
2256     _description = 'Templates for Taxes'
2257
2258     _columns = {
2259         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2260         'name': fields.char('Tax Name', size=64, required=True),
2261         '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."),
2262         'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2263         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
2264         '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."),
2265         '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."),
2266         'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2267         'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2268         'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2269         '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."),
2270         'python_compute':fields.text('Python Code'),
2271         'python_compute_inv':fields.text('Python Code (reverse)'),
2272         'python_applicable':fields.text('Python Code'),
2273         'tax_group': fields.selection([('vat','VAT'),('other','Other')], 'Tax Group', help="If a default tax if given in the partner it only override taxes from account (or product) of the same group."),
2274
2275         #
2276         # Fields used for the VAT declaration
2277         #
2278         'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2279         'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2280         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2281         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2282
2283         # Same fields for refund invoices
2284
2285         'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2286         'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2287         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2288         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2289         '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."),
2290         'description': fields.char('Internal Name', size=32),
2291         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,)
2292     }
2293
2294     def name_get(self, cr, uid, ids, context={}):
2295         if not len(ids):
2296             return []
2297         res = []
2298         for record in self.read(cr, uid, ids, ['description','name'], context):
2299             name = record['description'] and record['description'] or record['name']
2300             res.append((record['id'],name ))
2301         return res
2302
2303     def _default_company(self, cr, uid, context={}):
2304         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2305         if user.company_id:
2306             return user.company_id.id
2307         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2308
2309     _defaults = {
2310         'python_compute': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n# product : product.product object or None\n# partner : res.partner object or None\n\nresult = price_unit * 0.10''',
2311         'python_compute_inv': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n# product : product.product object or False\n\nresult = price_unit * 0.10''',
2312         'applicable_type': lambda *a: 'true',
2313         'type': lambda *a: 'percent',
2314         'amount': lambda *a: 0,
2315         'sequence': lambda *a: 1,
2316         'tax_group': lambda *a: 'vat',
2317         'ref_tax_sign': lambda *a: 1,
2318         'ref_base_sign': lambda *a: 1,
2319         'tax_sign': lambda *a: 1,
2320         'base_sign': lambda *a: 1,
2321         'include_base_amount': lambda *a: False,
2322         'type_tax_use': lambda *a: 'all',
2323     }
2324     _order = 'sequence'
2325
2326
2327 account_tax_template()
2328
2329 # Fiscal Position Templates
2330
2331 class account_fiscal_position_template(osv.osv):
2332     _name = 'account.fiscal.position.template'
2333     _description = 'Template for Fiscal Position'
2334
2335     _columns = {
2336         'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
2337         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2338         'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2339         'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2340     }
2341
2342 account_fiscal_position_template()
2343
2344 class account_fiscal_position_tax_template(osv.osv):
2345     _name = 'account.fiscal.position.tax.template'
2346     _description = 'Fiscal Position Template Tax Mapping'
2347     _rec_name = 'position_id'
2348
2349     _columns = {
2350         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2351         'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2352         'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2353     }
2354
2355 account_fiscal_position_tax_template()
2356
2357 class account_fiscal_position_account_template(osv.osv):
2358     _name = 'account.fiscal.position.account.template'
2359     _description = 'Fiscal Position Template Account Mapping'
2360     _rec_name = 'position_id'
2361     _columns = {
2362         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2363         'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2364         'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2365     }
2366
2367 account_fiscal_position_account_template()
2368
2369     # Multi charts of Accounts wizard
2370
2371 class wizard_multi_charts_accounts(osv.osv_memory):
2372     """
2373     Create a new account chart for a company.
2374     Wizards ask for:
2375         * a company
2376         * an account chart template
2377         * a number of digits for formatting code of non-view accounts
2378         * a list of bank accounts owned by the company
2379     Then, the wizard:
2380         * generates all accounts from the template and assigns them to the right company
2381         * generates all taxes and tax codes, changing account assignations
2382         * generates all accounting properties and assigns them correctly
2383     """
2384     _name='wizard.multi.charts.accounts'
2385     _inherit = 'res.config'
2386
2387     _columns = {
2388         'company_id':fields.many2one('res.company', 'Company', required=True),
2389         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2390         'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts', required=True),
2391         'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2392         'seq_journal':fields.boolean('Separated Journal Sequences', help="Check this box if you want to use a different sequence for each created journal. Otherwise, all will use the same sequence."),
2393     }
2394
2395     def _get_chart(self, cr, uid, context={}):
2396         ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2397         if ids:
2398             return ids[0]
2399         return False
2400     _defaults = {
2401         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, [uid], c)[0].company_id.id,
2402         'chart_template_id': _get_chart,
2403         'code_digits': lambda *a:6,
2404         'seq_journal': True
2405     }
2406
2407     def execute(self, cr, uid, ids, context=None):
2408         obj_multi = self.browse(cr, uid, ids[0])
2409         obj_acc = self.pool.get('account.account')
2410         obj_acc_tax = self.pool.get('account.tax')
2411         obj_journal = self.pool.get('account.journal')
2412         obj_sequence = self.pool.get('ir.sequence')
2413         obj_acc_template = self.pool.get('account.account.template')
2414         obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2415         obj_fiscal_position = self.pool.get('account.fiscal.position')
2416         data_pool = self.pool.get('ir.model.data')
2417
2418         # Creating Account
2419         obj_acc_root = obj_multi.chart_template_id.account_root_id
2420         tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2421         company_id = obj_multi.company_id.id
2422
2423         #new code
2424         acc_template_ref = {}
2425         tax_template_ref = {}
2426         tax_code_template_ref = {}
2427         todo_dict = {}
2428
2429         #create all the tax code
2430         children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2431         children_tax_code_template.sort()
2432         for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2433             vals={
2434                 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2435                 'code': tax_code_template.code,
2436                 'info': tax_code_template.info,
2437                 '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,
2438                 'company_id': company_id,
2439                 'sign': tax_code_template.sign,
2440             }
2441             new_tax_code = self.pool.get('account.tax.code').create(cr, uid, vals)
2442             #recording the new tax code to do the mapping
2443             tax_code_template_ref[tax_code_template.id] = new_tax_code
2444
2445         #create all the tax
2446         for tax in obj_multi.chart_template_id.tax_template_ids:
2447             #create it
2448             vals_tax = {
2449                 'name':tax.name,
2450                 'sequence': tax.sequence,
2451                 'amount':tax.amount,
2452                 'type':tax.type,
2453                 'applicable_type': tax.applicable_type,
2454                 'domain':tax.domain,
2455                 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2456                 'child_depend': tax.child_depend,
2457                 'python_compute': tax.python_compute,
2458                 'python_compute_inv': tax.python_compute_inv,
2459                 'python_applicable': tax.python_applicable,
2460                 'tax_group':tax.tax_group,
2461                 '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,
2462                 '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,
2463                 'base_sign': tax.base_sign,
2464                 'tax_sign': tax.tax_sign,
2465                 '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,
2466                 '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,
2467                 'ref_base_sign': tax.ref_base_sign,
2468                 'ref_tax_sign': tax.ref_tax_sign,
2469                 'include_base_amount': tax.include_base_amount,
2470                 'description':tax.description,
2471                 'company_id': company_id,
2472                 'type_tax_use': tax.type_tax_use
2473             }
2474             new_tax = obj_acc_tax.create(cr, uid, vals_tax)
2475             #as the accounts have not been created yet, we have to wait before filling these fields
2476             todo_dict[new_tax] = {
2477                 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2478                 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2479             }
2480             tax_template_ref[tax.id] = new_tax
2481
2482         #deactivate the parent_store functionnality on account_account for rapidity purpose
2483         self.pool._init = True
2484
2485         children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2486         children_acc_template.sort()
2487         for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2488             tax_ids = []
2489             for tax in account_template.tax_ids:
2490                 tax_ids.append(tax_template_ref[tax.id])
2491             #create the account_account
2492
2493             dig = obj_multi.code_digits
2494             code_main = account_template.code and len(account_template.code) or 0
2495             code_acc = account_template.code or ''
2496             if code_main>0 and code_main<=dig and account_template.type != 'view':
2497                 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2498             vals={
2499                 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2500                 #'sign': account_template.sign,
2501                 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2502                 'code': code_acc,
2503                 'type': account_template.type,
2504                 'user_type': account_template.user_type and account_template.user_type.id or False,
2505                 'reconcile': account_template.reconcile,
2506                 'shortcut': account_template.shortcut,
2507                 'note': account_template.note,
2508                 '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,
2509                 'tax_ids': [(6,0,tax_ids)],
2510                 'company_id': company_id,
2511             }
2512             new_account = obj_acc.create(cr, uid, vals)
2513             acc_template_ref[account_template.id] = new_account
2514         #reactivate the parent_store functionnality on account_account
2515         self.pool._init = False
2516         self.pool.get('account.account')._parent_store_compute(cr)
2517
2518         for key,value in todo_dict.items():
2519             if value['account_collected_id'] or value['account_paid_id']:
2520                 obj_acc_tax.write(cr, uid, [key], {
2521                     'account_collected_id': acc_template_ref[value['account_collected_id']],
2522                     'account_paid_id': acc_template_ref[value['account_paid_id']],
2523                 })
2524
2525         # Creating Journals
2526         vals_journal={}
2527         data_id = data_pool.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_view')])
2528         data = data_pool.browse(cr, uid, data_id[0])
2529         view_id = data.res_id
2530         
2531         seq_id = obj_sequence.search(cr, uid, [('name','=','Account Journal')])[0]
2532
2533         if obj_multi.seq_journal:
2534             seq_id_sale = obj_sequence.search(cr, uid, [('name','=','Sale Journal')])[0]
2535             seq_id_purchase = obj_sequence.search(cr, uid, [('name','=','Purchase Journal')])[0]
2536         else:
2537             seq_id_sale = seq_id
2538             seq_id_purchase = seq_id
2539
2540         vals_journal['view_id'] = view_id
2541
2542         #Sales Journal
2543         vals_journal['name'] = _('Sales Journal')
2544         vals_journal['type'] = 'sale'
2545         vals_journal['code'] = _('SAJ')
2546         vals_journal['sequence_id'] = seq_id_sale
2547
2548         if obj_multi.chart_template_id.property_account_receivable:
2549             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2550             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2551
2552         obj_journal.create(cr,uid,vals_journal)
2553
2554         # Purchase Journal
2555         vals_journal['name'] = _('Purchase Journal')
2556         vals_journal['type'] = 'purchase'
2557         vals_journal['code'] = _('EXJ')
2558         vals_journal['sequence_id'] = seq_id_purchase
2559
2560         if obj_multi.chart_template_id.property_account_payable:
2561             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2562             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2563
2564         obj_journal.create(cr,uid,vals_journal)
2565
2566         # Bank Journals
2567         data_id = data_pool.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')])
2568         data = data_pool.browse(cr, uid, data_id[0])
2569         view_id_cash = data.res_id
2570         #view_id_cash = self.pool.get('account.journal.view').search(cr, uid, [('name','=','Bank/Cash Journal View')])[0] #TOFIX: why put  fix name
2571         
2572         data_id = data_pool.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view_multi')])
2573         data = data_pool.browse(cr, uid, data_id[0])
2574         ref_acc_bank = data.res_id
2575         #ref_acc_bank = self.pool.get('account.journal.view').search(cr, uid, [('name','=','Bank/Cash Journal (Multi-Currency) View')])[0] #TOFIX: why put fix name
2576         ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2577
2578         current_num = 1
2579         for line in obj_multi.bank_accounts_id:
2580             #create the account_account for this bank journal
2581             tmp = self.pool.get('res.partner.bank').name_get(cr, uid, [line.acc_no.id])[0][1]
2582             dig = obj_multi.code_digits
2583             if ref_acc_bank.code:
2584                 try:
2585                     new_code = str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num)
2586                 except Exception,e:
2587                     new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)),'0')) + str(current_num)
2588             vals = {
2589                 'name': line.acc_no.bank and line.acc_no.bank.name+' '+tmp or tmp,
2590                 'currency_id': line.currency_id and line.currency_id.id or False,
2591                 'code': new_code,
2592                 'type': 'other',
2593                 'user_type': account_template.user_type and account_template.user_type.id or False,
2594                 'reconcile': True,
2595                 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2596                 'company_id': company_id,
2597             }
2598             acc_cash_id  = obj_acc.create(cr,uid,vals)
2599
2600             if obj_multi.seq_journal:
2601                 vals_seq={
2602                         'name': _('Bank Journal ') + vals['name'],
2603                         'code': 'account.journal',
2604                 }
2605                 seq_id = obj_sequence.create(cr,uid,vals_seq)
2606
2607             #create the bank journal
2608             vals_journal['name']= vals['name']
2609             vals_journal['code']= _('BNK') + str(current_num)
2610             vals_journal['sequence_id'] = seq_id
2611             vals_journal['type'] = 'cash'
2612             if line.currency_id:
2613                 vals_journal['view_id'] = view_id_cur
2614                 vals_journal['currency'] = line.currency_id.id
2615             else:
2616                 vals_journal['view_id'] = view_id_cash
2617             vals_journal['default_credit_account_id'] = acc_cash_id
2618             vals_journal['default_debit_account_id'] = acc_cash_id
2619             obj_journal.create(cr,uid,vals_journal)
2620
2621             current_num += 1
2622
2623         #create the properties
2624         property_obj = self.pool.get('ir.property')
2625         fields_obj = self.pool.get('ir.model.fields')
2626
2627         todo_list = [
2628             ('property_account_receivable','res.partner','account.account'),
2629             ('property_account_payable','res.partner','account.account'),
2630             ('property_account_expense_categ','product.category','account.account'),
2631             ('property_account_income_categ','product.category','account.account'),
2632             ('property_account_expense','product.template','account.account'),
2633             ('property_account_income','product.template','account.account')
2634         ]
2635         for record in todo_list:
2636             r = []
2637             r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2638             account = getattr(obj_multi.chart_template_id, record[0])
2639             field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2640             vals = {
2641                 'name': record[0],
2642                 'company_id': company_id,
2643                 'fields_id': field[0],
2644                 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2645             }
2646             if r:
2647                 #the property exist: modify it
2648                 property_obj.write(cr, uid, r, vals)
2649             else:
2650                 #create the property
2651                 property_obj.create(cr, uid, vals)
2652
2653         fp_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)])
2654
2655         if fp_ids:
2656             for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2657
2658                 vals_fp = {
2659                            'company_id' : company_id,
2660                            'name' : position.name,
2661                            }
2662                 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2663
2664                 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2665                 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2666
2667                 for tax in position.tax_ids:
2668                     vals_tax = {
2669                                 'tax_src_id' : tax_template_ref[tax.tax_src_id.id],
2670                                 'tax_dest_id' : tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2671                                 'position_id' : new_fp,
2672                                 }
2673                     obj_tax_fp.create(cr, uid, vals_tax)
2674
2675                 for acc in position.account_ids:
2676                     vals_acc = {
2677                                 'account_src_id' : acc_template_ref[acc.account_src_id.id],
2678                                 'account_dest_id' : acc_template_ref[acc.account_dest_id.id],
2679                                 'position_id' : new_fp,
2680                                 }
2681                     obj_ac_fp.create(cr, uid, vals_acc)
2682 wizard_multi_charts_accounts()
2683
2684 class account_bank_accounts_wizard(osv.osv_memory):
2685     _name='account.bank.accounts.wizard'
2686
2687     _columns = {
2688         'acc_name':fields.char('Account Name.', size=64, required=True),
2689         'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2690         'currency_id':fields.many2one('res.currency', 'Currency'),
2691         'account_type':fields.selection([('cash','Cash'),('check','Check'),('bank','Bank')], 'Type', size=32),
2692     }
2693     _defaults = {
2694         'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
2695         }
2696
2697 account_bank_accounts_wizard()
2698
2699 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
2700