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