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