*added boolean field refund_journal on account_journal
[odoo/odoo.git] / addons / account / account.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22 import time
23 import netsvc
24 from osv import fields, osv
25
26 from tools.misc import currency
27 from tools.translate import _
28 import pooler
29 import mx.DateTime
30 from mx.DateTime import RelativeDateTime, now, DateTime, localtime
31
32
33 class account_payment_term(osv.osv):
34     _name = "account.payment.term"
35     _description = "Payment Term"
36     _columns = {
37         'name': fields.char('Payment Term', size=32, translate=True, required=True),
38         'active': fields.boolean('Active'),
39         'note': fields.text('Description', translate=True),
40         'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
41     }
42     _defaults = {
43         'active': lambda *a: 1,
44     }
45     _order = "name"
46
47     def compute(self, cr, uid, id, value, date_ref=False, context={}):
48         if not date_ref:
49             date_ref = now().strftime('%Y-%m-%d')
50         pt = self.browse(cr, uid, id, context)
51         amount = value
52         result = []
53         for line in pt.line_ids:
54             if line.value=='fixed':
55                 amt = round(line.value_amount, 2)
56             elif line.value=='procent':
57                 amt = round(value * line.value_amount, 2)
58             elif line.value=='balance':
59                 amt = round(amount, 2)
60             if amt:
61                 next_date = mx.DateTime.strptime(date_ref, '%Y-%m-%d') + RelativeDateTime(days=line.days)
62                 if line.days2<0:
63                     next_date += RelativeDateTime(day=line.days2)
64                 if line.days2>0:
65                     next_date += RelativeDateTime(day=line.days2, months=1)
66                 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
67                 amount -= amt
68         return result
69
70 account_payment_term()
71
72 class account_payment_term_line(osv.osv):
73     _name = "account.payment.term.line"
74     _description = "Payment Term Line"
75     _columns = {
76         'name': fields.char('Line Name', size=32,required=True),
77         '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"),
78         'value': fields.selection([('procent','Percent'),('balance','Balance'),('fixed','Fixed Amount')], 'Value',required=True),
79         'value_amount': fields.float('Value Amount'),
80         'days': fields.integer('Number of Days',required=True, help="Number of days to add before computation of the day of month."),
81         '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 end of the month)."),
82         'payment_id': fields.many2one('account.payment.term','Payment Term', required=True, select=True),
83     }
84     _defaults = {
85         'value': lambda *a: 'balance',
86         'sequence': lambda *a: 5,
87         'days2': lambda *a: 0,
88     }
89     _order = "sequence"
90 account_payment_term_line()
91
92
93 class account_account_type(osv.osv):
94     _name = "account.account.type"
95     _description = "Account Type"
96     _columns = {
97         'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
98         'code': fields.char('Code', size=32, required=True),
99         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of account types."),
100         'partner_account': fields.boolean('Partner account'),
101         'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True),
102         'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign on Reports', required=True, help='Allows to change the displayed amount of the balance in the reports, in order to see positive results instead of negative ones in expenses accounts.'),
103     }
104     _defaults = {
105         'close_method': lambda *a: 'none',
106         'sequence': lambda *a: 5,
107         'sign': lambda *a: 1,
108     }
109     _order = "sequence"
110 account_account_type()
111
112 def _code_get(self, cr, uid, context={}):
113     acc_type_obj = self.pool.get('account.account.type')
114     ids = acc_type_obj.search(cr, uid, [])
115     res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context)
116     return [(r['code'], r['name']) for r in res]
117
118 #----------------------------------------------------------
119 # Accounts
120 #----------------------------------------------------------
121
122 class account_tax(osv.osv):
123     _name = 'account.tax'
124 account_tax()
125
126 class account_account(osv.osv):
127     _order = "parent_left"
128     _parent_order = "code"
129     _name = "account.account"
130     _description = "Account"
131     _parent_store = True
132
133     def search(self, cr, uid, args, offset=0, limit=None, order=None,
134             context=None, count=False):
135         if context is None:
136             context = {}
137         pos = 0
138
139         while pos<len(args):
140
141             if args[pos][0]=='code' and args[pos][1] in ('like','ilike') and args[pos][2]:
142                 args[pos] = ('code', '=like', str(args[pos][2].replace('%',''))+'%')
143             if args[pos][0]=='journal_id':
144                 if not args[pos][2]:
145                     del args[pos]
146                     continue
147                 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
148                 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
149                     del args[pos]
150                     continue
151                 ids3 = map(lambda x: x.code, jour.type_control_ids)
152                 ids1 = super(account_account,self).search(cr, uid, [('type','in',ids3)])
153                 ids1 += map(lambda x: x.id, jour.account_control_ids)
154                 args[pos] = ('id','in',ids1)
155             pos+=1
156
157         if context and context.has_key('consolidate_childs'): #add concolidated childs of accounts
158             ids = super(account_account,self).search(cr, uid, args, offset, limit,
159                 order, context=context, count=count)
160             for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
161                 ids.append(consolidate_child.id)
162             return ids
163
164         return super(account_account,self).search(cr, uid, args, offset, limit,
165                 order, context=context, count=count)
166
167     def _get_children_and_consol(self, cr, uid, ids, context={}):
168         #this function search for all the children and all consolidated children (recursively) of the given account ids
169         res = self.search(cr, uid, [('parent_id', 'child_of', ids)])
170         for id in res:
171             this = self.browse(cr, uid, id, context)
172             for child in this.child_consol_ids:
173                 if child.id not in res:
174                     res.append(child.id)
175         if len(res) != len(ids):
176             return self._get_children_and_consol(cr, uid, res, context)
177         return res
178
179     def __compute(self, cr, uid, ids, field_names, arg, context={}, query=''):
180         #compute the balance/debit/credit accordingly to the value of field_name for the given account ids
181         mapping = {
182             'balance': "COALESCE(SUM(l.debit) - SUM(l.credit), 0) as balance ",
183             'debit': "COALESCE(SUM(l.debit), 0) as debit ",
184             'credit': "COALESCE(SUM(l.credit), 0) as credit "
185         }
186         #get all the necessary accounts
187         ids2 = self._get_children_and_consol(cr, uid, ids, context)
188         acc_set = ",".join(map(str, ids2))
189         #compute for each account the balance/debit/credit from the move lines
190         accounts = {}
191         if ids2:
192             query = self.pool.get('account.move.line')._query_get(cr, uid,
193                     context=context)
194             cr.execute(("SELECT l.account_id as id, " +\
195                     ' , '.join(map(lambda x: mapping[x], field_names)) +
196                     "FROM " \
197                         "account_move_line l " \
198                     "WHERE " \
199                         "l.account_id IN (%s) " \
200                         "AND " + query + " " \
201                     "GROUP BY l.account_id") % (acc_set, ))
202
203             for res in cr.dictfetchall():
204                 accounts[res['id']] = res
205
206         #for the asked accounts, get from the dictionnary 'accounts' the value of it
207         res = {}
208         for id in ids:
209             res[id] = self._get_account_values(cr, uid, id, accounts, field_names, context)
210         return res
211
212     def _get_account_values(self, cr, uid, id, accounts, field_names, context={}):
213         res = {}.fromkeys(field_names, 0.0)
214         browse_rec = self.browse(cr, uid, id)
215         if browse_rec.type == 'consolidation':
216             ids2 = self.read(cr, uid, [browse_rec.id], ['child_consol_ids'], context)[0]['child_consol_ids']
217             for t in self.search(cr, uid, [('parent_id', 'child_of', [browse_rec.id])]):
218                 if t not in ids2 and t != browse_rec.id:
219                     ids2.append(t)
220             for i in ids2:
221                 tmp = self._get_account_values(cr, uid, i, accounts, field_names, context)
222                 for a in field_names:
223                     res[a] += tmp[a]
224         else:
225             ids2 = self.search(cr, uid, [('parent_id', 'child_of', [browse_rec.id])])
226             for i in ids2:
227                 for a in field_names:
228                     res[a] += accounts.get(i, {}).get(a, 0.0)
229         return res
230
231     def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
232         result = {}
233         for rec in self.browse(cr, uid, ids, context):
234             result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
235         return result
236
237     def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
238         result={}
239         for record in self.browse(cr, uid, ids, context):
240             if record.child_parent_ids:
241                 result[record.id]=[x.id for x in record.child_parent_ids]
242             else:
243                 result[record.id]=[]
244
245             if record.child_consol_ids:
246                 for acc in record.child_consol_ids:
247                     result[record.id].append(acc.id)
248
249         return result
250
251     _columns = {
252         'name': fields.char('Name', size=128, required=True, select=True),
253         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Force all moves for this account to have this secondary currency."),
254         'code': fields.char('Code', size=64, required=True),
255         'type': fields.selection([
256             ('receivable','Receivable'),
257             ('payable','Payable'),
258             ('view','View'),
259             ('consolidation','Consolidation'),
260             ('other','Others'),
261             ('closed','Closed'),
262         ], 'Internal Type', required=True,),
263
264         'user_type': fields.many2one('account.account.type', 'Account Type', required=True),
265         'parent_id': fields.many2one('account.account','Parent', ondelete='cascade'),
266         'child_parent_ids':fields.one2many('account.account','parent_id','Children'),
267         'child_consol_ids':fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
268         'child_id': fields.function(_get_child_ids, method=True, type='many2many',relation="account.account",string="Children Accounts"),
269         'balance': fields.function(__compute, digits=(16,2), method=True, string='Balance', multi='balance'),
270         'credit': fields.function(__compute, digits=(16,2), method=True, string='Credit', multi='balance'),
271         'debit': fields.function(__compute, digits=(16,2), method=True, string='Debit', multi='balance'),
272         'reconcile': fields.boolean('Reconcile', help="Check this account if the user can make a reconciliation of the entries in this account."),
273         'shortcut': fields.char('Shortcut', size=12),
274         'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
275             'account_id','tax_id', 'Default Taxes'),
276         'note': fields.text('Note'),
277         'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
278         'company_id': fields.many2one('res.company', 'Company', required=True),
279         'active': fields.boolean('Active', select=2),
280
281         'parent_left': fields.integer('Parent Left', select=1),
282         'parent_right': fields.integer('Parent Right', select=1),
283         'currency_mode': fields.selection([('current','At Date'),('average','Average Rate')], 'Outgoing Currencies Rate',
284             help=
285             'This will select how is computed the current currency rate for outgoing transactions. '\
286             'In most countries the legal method is "average" but only a few softwares are able to '\
287             'manage this. So if you import from another software, you may have to use the rate at date. ' \
288             'Incoming transactions, always use the rate at date.', \
289             required=True),
290         'check_history': fields.boolean('Display History',
291             help="Check this box if you want to print all entries when printing the General Ledger, "\
292             "otherwise it will only print its balance."),
293          'merge_invoice': fields.boolean('Merge Invoice Entries',help="Check this box if you want that all lines of "\
294             "a customer or supplier invoice using this account are created in one line only"),
295     }
296
297     def _default_company(self, cr, uid, context={}):
298         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
299         if user.company_id:
300             return user.company_id.id
301         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
302
303     _defaults = {
304         'type' : lambda *a :'view',
305         'reconcile': lambda *a: False,
306         'company_id': _default_company,
307         'active': lambda *a: True,
308         'check_history': lambda *a: True,
309         'currency_mode': lambda *a: 'current'
310     }
311
312     def _check_recursion(self, cr, uid, ids):
313         obj_self=self.browse(cr,uid,ids[0])
314         p_id=obj_self.parent_id and obj_self.parent_id.id
315         if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
316             return False
317         while(ids):
318             cr.execute('select distinct child_id from account_account_consol_rel where parent_id in ('+','.join(map(str,ids))+')')
319             child_ids = filter(None, map(lambda x:x[0], cr.fetchall()))
320             c_ids=child_ids
321             if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
322                 return False
323             while len(c_ids):
324                 s_ids=self.search(cr,uid,[('parent_id','in',c_ids)])
325                 if p_id and (p_id in s_ids):
326                     return False
327                 c_ids=s_ids
328             ids=child_ids
329         return True
330
331     _constraints = [
332         (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
333     ]
334     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
335         if not args:
336             args=[]
337         if not context:
338             context = {}
339         args = args[:]
340         ids = []
341         try:
342             if name and str(name).startswith('partner:'):
343                 part_id = int(name.split(':')[1])
344                 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
345                 args += [('id','in', (part.property_account_payable.id, part.property_account_receivable.id))]
346                 name = False
347             if name and str(name).startswith('type:'):
348                 type = name.split(':')[1]
349                 args += [('type','=', type)]
350                 name = False
351         except:
352             pass
353         if name:
354             ids = self.search(cr, user, [('code','=like',name+"%")]+ args, limit=limit)
355             if not ids:
356                 ids = self.search(cr, user, [('shortcut','=',name)]+ args, limit=limit)
357             if not ids:
358                 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
359         else:
360             ids = self.search(cr, user, args, context=context, limit=limit)
361         return self.name_get(cr, user, ids, context=context)
362
363     def name_get(self, cr, uid, ids, context={}):
364         if not len(ids):
365             return []
366         reads = self.read(cr, uid, ids, ['name','code'], context)
367         res = []
368         for record in reads:
369             name = record['name']
370             if record['code']:
371                 name = record['code']+' '+name
372             res.append((record['id'],name ))
373         return res
374
375     def copy(self, cr, uid, id, default={}, context={},done_list=[]):
376         account = self.browse(cr, uid, id, context=context)
377         new_child_ids = []
378         if not default:
379             default={}
380         default=default.copy()
381         default['parent_id'] = False
382         if account.id in done_list:
383             return False
384         done_list.append(account.id)
385         if account:
386             for child in account.child_id:
387                 child_ids=self.copy(cr, uid, child.id, default, context=context,done_list=done_list)
388                 if child_ids:
389                     new_child_ids.append(child_ids)
390             default['child_parent_ids'] = [(6, 0, new_child_ids)]
391         else:
392             default['child_parent_ids'] = False
393         return super(account_account, self).copy(cr, uid, id, default, context=context)
394
395     def write(self, cr, uid, ids, vals, context=None):
396         if not context:
397             context={}
398         if 'active' in vals and not vals['active']:
399             line_obj = self.pool.get('account.move.line')
400             account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
401             if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
402                 raise osv.except_osv(_('Error !'), _('You can not deactivate an account that contains account moves.'))
403         return super(account_account, self).write(cr, uid, ids, vals, context=context)
404 account_account()
405
406 class account_journal_view(osv.osv):
407     _name = "account.journal.view"
408     _description = "Journal View"
409     _columns = {
410         'name': fields.char('Journal View', size=64, required=True),
411         'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
412     }
413     _order = "name"
414 account_journal_view()
415
416
417 class account_journal_column(osv.osv):
418     def _col_get(self, cr, user, context={}):
419         result = []
420         cols = self.pool.get('account.move.line')._columns
421         for col in cols:
422             result.append( (col, cols[col].string) )
423         result.sort()
424         return result
425     _name = "account.journal.column"
426     _description = "Journal Column"
427     _columns = {
428         'name': fields.char('Column Name', size=64, required=True),
429         'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
430         'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
431         'sequence': fields.integer('Sequence'),
432         'required': fields.boolean('Required'),
433         'readonly': fields.boolean('Readonly'),
434     }
435     _order = "sequence"
436 account_journal_column()
437
438 class account_journal(osv.osv):
439     _name = "account.journal"
440     _description = "Journal"
441     _columns = {
442         'name': fields.char('Journal Name', size=64, required=True, translate=True),
443         'code': fields.char('Code', size=16),
444         'type': fields.selection([('sale','Sale'), ('purchase','Purchase'), ('cash','Cash'), ('general','General'), ('situation','Situation')], 'Type', size=32, required=True),
445         'refund_journal': fields.boolean('Refund Journal'),
446
447         'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
448         'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
449
450         'active': fields.boolean('Active'),
451         'view_id': fields.many2one('account.journal.view', 'View', required=True, help="Gives the view used when writing or browsing entries in this journal. The view tell Tiny 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."),
452         'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account'),
453         'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account'),
454         'centralisation': fields.boolean('Centralised counterpart', help="Check this box if you want that each entry doesn't create a counterpart but share the same counterpart for each entry of this journal."),
455         'update_posted': fields.boolean('Allow Cancelling Entries'),
456         'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals", required=True),
457         'user_id': fields.many2one('res.users', 'User', help="The responsible user of this journal"),
458         'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
459         'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
460         'entry_posted': fields.boolean('Skip \'Draft\' State for Created Entries', help='Check this box if you don\'t want that new account moves pass through the \'draft\' state and goes direclty to the \'posted state\' without any manual validation.'),
461         'company_id': fields.related('default_credit_account_id','company_id',type='many2one', relation="res.company", string="Company"),
462     }
463
464     _defaults = {
465         'active': lambda *a: 1,
466         'user_id': lambda self,cr,uid,context: uid,
467     }
468     def create(self, cr, uid, vals, context={}):
469         journal_id = super(osv.osv, self).create(cr, uid, vals, context)
470 #       journal_name = self.browse(cr, uid, [journal_id])[0].code
471 #       periods = self.pool.get('account.period')
472 #       ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
473 #       for period in periods.browse(cr, uid, ids):
474 #           self.pool.get('account.journal.period').create(cr, uid, {
475 #               'name': (journal_name or '')+':'+(period.code or ''),
476 #               'journal_id': journal_id,
477 #               'period_id': period.id
478 #           })
479         return journal_id
480     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
481         if not args:
482             args=[]
483         if not context:
484             context={}
485         ids = []
486         if name:
487             ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
488         if not ids:
489             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
490         return self.name_get(cr, user, ids, context=context)
491 account_journal()
492
493 class account_fiscalyear(osv.osv):
494     _name = "account.fiscalyear"
495     _description = "Fiscal Year"
496     _columns = {
497         'name': fields.char('Fiscal Year', size=64, required=True),
498         'code': fields.char('Code', size=6, required=True),
499         'date_start': fields.date('Start date', required=True),
500         'date_stop': fields.date('End date', required=True),
501         'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
502         'state': fields.selection([('draft','Draft'), ('done','Done')], 'Status', redonly=True),
503     }
504
505     _defaults = {
506         'state': lambda *a: 'draft',
507     }
508     _order = "date_start"
509
510     def _check_duration(self,cr,uid,ids):
511         obj_fy=self.browse(cr,uid,ids[0])
512         if obj_fy.date_stop < obj_fy.date_start:
513             return False
514         return True
515
516     _constraints = [
517         (_check_duration, 'Error ! The date duration of the Fiscal Year is invalid. ', ['date_stop'])
518     ]
519
520     def create_period3(self,cr, uid, ids, context={}):
521         return self.create_period(cr, uid, ids, context, 3)
522
523     def create_period(self,cr, uid, ids, context={}, interval=1):
524         for fy in self.browse(cr, uid, ids, context):
525             dt = fy.date_start
526             ds = mx.DateTime.strptime(fy.date_start, '%Y-%m-%d')
527             while ds.strftime('%Y-%m-%d')<fy.date_stop:
528                 de = ds + RelativeDateTime(months=interval, days=-1)
529
530                 if de.strftime('%Y-%m-%d')>fy.date_stop:
531                     de=mx.DateTime.strptime(fy.date_stop, '%Y-%m-%d')
532
533                 self.pool.get('account.period').create(cr, uid, {
534                     'name': ds.strftime('%m/%Y'),
535                     'code': ds.strftime('%m/%Y'),
536                     'date_start': ds.strftime('%Y-%m-%d'),
537                     'date_stop': de.strftime('%Y-%m-%d'),
538                     'fiscalyear_id': fy.id,
539                 })
540                 ds = ds + RelativeDateTime(months=interval)
541         return True
542
543     def find(self, cr, uid, dt=None, exception=True, context={}):
544         if not dt:
545             dt = time.strftime('%Y-%m-%d')
546         ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
547         if not ids:
548             if exception:
549                 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
550             else:
551                 return False
552         return ids[0]
553 account_fiscalyear()
554
555 class account_period(osv.osv):
556     _name = "account.period"
557     _description = "Account period"
558     _columns = {
559         'name': fields.char('Period Name', size=64, required=True),
560         'code': fields.char('Code', size=12),
561         'date_start': fields.date('Start of period', required=True, states={'done':[('readonly',True)]}),
562         'date_stop': fields.date('End of period', required=True, states={'done':[('readonly',True)]}),
563         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
564         'state': fields.selection([('draft','Draft'), ('done','Done')], 'Status', readonly=True)
565     }
566     _defaults = {
567         'state': lambda *a: 'draft',
568     }
569     _order = "date_start"
570
571     def _check_duration(self,cr,uid,ids):
572         obj_period=self.browse(cr,uid,ids[0])
573         if obj_period.date_stop < obj_period.date_start:
574             return False
575         return True
576
577     def _check_year_limit(self,cr,uid,ids):
578         obj_period=self.browse(cr,uid,ids[0])
579         if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or obj_period.fiscalyear_id.date_stop < obj_period.date_start or obj_period.fiscalyear_id.date_start > obj_period.date_start or obj_period.fiscalyear_id.date_start > obj_period.date_stop:
580             return False
581         return True
582
583     _constraints = [
584         (_check_duration, 'Error ! The date duration of the Period(s) is invalid. ', ['date_stop']),
585         (_check_year_limit, 'Error ! The date duration of the Period(s) should be within the limit of the Fiscal year. ', ['date_stop'])
586     ]
587
588     def next(self, cr, uid, period, step, context={}):
589         ids = self.search(cr, uid, [('date_start','>',period.date_start)])
590         if len(ids)>=step:
591             return ids[step-1]
592         return False
593
594     def find(self, cr, uid, dt=None, context={}):
595         if not dt:
596             dt = time.strftime('%Y-%m-%d')
597 #CHECKME: shouldn't we check the state of the period?
598         ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
599         if not ids:
600             raise osv.except_osv(_('Error !'), _('No period defined for this date !\nPlease create a fiscal year.'))
601         return ids
602
603     def action_draft(self, cr, uid, ids, *args):
604         users_roles = self.pool.get('res.users').browse(cr, uid, uid).roles_id
605         for role in users_roles:
606             if role.name=='Period':
607                 mode = 'draft'
608                 for id in ids:
609                     cr.execute('update account_journal_period set state=%s where period_id=%d', (mode, id))
610                     cr.execute('update account_period set state=%s where id=%d', (mode, id))
611         return True
612
613 account_period()
614
615 class account_journal_period(osv.osv):
616     _name = "account.journal.period"
617     _description = "Journal - Period"
618
619     def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
620         result = {}.fromkeys(ids, 'STOCK_NEW')
621         for r in self.read(cr, uid, ids, ['state']):
622             result[r['id']] = {
623                 'draft': 'STOCK_NEW',
624                 'printed': 'STOCK_PRINT_PREVIEW',
625                 'done': 'STOCK_DIALOG_AUTHENTICATION',
626             }.get(r['state'], 'STOCK_NEW')
627         return result
628
629     _columns = {
630         'name': fields.char('Journal-Period Name', size=64, required=True),
631         'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
632         'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
633         'icon': fields.function(_icon_get, method=True, string='Icon', type='string'),
634         'active': fields.boolean('Active', required=True),
635         'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'Status', required=True, readonly=True)
636     }
637
638     def _check(self, cr, uid, ids, context={}):
639         for obj in self.browse(cr, uid, ids, context):
640             cr.execute('select * from account_move_line where journal_id=%d and period_id=%d limit 1', (obj.journal_id.id, obj.period_id.id))
641             res = cr.fetchall()
642             if res:
643                 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
644         return True
645
646     def write(self, cr, uid, ids, vals, context={}):
647         self._check(cr, uid, ids, context)
648         return super(account_journal_period, self).write(cr, uid, ids, vals, context)
649
650     def create(self, cr, uid, vals, context={}):
651         period_id=vals.get('period_id',False)
652         if period_id:
653             period = self.pool.get('account.period').browse(cr, uid,period_id)
654             vals['state']=period.state
655         return super(account_journal_period, self).create(cr, uid, vals, context)
656
657     def unlink(self, cr, uid, ids, context={}):
658         self._check(cr, uid, ids, context)
659         return super(account_journal_period, self).unlink(cr, uid, ids, context)
660
661     _defaults = {
662         'state': lambda *a: 'draft',
663         'active': lambda *a: True,
664     }
665     _order = "period_id"
666
667 account_journal_period()
668
669 class account_fiscalyear(osv.osv):
670     _inherit = "account.fiscalyear"
671     _description = "Fiscal Year"
672     _columns = {
673         'start_journal_period_id':fields.many2one('account.journal.period','New Entries Journal'),
674         'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
675     }
676
677 account_fiscalyear()
678 #----------------------------------------------------------
679 # Entries
680 #----------------------------------------------------------
681 class account_move(osv.osv):
682     _name = "account.move"
683     _description = "Account Entry"
684
685     def name_get(self, cursor, user, ids, context=None):
686         if not len(ids):
687             return []
688         res=[]
689         data_move = self.pool.get('account.move').browse(cursor,user,ids)
690         for move in data_move:
691             if move.state=='draft':
692                 name = '*' + str(move.id)
693             else:
694                 name = move.name
695             res.append((move.id, name))
696         return res
697
698
699     def _get_period(self, cr, uid, context):
700         periods = self.pool.get('account.period').find(cr, uid)
701         if periods:
702             return periods[0]
703         else:
704             return False
705
706     def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
707         if not ids: return {}
708         cr.execute('select move_id,sum(debit) from account_move_line where move_id in ('+','.join(map(str,ids))+') group by move_id')
709         result = dict(cr.fetchall())
710         for id in ids:
711             result.setdefault(id, 0.0)
712         return result
713
714     _columns = {
715         'name': fields.char('Entry Number', size=64, required=True),
716         'ref': fields.char('Ref', size=64),
717         'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
718         'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
719         'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'Status', required=True, readonly=True),
720         'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
721         'to_check': fields.boolean('To Be Verified'),
722         'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
723         'amount': fields.function(_amount_compute, method=True, string='Amount', digits=(16,2), store=True),
724         'type': fields.selection([
725             ('pay_voucher','Cash Payment'),
726             ('bank_pay_voucher','Bank Payment'),
727             ('rec_voucher','Cash Receipt'),
728             ('bank_rec_voucher','Bank Receipt'),
729             ('cont_voucher','Contra'),
730             ('journal_sale_vou','Journal Sale'),
731             ('journal_pur_voucher','Journal Purchase'),
732             ('journal_voucher','Journal Voucher'),
733         ],'Type', readonly=True, select=True, states={'draft':[('readonly',False)]}),
734     }
735     _defaults = {
736         'name': lambda *a: '/',
737         'state': lambda *a: 'draft',
738         'period_id': _get_period,
739         'type' : lambda *a : 'journal_voucher',
740     }
741
742     def _check_centralisation(self, cursor, user, ids):
743         for move in self.browse(cursor, user, ids):
744             if move.journal_id.centralisation:
745                 move_ids = self.search(cursor, user, [
746                     ('period_id', '=', move.period_id.id),
747                     ('journal_id', '=', move.journal_id.id),
748                     ])
749                 if len(move_ids) > 1:
750                     return False
751         return True
752
753     def _check_period_journal(self, cursor, user, ids):
754         for move in self.browse(cursor, user, ids):
755             for line in move.line_id:
756                 if line.period_id.id != move.period_id.id:
757                     return False
758                 if line.journal_id.id != move.journal_id.id:
759                     return False
760         return True
761
762     _constraints = [
763         (_check_centralisation,
764             'You can not create more than one move per period on centralized journal',
765             ['journal_id']),
766         (_check_period_journal,
767             'You can not create entries on different period/journal in the same move',
768             ['line_id']),
769     ]
770     def post(self, cr, uid, ids, context=None):
771         if self.validate(cr, uid, ids, context) and len(ids):
772             for move in self.browse(cr, uid, ids):
773                 if move.name =='/':
774                     new_name = False
775                     journal = move.journal_id
776                     if journal.sequence_id:
777                         new_name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
778                     else:
779                         raise osv.except_osv(_('Error'), _('No sequence defined in the journal !'))
780                     if new_name:
781                         self.write(cr, uid, [move.id], {'name':new_name})
782
783             cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('posted',))
784         else:
785             raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non balanced entry !'))
786         return True
787
788     def button_validate(self, cursor, user, ids, context=None):
789         return self.post(cursor, user, ids, context=context)
790
791     def button_cancel(self, cr, uid, ids, context={}):
792         for line in self.browse(cr, uid, ids, context):
793             if not line.journal_id.update_posted:
794                 raise osv.except_osv(_('Error !'), _('You can not modify a posted entry of this journal !'))
795         if len(ids):
796             cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('draft',))
797         return True
798
799     def write(self, cr, uid, ids, vals, context={}):
800         c = context.copy()
801         c['novalidate'] = True
802         result = super(osv.osv, self).write(cr, uid, ids, vals, c)
803         self.validate(cr, uid, ids, context)
804         return result
805
806     #
807     # TODO: Check if period is closed !
808     #
809     def create(self, cr, uid, vals, context={}):
810         if 'line_id' in vals:
811             if 'journal_id' in vals:
812                 for l in vals['line_id']:
813                     if not l[0]:
814                         l[2]['journal_id'] = vals['journal_id']
815                 context['journal_id'] = vals['journal_id']
816             if 'period_id' in vals:
817                 for l in vals['line_id']:
818                     if not l[0]:
819                         l[2]['period_id'] = vals['period_id']
820                 context['period_id'] = vals['period_id']
821             else:
822                 default_period = self._get_period(cr, uid, context)
823                 for l in vals['line_id']:
824                     if not l[0]:
825                         l[2]['period_id'] = default_period
826                 context['period_id'] = default_period
827
828         accnt_journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'])
829         if 'line_id' in vals:
830             c = context.copy()
831             c['novalidate'] = True
832             result = super(account_move, self).create(cr, uid, vals, c)
833             self.validate(cr, uid, [result], context)
834         else:
835             result = super(account_move, self).create(cr, uid, vals, context)
836         return result
837
838     def copy(self, cr, uid, id, default=None, context=None):
839         if default is None:
840             default = {}
841         default = default.copy()
842         default.update({'state':'draft', 'name':'/',})
843         return super(account_move, self).copy(cr, uid, id, default, context)
844
845     def unlink(self, cr, uid, ids, context={}, check=True):
846         toremove = []
847         for move in self.browse(cr, uid, ids, context):
848             if move['state'] <> 'draft':
849                 raise osv.except_osv(_('UserError'),
850                         _('You can not delete posted movement: "%s"!') % \
851                                 move['name'])
852             line_ids = map(lambda x: x.id, move.line_id)
853             context['journal_id'] = move.journal_id.id
854             context['period_id'] = move.period_id.id
855             self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
856             toremove.append(move.id)
857         result = super(account_move, self).unlink(cr, uid, toremove, context)
858         return result
859
860     def _compute_balance(self, cr, uid, id, context={}):
861         move = self.browse(cr, uid, [id])[0]
862         amount = 0
863         for line in move.line_id:
864             amount+= (line.debit - line.credit)
865         return amount
866
867     def _centralise(self, cr, uid, move, mode):
868         if mode=='credit':
869             account_id = move.journal_id.default_debit_account_id.id
870             mode2 = 'debit'
871             if not account_id:
872                 raise osv.except_osv(_('UserError'),
873                         _('There is no default default debit account defined \n' \
874                                 'on journal "%s"') % move.journal_id.name)
875         else:
876             account_id = move.journal_id.default_credit_account_id.id
877             mode2 = 'credit'
878             if not account_id:
879                 raise osv.except_osv(_('UserError'),
880                         _('There is no default default credit account defined \n' \
881                                 'on journal "%s"') % move.journal_id.name)
882
883         # find the first line of this move with the current mode
884         # or create it if it doesn't exist
885         cr.execute('select id from account_move_line where move_id=%d and centralisation=%s limit 1', (move.id, mode))
886         res = cr.fetchone()
887         if res:
888             line_id = res[0]
889         else:
890             line_id = self.pool.get('account.move.line').create(cr, uid, {
891                 'name': 'Centralisation '+mode,
892                 'centralisation': mode,
893                 'account_id': account_id,
894                 'move_id': move.id,
895                 'journal_id': move.journal_id.id,
896                 'period_id': move.period_id.id,
897                 'date': move.period_id.date_stop,
898                 'debit': 0.0,
899                 'credit': 0.0,
900             }, {'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
901
902         # find the first line of this move with the other mode
903         # so that we can exclude it from our calculation
904         cr.execute('select id from account_move_line where move_id=%d and centralisation=%s limit 1', (move.id, mode2))
905         res = cr.fetchone()
906         if res:
907             line_id2 = res[0]
908         else:
909             line_id2 = 0
910
911         cr.execute('select sum('+mode+') from account_move_line where move_id=%d and id<>%d', (move.id, line_id2))
912         result = cr.fetchone()[0] or 0.0
913         cr.execute('update account_move_line set '+mode2+'=%f where id=%d', (result, line_id))
914         return True
915
916     #
917     # Validate a balanced move. If it is a centralised journal, create a move.
918     #
919     def validate(self, cr, uid, ids, context={}):
920         ok = True
921         for move in self.browse(cr, uid, ids, context):
922             journal = move.journal_id
923             amount = 0
924             line_ids = []
925             line_draft_ids = []
926             company_id=None
927             for line in move.line_id:
928                 amount += line.debit - line.credit
929                 line_ids.append(line.id)
930                 if line.state=='draft':
931                     line_draft_ids.append(line.id)
932
933                 if not company_id:
934                     company_id = line.account_id.company_id.id
935                 if not company_id == line.account_id.company_id.id:
936                     raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
937
938                 if line.account_id.currency_id:
939                     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):
940                             raise osv.except_osv(_('Error'), _("""Couldn't create move with currency different than 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)))
941
942             if abs(amount) < 0.0001:
943                 if not len(line_draft_ids):
944                     continue
945                 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
946                     'journal_id': move.journal_id.id,
947                     'period_id': move.period_id.id,
948                     'state': 'valid'
949                 }, context, check=False)
950                 todo = []
951                 account = {}
952                 account2 = {}
953                 if journal.type not in ('purchase','sale'):
954                     continue
955
956                 for line in move.line_id:
957                     if move.journal_id.type == 'sale':
958                         if line.debit:
959                             field_base = 'ref_'
960                             key = 'account_paid_id'
961                         else:
962                             field_base = ''
963                             key = 'account_collected_id'
964                     else:
965                         if line.debit:
966                             field_base = ''
967                             key = 'account_collected_id'
968                         else:
969                             field_base = 'ref_'
970                             key = 'account_paid_id'
971                     if line.account_id.tax_ids:
972                         code = amount = False
973                         for tax in line.account_id.tax_ids:
974                             if tax.tax_code_id:
975                                 acc = getattr(tax, key).id
976                                 account[acc] = (getattr(tax,
977                                     field_base + 'tax_code_id').id,
978                                     getattr(tax, field_base + 'tax_sign'))
979                                 account2[(acc,getattr(tax,
980                                     field_base + 'tax_code_id').id)] = (getattr(tax,
981                                         field_base + 'tax_code_id').id,
982                                         getattr(tax, field_base + 'tax_sign'))
983                                 code = getattr(tax, field_base + 'base_code_id').id
984                                 amount = getattr(tax, field_base+'base_sign') * \
985                                         (line.debit + line.credit)
986                                 break
987                         if code and not (line.tax_code_id or line.tax_amount):
988                             self.pool.get('account.move.line').write(cr, uid,
989                                     [line.id], {
990                                 'tax_code_id': code,
991                                 'tax_amount': amount
992                             }, context=context, check=False)
993                     else:
994                         todo.append(line)
995                 for line in todo:
996                     code = amount = 0
997                     key = (line.account_id.id, line.tax_code_id.id)
998                     if key in account2:
999                         code = account2[key][0]
1000                         amount = account2[key][1] * (line.debit + line.credit)
1001                     elif line.account_id.id in account:
1002                         code = account[line.account_id.id][0]
1003                         amount = account[line.account_id.id][1] * (line.debit + line.credit)
1004                     if (code or amount) and not (line.tax_code_id or line.tax_amount):
1005                         self.pool.get('account.move.line').write(cr, uid, [line.id], {
1006                             'tax_code_id': code,
1007                             'tax_amount': amount
1008                         }, context, check=False)
1009                 #
1010                 # Compute VAT
1011                 #
1012                 continue
1013             if journal.centralisation:
1014                 self._centralise(cr, uid, move, 'debit')
1015                 self._centralise(cr, uid, move, 'credit')
1016                 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1017                     'state': 'valid'
1018                 }, context, check=False)
1019                 continue
1020             else:
1021                 self.pool.get('account.move.line').write(cr, uid, line_ids, {
1022                     'journal_id': move.journal_id.id,
1023                     'period_id': move.period_id.id,
1024                     #'tax_code_id': False,
1025                     #'tax_amount': False,
1026                     'state': 'draft'
1027                 }, context, check=False)
1028                 ok = False
1029         return ok
1030 account_move()
1031
1032 class account_move_reconcile(osv.osv):
1033     _name = "account.move.reconcile"
1034     _description = "Account Reconciliation"
1035     _columns = {
1036         'name': fields.char('Name', size=64, required=True),
1037         'type': fields.char('Type', size=16, required=True),
1038         'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry lines'),
1039         'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1040         'create_date': fields.date('Creation date', readonly=True),
1041     }
1042     _defaults = {
1043         'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1044     }
1045     def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1046         for rec in self.browse(cr, uid, ids, context):
1047             total = 0.0
1048             for line in rec.line_partial_ids:
1049                 total += (line.debit or 0.0) - (line.credit or 0.0)
1050         if not total:
1051             self.pool.get('account.move.line').write(cr, uid,
1052                 map(lambda x: x.id, rec.line_partial_ids),
1053                 {'reconcile_id': rec.id }
1054             )
1055         return True
1056
1057     def name_get(self, cr, uid, ids, context=None):
1058         if not len(ids):
1059             return []
1060         result = []
1061         for r in self.browse(cr, uid, ids, context):
1062             total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1063             if total:
1064                 name = '%s (%.2f)' % (r.name, total)
1065                 result.append((r.id,name))
1066             else:
1067                 result.append((r.id,r.name))
1068         return result
1069
1070
1071 account_move_reconcile()
1072
1073 #----------------------------------------------------------
1074 # Tax
1075 #----------------------------------------------------------
1076 """
1077 a documenter
1078 child_depend: la taxe depend des taxes filles
1079 """
1080 class account_tax_code(osv.osv):
1081     """
1082     A code for the tax object.
1083
1084     This code is used for some tax declarations.
1085     """
1086     def _sum(self, cr, uid, ids, name, args, context, where =''):
1087         ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
1088         acc_set = ",".join(map(str, ids2))
1089         if context.get('based_on', 'invoices') == 'payments':
1090             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1091                     FROM account_move_line AS line, \
1092                         account_move AS move \
1093                         LEFT JOIN account_invoice invoice ON \
1094                             (invoice.move_id = move.id) \
1095                     WHERE line.tax_code_id in ('+acc_set+') '+where+' \
1096                         AND move.id = line.move_id \
1097                         AND ((invoice.state = \'paid\') \
1098                             OR (invoice.id IS NULL)) \
1099                     GROUP BY line.tax_code_id')
1100         else:
1101             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1102                     FROM account_move_line AS line \
1103                     WHERE line.tax_code_id in ('+acc_set+') '+where+' \
1104                     GROUP BY line.tax_code_id')
1105         res=dict(cr.fetchall())
1106         for record in self.browse(cr, uid, ids, context):
1107             def _rec_get(record):
1108                 amount = res.get(record.id, 0.0)
1109                 for rec in record.child_ids:
1110                     amount += _rec_get(rec) * rec.sign
1111                 return amount
1112             res[record.id] = round(_rec_get(record), 2)
1113         return res
1114
1115     def _sum_period(self, cr, uid, ids, name, args, context):
1116         if 'period_id' in context and context['period_id']:
1117             period_id = context['period_id']
1118         else:
1119             period_id = self.pool.get('account.period').find(cr, uid)
1120             if not len(period_id):
1121                 return dict.fromkeys(ids, 0.0)
1122             period_id = period_id[0]
1123         return self._sum(cr, uid, ids, name, args, context,
1124                 where=' and line.period_id='+str(period_id))
1125
1126     _name = 'account.tax.code'
1127     _description = 'Tax Code'
1128     _rec_name = 'code'
1129     _columns = {
1130         'name': fields.char('Tax Case Name', size=64, required=True),
1131         'code': fields.char('Case Code', size=64),
1132         'info': fields.text('Description'),
1133         'sum': fields.function(_sum, method=True, string="Year Sum"),
1134         'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1135         'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1136         'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Childs Codes'),
1137         'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1138         'company_id': fields.many2one('res.company', 'Company', required=True),
1139         'sign': fields.float('Sign for parent', required=True),
1140     }
1141
1142     def name_get(self, cr, uid, ids, context=None):
1143         if not len(ids):
1144             return []
1145         if isinstance(ids, (int, long)):
1146             ids = [ids]
1147         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1148         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1149                 for x in reads]
1150
1151     def _default_company(self, cr, uid, context={}):
1152         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1153         if user.company_id:
1154             return user.company_id.id
1155         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1156     _defaults = {
1157         'company_id': _default_company,
1158         'sign': lambda *args: 1.0,
1159     }
1160     def _check_recursion(self, cr, uid, ids):
1161         level = 100
1162         while len(ids):
1163             cr.execute('select distinct parent_id from account_tax_code where id in ('+','.join(map(str,ids))+')')
1164             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1165             if not level:
1166                 return False
1167             level -= 1
1168         return True
1169
1170     _constraints = [
1171         (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1172     ]
1173     _order = 'code,name'
1174 account_tax_code()
1175
1176 class account_tax(osv.osv):
1177     """
1178     A tax object.
1179
1180     Type: percent, fixed, none, code
1181         PERCENT: tax = price * amount
1182         FIXED: tax = price + amount
1183         NONE: no tax line
1184         CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1185             return result in the context
1186             Ex: result=round(price_unit*0.21,4)
1187     """
1188     _name = 'account.tax'
1189     _description = 'Tax'
1190     _columns = {
1191         'name': fields.char('Tax Name', size=64, required=True, help="This name will be used to be displayed on reports"),
1192         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the taxes lines from the lowest sequences to the higher ones. The order is important if you have a tax that have several tax childs. In this case, the evaluation order is important."),
1193         'amount': fields.float('Amount', required=True, digits=(14,4)),
1194         'active': fields.boolean('Active'),
1195         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
1196         'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True),
1197         'domain':fields.char('Domain', size=32, help="This field is only used if you develop your own module allowing developpers to create specific taxes in a custom domain."),
1198         'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1199         'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1200         'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1201         'child_ids':fields.one2many('account.tax', 'parent_id', 'Childs Tax Account'),
1202         'child_depend':fields.boolean('Tax on Childs', help="Indicate if the tax computation is based on the value computed for the computation of child taxes or based on the total amount."),
1203         'python_compute':fields.text('Python Code'),
1204         'python_compute_inv':fields.text('Python Code (reverse)'),
1205         'python_applicable':fields.text('Python Code'),
1206         '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."),
1207
1208         #
1209         # Fields used for the VAT declaration
1210         #
1211         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1212         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1213         'base_sign': fields.float('Base Code Sign', help="Usualy 1 or -1."),
1214         'tax_sign': fields.float('Tax Code Sign', help="Usualy 1 or -1."),
1215
1216         # Same fields for refund invoices
1217
1218         'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1219         'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1220         'ref_base_sign': fields.float('Base Code Sign', help="Usualy 1 or -1."),
1221         'ref_tax_sign': fields.float('Tax Code Sign', help="Usualy 1 or -1."),
1222         'include_base_amount': fields.boolean('Include in base amount', help="Indicate if the amount of tax must be included in the base amount for the computation of the next taxes"),
1223         'company_id': fields.many2one('res.company', 'Company', required=True),
1224         'description': fields.char('Internal Name',size=32),
1225     }
1226
1227     def name_get(self, cr, uid, ids, context={}):
1228         if not len(ids):
1229             return []
1230         res = []
1231         for record in self.read(cr, uid, ids, ['description','name'], context):
1232             name = record['description'] and record['description'] or record['name']
1233             res.append((record['id'],name ))
1234         return res
1235
1236     def _default_company(self, cr, uid, context={}):
1237         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1238         if user.company_id:
1239             return user.company_id.id
1240         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1241     _defaults = {
1242         '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''',
1243         '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''',
1244         'applicable_type': lambda *a: 'true',
1245         'type': lambda *a: 'percent',
1246         'amount': lambda *a: 0,
1247         'active': lambda *a: 1,
1248         'sequence': lambda *a: 1,
1249         'tax_group': lambda *a: 'vat',
1250         'ref_tax_sign': lambda *a: 1,
1251         'ref_base_sign': lambda *a: 1,
1252         'tax_sign': lambda *a: 1,
1253         'base_sign': lambda *a: 1,
1254         'include_base_amount': lambda *a: False,
1255         'company_id': _default_company,
1256     }
1257     _order = 'sequence'
1258
1259     def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1260         res = []
1261         for tax in taxes:
1262             if tax.applicable_type=='code':
1263                 localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id), 'product':product, 'partner':partner}
1264                 exec tax.python_applicable in localdict
1265                 if localdict.get('result', False):
1266                     res.append(tax)
1267             else:
1268                 res.append(tax)
1269         return res
1270
1271     def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1272         taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1273
1274         res = []
1275         cur_price_unit=price_unit
1276         for tax in taxes:
1277             # we compute the amount for the current tax object and append it to the result
1278
1279             if tax.type=='percent':
1280                 amount = cur_price_unit * tax.amount
1281                 res.append({'id':tax.id,
1282                             'name':tax.name,
1283                             'amount':amount,
1284                             'account_collected_id':tax.account_collected_id.id,
1285                             'account_paid_id':tax.account_paid_id.id,
1286                             'base_code_id': tax.base_code_id.id,
1287                             'ref_base_code_id': tax.ref_base_code_id.id,
1288                             'sequence': tax.sequence,
1289                             'base_sign': tax.base_sign,
1290                             'tax_sign': tax.tax_sign,
1291                             'ref_base_sign': tax.ref_base_sign,
1292                             'ref_tax_sign': tax.ref_tax_sign,
1293                             'price_unit': cur_price_unit,
1294                             'tax_code_id': tax.tax_code_id.id,
1295                             'ref_tax_code_id': tax.ref_tax_code_id.id,
1296                             })
1297
1298             elif tax.type=='fixed':
1299                 res.append({'id':tax.id,
1300                             'name':tax.name,
1301                             'amount':tax.amount,
1302                             'account_collected_id':tax.account_collected_id.id,
1303                             'account_paid_id':tax.account_paid_id.id,
1304                             'base_code_id': tax.base_code_id.id,
1305                             'ref_base_code_id': tax.ref_base_code_id.id,
1306                             'sequence': tax.sequence,
1307                             'base_sign': tax.base_sign,
1308                             'tax_sign': tax.tax_sign,
1309                             'ref_base_sign': tax.ref_base_sign,
1310                             'ref_tax_sign': tax.ref_tax_sign,
1311                             'price_unit': 1,
1312                             'tax_code_id': tax.tax_code_id.id,
1313                             'ref_tax_code_id': tax.ref_tax_code_id.id,})
1314             elif tax.type=='code':
1315                 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1316                 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1317                 exec tax.python_compute in localdict
1318                 amount = localdict['result']
1319                 res.append({
1320                     'id': tax.id,
1321                     'name': tax.name,
1322                     'amount': amount,
1323                     'account_collected_id': tax.account_collected_id.id,
1324                     'account_paid_id': tax.account_paid_id.id,
1325                     'base_code_id': tax.base_code_id.id,
1326                     'ref_base_code_id': tax.ref_base_code_id.id,
1327                     'sequence': tax.sequence,
1328                     'base_sign': tax.base_sign,
1329                     'tax_sign': tax.tax_sign,
1330                     'ref_base_sign': tax.ref_base_sign,
1331                     'ref_tax_sign': tax.ref_tax_sign,
1332                     'price_unit': cur_price_unit,
1333                     'tax_code_id': tax.tax_code_id.id,
1334                     'ref_tax_code_id': tax.ref_tax_code_id.id,
1335                 })
1336             amount2 = res[-1]['amount']
1337             if len(tax.child_ids):
1338                 if tax.child_depend:
1339                     del res[-1]
1340                 amount = amount2
1341                 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner)
1342                 res.extend(child_tax)
1343             if tax.include_base_amount:
1344                 cur_price_unit+=amount2
1345         return res
1346
1347     def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1348
1349         """
1350         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1351
1352         RETURN:
1353             [ tax ]
1354             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1355             one tax for each tax id in IDS and their childs
1356         """
1357         res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner)
1358         for r in res:
1359             r['amount'] *= quantity
1360         return res
1361
1362     def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1363         taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1364
1365         res = []
1366         taxes.reverse()
1367         cur_price_unit=price_unit
1368         for tax in taxes:
1369             # we compute the amount for the current tax object and append it to the result
1370
1371             if tax.type=='percent':
1372                 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1373                 res.append({'id':tax.id,
1374                             'name':tax.name,
1375                             'amount':amount,
1376                             'account_collected_id':tax.account_collected_id.id,
1377                             'account_paid_id':tax.account_paid_id.id,
1378                             'base_code_id': tax.base_code_id.id,
1379                             'ref_base_code_id': tax.ref_base_code_id.id,
1380                             'sequence': tax.sequence,
1381                             'base_sign': tax.base_sign,
1382                             'tax_sign': tax.tax_sign,
1383                             'ref_base_sign': tax.ref_base_sign,
1384                             'ref_tax_sign': tax.ref_tax_sign,
1385                             'price_unit': cur_price_unit - amount,
1386                             'tax_code_id': tax.tax_code_id.id,
1387                             'ref_tax_code_id': tax.ref_tax_code_id.id,})
1388
1389             elif tax.type=='fixed':
1390                 res.append({'id':tax.id,
1391                             'name':tax.name,
1392                             'amount':tax.amount,
1393                             'account_collected_id':tax.account_collected_id.id,
1394                             'account_paid_id':tax.account_paid_id.id,
1395                             'base_code_id': tax.base_code_id.id,
1396                             'ref_base_code_id': tax.ref_base_code_id.id,
1397                             'sequence': tax.sequence,
1398                             'base_sign': tax.base_sign,
1399                             'tax_sign': tax.tax_sign,
1400                             'ref_base_sign': tax.ref_base_sign,
1401                             'ref_tax_sign': tax.ref_tax_sign,
1402                             'price_unit': 1,
1403                             'tax_code_id': tax.tax_code_id.id,
1404                             'ref_tax_code_id': tax.ref_tax_code_id.id,})
1405
1406             elif tax.type=='code':
1407                 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1408                 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1409                 exec tax.python_compute_inv in localdict
1410                 amount = localdict['result']
1411                 res.append({
1412                     'id': tax.id,
1413                     'name': tax.name,
1414                     'amount': amount,
1415                     'account_collected_id': tax.account_collected_id.id,
1416                     'account_paid_id': tax.account_paid_id.id,
1417                     'base_code_id': tax.base_code_id.id,
1418                     'ref_base_code_id': tax.ref_base_code_id.id,
1419                     'sequence': tax.sequence,
1420                     'base_sign': tax.base_sign,
1421                     'tax_sign': tax.tax_sign,
1422                     'ref_base_sign': tax.ref_base_sign,
1423                     'ref_tax_sign': tax.ref_tax_sign,
1424                     'price_unit': cur_price_unit - amount,
1425                     'tax_code_id': tax.tax_code_id.id,
1426                     'ref_tax_code_id': tax.ref_tax_code_id.id,
1427                 })
1428
1429             amount2 = res[-1]['amount']
1430             if len(tax.child_ids):
1431                 if tax.child_depend:
1432                     del res[-1]
1433                     amount = price_unit
1434                 else:
1435                     amount = amount2
1436             for t in tax.child_ids:
1437                 parent_tax = self._unit_compute_inv(cr, uid, [t], amount, address_id, product, partner)
1438                 res.extend(parent_tax)
1439             if tax.include_base_amount:
1440                 cur_price_unit-=amount
1441         taxes.reverse()
1442         return res
1443
1444     def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1445         """
1446         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1447         Price Unit is a VAT included price
1448
1449         RETURN:
1450             [ tax ]
1451             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1452             one tax for each tax id in IDS and their childs
1453         """
1454         res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1455         for r in res:
1456             r['amount'] *= quantity
1457         return res
1458 account_tax()
1459
1460 # ---------------------------------------------------------
1461 # Account Entries Models
1462 # ---------------------------------------------------------
1463
1464 class account_model(osv.osv):
1465     _name = "account.model"
1466     _description = "Account Model"
1467     _columns = {
1468         'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
1469         'ref': fields.char('Ref', size=64),
1470         'journal_id': fields.many2one('account.journal', 'Journal', required=True),
1471         'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
1472         'legend' :fields.text('Legend',readonly=True,size=100),
1473     }
1474
1475     _defaults = {
1476         'legend':lambda *a:'''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''',
1477     }
1478
1479 account_model()
1480
1481 class account_model_line(osv.osv):
1482     _name = "account.model.line"
1483     _description = "Account Model Entries"
1484     _columns = {
1485         'name': fields.char('Name', size=64, required=True),
1486         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from the lowest sequences to the higher ones"),
1487         'quantity': fields.float('Quantity', digits=(16,2), help="The optionnal quantity on entries"),
1488         'debit': fields.float('Debit', digits=(16,2)),
1489         'credit': fields.float('Credit', digits=(16,2)),
1490
1491         'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
1492
1493         'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
1494
1495         'ref': fields.char('Ref.', size=16),
1496
1497         'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optionnal other currency."),
1498         'currency_id': fields.many2one('res.currency', 'Currency'),
1499
1500         'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
1501         '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 chosse between the date of the creation action or the the date of the creation of the entries plus the partner payment terms."),
1502         'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
1503     }
1504     _defaults = {
1505         'date': lambda *a: 'today'
1506     }
1507     _order = 'sequence'
1508     _sql_constraints = [
1509         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in model !'),
1510         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model !'),
1511     ]
1512 account_model_line()
1513
1514 # ---------------------------------------------------------
1515 # Account Subscription
1516 # ---------------------------------------------------------
1517
1518
1519 class account_subscription(osv.osv):
1520     _name = "account.subscription"
1521     _description = "Account Subscription"
1522     _columns = {
1523         'name': fields.char('Name', size=64, required=True),
1524         'ref': fields.char('Ref.', size=16),
1525         'model_id': fields.many2one('account.model', 'Model', required=True),
1526
1527         'date_start': fields.date('Starting date', required=True),
1528         'period_total': fields.integer('Number of period', required=True),
1529         'period_nbr': fields.integer('Period', required=True),
1530         'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
1531         'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', required=True, readonly=True),
1532
1533         'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
1534     }
1535     _defaults = {
1536         'date_start': lambda *a: time.strftime('%Y-%m-%d'),
1537         'period_type': lambda *a: 'month',
1538         'period_total': lambda *a: 12,
1539         'period_nbr': lambda *a: 1,
1540         'state': lambda *a: 'draft',
1541     }
1542     def state_draft(self, cr, uid, ids, context={}):
1543         self.write(cr, uid, ids, {'state':'draft'})
1544         return False
1545
1546     def check(self, cr, uid, ids, context={}):
1547         todone = []
1548         for sub in self.browse(cr, uid, ids, context):
1549             ok = True
1550             for line in sub.lines_id:
1551                 if not line.move_id.id:
1552                     ok = False
1553                     break
1554             if ok:
1555                 todone.append(sub.id)
1556         if len(todone):
1557             self.write(cr, uid, todone, {'state':'done'})
1558         return False
1559
1560     def remove_line(self, cr, uid, ids, context={}):
1561         toremove = []
1562         for sub in self.browse(cr, uid, ids, context):
1563             for line in sub.lines_id:
1564                 if not line.move_id.id:
1565                     toremove.append(line.id)
1566         if len(toremove):
1567             self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
1568         self.write(cr, uid, ids, {'state':'draft'})
1569         return False
1570
1571     def compute(self, cr, uid, ids, context={}):
1572         for sub in self.browse(cr, uid, ids, context):
1573             ds = sub.date_start
1574             for i in range(sub.period_total):
1575                 self.pool.get('account.subscription.line').create(cr, uid, {
1576                     'date': ds,
1577                     'subscription_id': sub.id,
1578                 })
1579                 if sub.period_type=='day':
1580                     ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(days=sub.period_nbr)).strftime('%Y-%m-%d')
1581                 if sub.period_type=='month':
1582                     ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(months=sub.period_nbr)).strftime('%Y-%m-%d')
1583                 if sub.period_type=='year':
1584                     ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(years=sub.period_nbr)).strftime('%Y-%m-%d')
1585         self.write(cr, uid, ids, {'state':'running'})
1586         return True
1587 account_subscription()
1588
1589 class account_subscription_line(osv.osv):
1590     _name = "account.subscription.line"
1591     _description = "Account Subscription Line"
1592     _columns = {
1593         'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
1594         'date': fields.date('Date', required=True),
1595         'move_id': fields.many2one('account.move', 'Entry'),
1596     }
1597     _defaults = {
1598     }
1599     def move_create(self, cr, uid, ids, context={}):
1600         tocheck = {}
1601         for line in self.browse(cr, uid, ids, context):
1602             datas = {
1603                 'date': line.date,
1604             }
1605             ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
1606             tocheck[line.subscription_id.id] = True
1607             self.write(cr, uid, [line.id], {'move_id':ids[0]})
1608         if tocheck:
1609             self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
1610         return True
1611     _rec_name = 'date'
1612 account_subscription_line()
1613
1614
1615 class account_config_wizard(osv.osv_memory):
1616     _name = 'account.config.wizard'
1617
1618     def _get_charts(self, cr, uid, context):
1619         module_obj=self.pool.get('ir.module.module')
1620         ids=module_obj.search(cr, uid, [('category_id', '=', 'Account Charts'), ('state', '<>', 'installed')])
1621         res=[(m.id, m.shortdesc) for m in module_obj.browse(cr, uid, ids)]
1622         res.append((-1, 'None'))
1623         res.sort(lambda x,y: cmp(x[1],y[1]))
1624         return res
1625
1626     _columns = {
1627         'name':fields.char('Name', required=True, size=64, help="Name of the fiscal year as displayed on screens."),
1628         'code':fields.char('Code', required=True, size=64, help="Name of the fiscal year as displayed in reports."),
1629         'date1': fields.date('Starting Date', required=True),
1630         'date2': fields.date('Ending Date', required=True),
1631         'period':fields.selection([('month','Month'),('3months','3 Months')], 'Periods', required=True),
1632         'charts' : fields.selection(_get_charts, 'Charts of Account',required=True)
1633     }
1634     _defaults = {
1635         'code': lambda *a: time.strftime('%Y'),
1636         'name': lambda *a: time.strftime('%Y'),
1637         'date1': lambda *a: time.strftime('%Y-01-01'),
1638         'date2': lambda *a: time.strftime('%Y-12-31'),
1639         'period':lambda *a:'month',
1640     }
1641     def action_cancel(self,cr,uid,ids,conect=None):
1642         return {
1643                 'view_type': 'form',
1644                 "view_mode": 'form',
1645                 'res_model': 'ir.actions.configuration.wizard',
1646                 'type': 'ir.actions.act_window',
1647                 'target':'new',
1648         }
1649
1650     def install_account_chart(self, cr, uid,ids, context=None):
1651         for res in self.read(cr,uid,ids):
1652             id = res['charts']
1653             def install(id):
1654                 mod_obj = self.pool.get('ir.module.module')
1655                 mod_obj.write(cr , uid, [id] ,{'state' : 'to install'})
1656                 mod_obj.download(cr, uid, [id], context=context)
1657                 cr.commit()
1658                 cr.execute("select m.id as id from ir_module_module_dependency d inner join ir_module_module m on (m.name=d.name) where d.module_id=%d and m.state='uninstalled'",(id,))
1659                 ret = cr.fetchall()
1660                 if len(ret):
1661                     for r in ret:
1662                         install(r[0])
1663                 else:
1664                     mod_obj.write(cr , uid, [id] ,{'state' : 'to install'})
1665                     mod_obj.download(cr, uid, [id], context=context)
1666                     cr.commit()
1667             if id>0:
1668                 install(id)
1669         cr.commit()
1670         db, pool = pooler.restart_pool(cr.dbname, update_module=True)
1671
1672     def action_create(self, cr, uid,ids, context=None):
1673         for res in self.read(cr,uid,ids):
1674             if 'date1' in res and 'date2' in res:
1675                 res_obj = self.pool.get('account.fiscalyear')
1676                 start_date=res['date1']
1677                 end_date=res['date2']
1678                 name=res['name']#DateTime.strptime(start_date, '%Y-%m-%d').strftime('%m.%Y') + '-' + DateTime.strptime(end_date, '%Y-%m-%d').strftime('%m.%Y')
1679                 vals={
1680                     'name':name,
1681                     'code':name,
1682                     'date_start':start_date,
1683                     'date_stop':end_date,
1684                 }
1685                 new_id=res_obj.create(cr, uid, vals, context=context)
1686                 if res['period']=='month':
1687                     res_obj.create_period(cr,uid,[new_id])
1688                 elif res['period']=='3months':
1689                     res_obj.create_period3(cr,uid,[new_id])
1690         self.install_account_chart(cr,uid,ids)
1691         return {
1692                 'view_type': 'form',
1693                 "view_mode": 'form',
1694                 'res_model': 'ir.actions.configuration.wizard',
1695                 'type': 'ir.actions.act_window',
1696                 'target':'new',
1697         }
1698
1699
1700
1701 account_config_wizard()
1702
1703
1704 #  ---------------------------------------------------------------
1705 #   Account Templates : Account, Tax, Tax Code and chart. + Wizard
1706 #  ---------------------------------------------------------------
1707
1708 class account_tax_template(osv.osv):
1709     _name = 'account.tax.template'
1710 account_tax_template()
1711
1712 class account_account_template(osv.osv):
1713     _order = "code"
1714     _name = "account.account.template"
1715     _description ='Templates for Accounts'
1716
1717     _columns = {
1718         'name': fields.char('Name', size=128, required=True, select=True),
1719         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Force all moves for this account to have this secondary currency."),
1720         'code': fields.char('Code', size=64),
1721         'type': fields.selection([
1722             ('receivable','Receivable'),
1723             ('payable','Payable'),
1724             ('view','View'),
1725             ('consolidation','Consolidation'),
1726             ('other','Others'),
1727             ('closed','Closed'),
1728             ], 'Internal Type', required=True,),
1729         'user_type': fields.many2one('account.account.type', 'Account Type', required=True),
1730         'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if the user can make a reconciliation of the entries in this account."),
1731         'shortcut': fields.char('Shortcut', size=12),
1732         'note': fields.text('Note'),
1733         'parent_id': fields.many2one('account.account.template','Parent Account Template', ondelete='cascade'),
1734         'child_parent_ids':fields.one2many('account.account.template','parent_id','Children'),
1735         'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel','account_id','tax_id', 'Default Taxes'),
1736     }
1737
1738     _defaults = {
1739         'reconcile': lambda *a: False,
1740         'type' : lambda *a :'view',
1741     }
1742
1743     def _check_recursion(self, cr, uid, ids):
1744         level = 100
1745         while len(ids):
1746             cr.execute('select parent_id from account_account_template where id in ('+','.join(map(str,ids))+')')
1747             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1748             if not level:
1749                 return False
1750             level -= 1
1751         return True
1752
1753     _constraints = [
1754         (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
1755     ]
1756
1757
1758     def name_get(self, cr, uid, ids, context={}):
1759         if not len(ids):
1760             return []
1761         reads = self.read(cr, uid, ids, ['name','code'], context)
1762         res = []
1763         for record in reads:
1764             name = record['name']
1765             if record['code']:
1766                 name = record['code']+' '+name
1767             res.append((record['id'],name ))
1768         return res
1769
1770 account_account_template()
1771
1772 class account_tax_code_template(osv.osv):
1773
1774     _name = 'account.tax.code.template'
1775     _description = 'Tax Code Template'
1776     _order = 'code'
1777     _rec_name = 'code'
1778     _columns = {
1779         'name': fields.char('Tax Case Name', size=64, required=True),
1780         'code': fields.char('Case Code', size=64),
1781         'info': fields.text('Description'),
1782         'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
1783         'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Childs Codes'),
1784         'sign': fields.float('Sign for parent', required=True),
1785     }
1786
1787     _defaults = {
1788         'sign': lambda *args: 1.0,
1789     }
1790
1791     def name_get(self, cr, uid, ids, context=None):
1792         if not len(ids):
1793             return []
1794         if isinstance(ids, (int, long)):
1795             ids = [ids]
1796         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1797         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1798                 for x in reads]
1799
1800     def _check_recursion(self, cr, uid, ids):
1801         level = 100
1802         while len(ids):
1803             cr.execute('select distinct parent_id from account_tax_code_template where id in ('+','.join(map(str,ids))+')')
1804             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1805             if not level:
1806                 return False
1807             level -= 1
1808         return True
1809
1810     _constraints = [
1811         (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
1812     ]
1813     _order = 'code,name'
1814 account_tax_code_template()
1815
1816
1817 class account_chart_template(osv.osv):
1818     _name="account.chart.template"
1819     _description= "Templates for Account Chart"
1820
1821     _columns={
1822         'name': fields.char('Name', size=64, required=True),
1823         'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
1824         'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
1825         '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'),
1826         'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
1827         'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
1828         'property_account_payable': fields.many2one('account.account.template','Payable Account'),
1829         'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
1830         'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
1831         'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
1832         'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
1833     }
1834
1835 account_chart_template()
1836
1837 class account_tax_template(osv.osv):
1838
1839     _name = 'account.tax.template'
1840     _description = 'Templates for Taxes'
1841
1842     _columns = {
1843         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
1844         'name': fields.char('Tax Name', size=64, required=True),
1845         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the taxes lines from the lowest sequences to the higher ones. The order is important if you have a tax that have several tax children. In this case, the evaluation order is important."),
1846         'amount': fields.float('Amount', required=True, digits=(14,4)),
1847         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
1848         'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True),
1849         '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."),
1850         'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
1851         'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
1852         'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
1853         'child_depend':fields.boolean('Tax on Childs', help="Indicate if the tax computation is based on the value computed for the computation of child taxes or based on the total amount."),
1854         'python_compute':fields.text('Python Code'),
1855         'python_compute_inv':fields.text('Python Code (reverse)'),
1856         'python_applicable':fields.text('Python Code'),
1857         '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."),
1858
1859         #
1860         # Fields used for the VAT declaration
1861         #
1862         'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
1863         'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
1864         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1865         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1866
1867         # Same fields for refund invoices
1868
1869         'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
1870         'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1871         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1872         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1873         'include_base_amount': fields.boolean('Include in base amount', help="Indicate if the amount of tax must be included in the base amount for the computation of the next taxes."),
1874         'description': fields.char('Internal Name', size=32),
1875     }
1876
1877     def name_get(self, cr, uid, ids, context={}):
1878         if not len(ids):
1879             return []
1880         res = []
1881         for record in self.read(cr, uid, ids, ['description','name'], context):
1882             name = record['description'] and record['description'] or record['name']
1883             res.append((record['id'],name ))
1884         return res
1885
1886     def _default_company(self, cr, uid, context={}):
1887         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1888         if user.company_id:
1889             return user.company_id.id
1890         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1891
1892     _defaults = {
1893         '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''',
1894         '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''',
1895         'applicable_type': lambda *a: 'true',
1896         'type': lambda *a: 'percent',
1897         'amount': lambda *a: 0,
1898         'sequence': lambda *a: 1,
1899         'tax_group': lambda *a: 'vat',
1900         'ref_tax_sign': lambda *a: 1,
1901         'ref_base_sign': lambda *a: 1,
1902         'tax_sign': lambda *a: 1,
1903         'base_sign': lambda *a: 1,
1904         'include_base_amount': lambda *a: False,
1905     }
1906     _order = 'sequence'
1907
1908
1909 account_tax_template()
1910
1911     # Multi charts of Accounts wizard
1912
1913 class wizard_multi_charts_accounts(osv.osv_memory):
1914     """
1915     Create a new account chart for a company.
1916     Wizards ask:
1917         * a company
1918         * an account chart template
1919         * a number of digits for formatting code of non-view accounts
1920         * a list of bank account owned by the company
1921     Then, the wizard:
1922         * generates all accounts from the template and assign them to the right company
1923         * generates all taxes and tax codes, changing account assignations
1924         * generates all accounting properties and assign correctly
1925     """
1926     _name='wizard.multi.charts.accounts'
1927
1928     _columns = {
1929         'company_id':fields.many2one('res.company','Company',required=True),
1930         'chart_template_id': fields.many2one('account.chart.template','Chart Template',required=True),
1931         'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts',required=True),
1932         'code_digits':fields.integer('# of Digits',required=True,help="No. of Digits to use for account code"),
1933     }
1934
1935     def _get_chart(self, cr, uid, context={}):
1936         ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
1937         if ids:
1938             return ids[0]
1939         return False
1940     _defaults = {
1941         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr,uid,[uid],c)[0].company_id.id,
1942         'chart_template_id': _get_chart,
1943         'code_digits': lambda *a:6,
1944     }
1945
1946     def action_create(self, cr, uid, ids, context=None):
1947         obj_multi = self.browse(cr,uid,ids[0])
1948         obj_acc = self.pool.get('account.account')
1949         obj_acc_tax = self.pool.get('account.tax')
1950         obj_journal = self.pool.get('account.journal')
1951         obj_acc_template = self.pool.get('account.account.template')
1952
1953         # Creating Account
1954         obj_acc_root = obj_multi.chart_template_id.account_root_id
1955         tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
1956         company_id = obj_multi.company_id.id
1957
1958         #new code
1959         acc_template_ref = {}
1960         tax_template_ref = {}
1961         tax_code_template_ref = {}
1962         todo_dict = {}
1963
1964         #create all the tax code
1965         children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
1966         for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
1967             vals={
1968                 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
1969                 'code': tax_code_template.code,
1970                 'info': tax_code_template.info,
1971                 '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,
1972                 'company_id': company_id,
1973                 'sign': tax_code_template.sign,
1974             }
1975             new_tax_code = self.pool.get('account.tax.code').create(cr,uid,vals)
1976             #recording the new tax code to do the mapping
1977             tax_code_template_ref[tax_code_template.id] = new_tax_code
1978
1979         #create all the tax
1980         for tax in obj_multi.chart_template_id.tax_template_ids:
1981             #create it
1982             vals_tax = {
1983                 'name':tax.name,
1984                 'sequence': tax.sequence,
1985                 'amount':tax.amount,
1986                 'type':tax.type,
1987                 'applicable_type': tax.applicable_type,
1988                 'domain':tax.domain,
1989                 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
1990                 'child_depend': tax.child_depend,
1991                 'python_compute': tax.python_compute,
1992                 'python_compute_inv': tax.python_compute_inv,
1993                 'python_applicable': tax.python_applicable,
1994                 'tax_group':tax.tax_group,
1995                 'base_code_id': tax.base_code_id and tax_code_template_ref[tax.base_code_id.id] or False,
1996                 'tax_code_id': tax.tax_code_id and tax_code_template_ref[tax.tax_code_id.id] or False,
1997                 'base_sign': tax.base_sign,
1998                 'tax_sign': tax.tax_sign,
1999                 'ref_base_code_id': tax.ref_base_code_id and tax_code_template_ref[tax.ref_base_code_id.id] or False,
2000                 'ref_tax_code_id': tax.ref_tax_code_id and tax_code_template_ref[tax.ref_tax_code_id.id] or False,
2001                 'ref_base_sign': tax.ref_base_sign,
2002                 'ref_tax_sign': tax.ref_tax_sign,
2003                 'include_base_amount': tax.include_base_amount,
2004                 'description':tax.description,
2005                 'company_id': company_id,
2006             }
2007             new_tax = obj_acc_tax.create(cr,uid,vals_tax)
2008             #as the accounts have not been created yet, we have to wait before filling these fields
2009             todo_dict[new_tax] = {
2010                 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2011                 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2012             }
2013             tax_template_ref[tax.id] = new_tax
2014
2015         #deactivate the parent_store functionnality on account_account for rapidity purpose
2016         self.pool._init = True
2017
2018         children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id])])
2019         children_acc_template.sort()
2020         for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2021             tax_ids = []
2022             for tax in account_template.tax_ids:
2023                 tax_ids.append(tax_template_ref[tax.id])
2024             #create the account_account
2025
2026             dig = obj_multi.code_digits
2027             code_main = len(account_template.code)
2028             code_acc = account_template.code
2029             if code_main<=dig and account_template.type != 'view':
2030                 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2031             vals={
2032                 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2033                 #'sign': account_template.sign,
2034                 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2035                 'code': code_acc,
2036                 'type': account_template.type,
2037                 'user_type': account_template.user_type and account_template.user_type.id or False,
2038                 'reconcile': account_template.reconcile,
2039                 'shortcut': account_template.shortcut,
2040                 'note': account_template.note,
2041                 '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,
2042                 'tax_ids': [(6,0,tax_ids)],
2043                 'company_id': company_id,
2044             }
2045             new_account = obj_acc.create(cr,uid,vals)
2046             acc_template_ref[account_template.id] = new_account
2047         #reactivate the parent_store functionnality on account_account
2048         self.pool._init = False
2049         self.pool.get('account.account')._parent_store_compute(cr)
2050
2051         for key,value in todo_dict.items():
2052             if value['account_collected_id'] or value['account_paid_id']:
2053                 obj_acc_tax.write(cr, uid, [key], vals={
2054                     'account_collected_id': acc_template_ref[value['account_collected_id']],
2055                     'account_paid_id': acc_template_ref[value['account_paid_id']],
2056                 })
2057
2058         # Creating Journals
2059         vals_journal={}
2060         view_id = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Journal View')])[0]
2061         seq_id = self.pool.get('ir.sequence').search(cr,uid,[('code','=','account.journal')])[0]
2062         seq_code = self.pool.get('ir.sequence').get(cr, uid, 'account.journal')
2063
2064         vals_journal['view_id']=view_id
2065         vals_journal['sequence_id']=seq_id
2066
2067         #Sales Journal
2068         vals_journal['name'] = _('Sales Journal')
2069         vals_journal['type'] = 'sale'
2070         vals_journal['code'] = _('SAJ')
2071
2072         if obj_multi.chart_template_id.property_account_receivable:
2073             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_receivable.id]
2074             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_receivable.id]
2075
2076         obj_journal.create(cr,uid,vals_journal)
2077
2078         # Purchase Journal
2079         vals_journal['name']=_('Purchase Journal')
2080         vals_journal['type']='purchase'
2081         vals_journal['code']=_('EXJ')
2082
2083         if obj_multi.chart_template_id.property_account_payable:
2084             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_payable.id]
2085             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_payable.id]
2086
2087         obj_journal.create(cr,uid,vals_journal)
2088
2089         # Bank Journals
2090         view_id_cash = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Cash Journal View')])[0]
2091         view_id_cur = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Multi-Currency Cash Journal View')])[0]
2092         ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2093
2094         current_num = 1
2095         for line in obj_multi.bank_accounts_id:
2096             #create the account_account for this bank journal
2097             tmp = self.pool.get('res.partner.bank').name_get(cr, uid, [line.acc_no.id])[0][1]
2098             dig = obj_multi.code_digits
2099             vals={
2100                 'name': line.acc_no.bank and line.acc_no.bank.name+' '+tmp or tmp,
2101                 'currency_id': line.currency_id and line.currency_id.id or False,
2102                 'code': str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num),
2103                 'type': 'other',
2104                 'user_type': account_template.user_type and account_template.user_type.id or False,
2105                 'reconcile': True,
2106                 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2107                 'company_id': company_id,
2108             }
2109             acc_cash_id  = obj_acc.create(cr,uid,vals)
2110
2111             #create the bank journal
2112             vals_journal['name']= vals['name']
2113             vals_journal['code']= _('BNK') + str(current_num)
2114             vals_journal['sequence_id'] = seq_id
2115             vals_journal['type'] = 'cash'
2116             if line.currency_id:
2117                 vals_journal['view_id'] = view_id_cur
2118                 vals_journal['currency'] = line.currency_id.id
2119             else:
2120                 vals_journal['view_id'] = view_id_cash
2121             vals_journal['default_credit_account_id'] = acc_cash_id
2122             vals_journal['default_debit_account_id']= acc_cash_id
2123             obj_journal.create(cr,uid,vals_journal)
2124
2125             current_num += 1
2126
2127         #create the properties
2128         property_obj = self.pool.get('ir.property')
2129         fields_obj = self.pool.get('ir.model.fields')
2130
2131         todo_list = [
2132             ('property_account_receivable','res.partner','account.account'),
2133             ('property_account_payable','res.partner','account.account'),
2134             ('property_account_expense_categ','product.category','account.account'),
2135             ('property_account_income_categ','product.category','account.account'),
2136             ('property_account_expense','product.template','account.account'),
2137             ('property_account_income','product.template','account.account')
2138         ]
2139         for record in todo_list:
2140             r = []
2141             r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2142             account = getattr(obj_multi.chart_template_id, record[0])
2143             field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2144             vals = {
2145                 'name': record[0],
2146                 'company_id': company_id,
2147                 'fields_id': field[0],
2148                 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2149             }
2150             if r:
2151                 #the property exist: modify it
2152                 property_obj.write(cr, uid, r, vals)
2153             else:
2154                 #create the property
2155                 property_obj.create(cr, uid, vals)
2156
2157         return {
2158                 'view_type': 'form',
2159                 "view_mode": 'form',
2160                 'res_model': 'ir.actions.configuration.wizard',
2161                 'type': 'ir.actions.act_window',
2162                 'target':'new',
2163         }
2164     def action_cancel(self,cr,uid,ids,conect=None):
2165         return {
2166                 'view_type': 'form',
2167                 "view_mode": 'form',
2168                 'res_model': 'ir.actions.configuration.wizard',
2169                 'type': 'ir.actions.act_window',
2170                 'target':'new',
2171         }
2172
2173
2174 wizard_multi_charts_accounts()
2175
2176 class account_bank_accounts_wizard(osv.osv_memory):
2177     _name='account.bank.accounts.wizard'
2178
2179     _columns = {
2180         'acc_no':fields.many2one('res.partner.bank','Account No.',required=True),
2181         'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2182         'currency_id':fields.many2one('res.currency', 'Currency'),
2183     }
2184
2185 account_bank_accounts_wizard()
2186
2187 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
2188