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