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