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