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