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