[IMP] improve find method of account period and make comapny_id as related on account...
[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 used to generate the numbers of the journal entries of this journal."),
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             else:
715                 name = "%s (%s)" % (rs.name, rs.company_id.currency_id.name)
716             res += [(rs.id, name)]
717         return res
718
719     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
720         if not args:
721             args = []
722         if context is None:
723             context = {}
724         ids = []
725         if context.get('journal_type', False):
726             args += [('type','=',context.get('journal_type'))]
727         if name:
728             ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
729         if not ids:
730             ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
731
732         return self.name_get(cr, user, ids, context=context)
733
734     def onchange_type(self, cr, uid, ids, type, currency, context=None):
735         obj_data = self.pool.get('ir.model.data')
736         user_pool = self.pool.get('res.users')
737
738         type_map = {
739             'sale':'account_sp_journal_view',
740             'sale_refund':'account_sp_refund_journal_view',
741             'purchase':'account_sp_journal_view',
742             'purchase_refund':'account_sp_refund_journal_view',
743             'cash':'account_journal_bank_view',
744             'bank':'account_journal_bank_view',
745             'general':'account_journal_view',
746             'situation':'account_journal_view'
747         }
748
749         res = {}
750         view_id = type_map.get(type, 'account_journal_view')
751         user = user_pool.browse(cr, uid, uid)
752         if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
753             view_id = 'account_journal_bank_view_multi'
754         data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
755         data = obj_data.browse(cr, uid, data_id[0], context=context)
756
757         res.update({
758             'centralisation':type == 'situation',
759             'view_id':data.res_id,
760         })
761         return {
762             'value':res
763         }
764
765 account_journal()
766
767 class account_fiscalyear(osv.osv):
768     _name = "account.fiscalyear"
769     _description = "Fiscal Year"
770     _columns = {
771         'name': fields.char('Fiscal Year', size=64, required=True),
772         'code': fields.char('Code', size=6, required=True),
773         'company_id': fields.many2one('res.company', 'Company', required=True),
774         'date_start': fields.date('Start Date', required=True),
775         'date_stop': fields.date('End Date', required=True),
776         'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
777         'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True),
778     }
779     _defaults = {
780         'state': 'draft',
781         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
782     }
783     _order = "date_start"
784
785     def _check_fiscal_year(self, cr, uid, ids, context=None):
786         current_fiscal_yr = self.browse(cr, uid, ids, context=context)[0]
787         obj_fiscal_ids = self.search(cr, uid, [('company_id', '=', current_fiscal_yr.company_id.id)], context=context)
788         obj_fiscal_ids.remove(ids[0])
789         data_fiscal_yr = self.browse(cr, uid, obj_fiscal_ids, context=context)
790
791         for old_fy in data_fiscal_yr:
792             if old_fy.company_id.id == current_fiscal_yr['company_id'].id:
793                 # Condition to check if the current fiscal year falls in between any previously defined fiscal year
794                 if old_fy.date_start <= current_fiscal_yr['date_start'] <= old_fy.date_stop or \
795                     old_fy.date_start <= current_fiscal_yr['date_stop'] <= old_fy.date_stop:
796                     return False
797         return True
798
799     def _check_duration(self, cr, uid, ids, context=None):
800         obj_fy = self.browse(cr, uid, ids[0], context=context)
801         if obj_fy.date_stop < obj_fy.date_start:
802             return False
803         return True
804
805     _constraints = [
806         (_check_duration, 'Error! The duration of the Fiscal Year is invalid. ', ['date_stop']),
807         (_check_fiscal_year, 'Error! You cannot define overlapping fiscal years',['date_start', 'date_stop'])
808     ]
809
810     def create_period3(self, cr, uid, ids, context=None):
811         return self.create_period(cr, uid, ids, context, 3)
812
813     def create_period(self, cr, uid, ids, context=None, interval=1):
814         period_obj = self.pool.get('account.period')
815         for fy in self.browse(cr, uid, ids, context=context):
816             ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
817             period_obj.create(cr, uid, {
818                     'name': _('Opening Period'),
819                     'code': ds.strftime('00/%Y'),
820                     'date_start': ds,
821                     'date_stop': ds,
822                     'special': True,
823                     'fiscalyear_id': fy.id,
824                 })
825             while ds.strftime('%Y-%m-%d') < fy.date_stop:
826                 de = ds + relativedelta(months=interval, days=-1)
827
828                 if de.strftime('%Y-%m-%d') > fy.date_stop:
829                     de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
830
831                 period_obj.create(cr, uid, {
832                     'name': ds.strftime('%m/%Y'),
833                     'code': ds.strftime('%m/%Y'),
834                     'date_start': ds.strftime('%Y-%m-%d'),
835                     'date_stop': de.strftime('%Y-%m-%d'),
836                     'fiscalyear_id': fy.id,
837                 })
838                 ds = ds + relativedelta(months=interval)
839         return True
840
841     def find(self, cr, uid, dt=None, exception=True, context=None):
842         if not dt:
843             dt = time.strftime('%Y-%m-%d')
844         ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
845         if not ids:
846             if exception:
847                 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
848             else:
849                 return False
850         return ids[0]
851
852     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
853         if args is None:
854             args = []
855         if context is None:
856             context = {}
857         ids = []
858         if name:
859             ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
860         if not ids:
861             ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
862         return self.name_get(cr, user, ids, context=context)
863
864 account_fiscalyear()
865
866 class account_period(osv.osv):
867     _name = "account.period"
868     _description = "Account period"
869     _columns = {
870         'name': fields.char('Period Name', size=64, required=True),
871         'code': fields.char('Code', size=12),
872         'special': fields.boolean('Opening/Closing Period', size=12,
873             help="These periods can overlap."),
874         'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
875         'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
876         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
877         'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True,
878                                   help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
879         'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
880     }
881     _defaults = {
882         'state': 'draft',
883     }
884     _order = "date_start, special desc"
885
886     def _check_duration(self,cr,uid,ids,context=None):
887         obj_period = self.browse(cr, uid, ids[0], context=context)
888         if obj_period.date_stop < obj_period.date_start:
889             return False
890         return True
891
892     def _check_year_limit(self,cr,uid,ids,context=None):
893         for obj_period in self.browse(cr, uid, ids, context=context):
894             if obj_period.special:
895                 continue
896
897             if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
898                obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
899                obj_period.fiscalyear_id.date_start > obj_period.date_start or \
900                obj_period.fiscalyear_id.date_start > obj_period.date_stop:
901                 return False
902
903             pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
904             for period in self.browse(cr, uid, pids):
905                 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
906                     return False
907         return True
908
909     _constraints = [
910         (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
911         (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
912     ]
913
914     def next(self, cr, uid, period, step, context=None):
915         ids = self.search(cr, uid, [('date_start','>',period.date_start)])
916         if len(ids)>=step:
917             return ids[step-1]
918         return False
919
920     def find(self, cr, uid, dt=None, context=None):
921         if context is None: context = {}
922         if not dt:
923             dt = time.strftime('%Y-%m-%d')
924 #CHECKME: shouldn't we check the state of the period?
925         args = [('date_start', '<=' ,dt), ('date_stop', '>=', dt)]
926         if context.get('company_id', False):
927             args.append(('company_id', '=', context['company_id']))
928         else:
929             company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
930             args.append(('company_id', '=', company_id))
931         ids = self.search(cr, uid, args, context=context)
932         if not ids:
933             raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create one.')%dt)
934         return ids
935
936     def action_draft(self, cr, uid, ids, *args):
937         mode = 'draft'
938         cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),))
939         cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),))
940         return True
941
942     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
943         if args is None:
944             args = []
945         if context is None:
946             context = {}
947         ids = []
948         if name:
949             ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
950         if not ids:
951             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
952         return self.name_get(cr, user, ids, context=context)
953
954     def write(self, cr, uid, ids, vals, context=None):
955         if 'company_id' in vals:
956             move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
957             if move_lines:
958                 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines'))
959         return super(account_period, self).write(cr, uid, ids, vals, context=context)
960
961     def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
962         period_from = self.browse(cr, uid, period_from_id)
963         period_date_start = period_from.date_start
964         company1_id = period_from.company_id.id
965         period_to = self.browse(cr, uid, period_to_id)
966         period_date_stop = period_to.date_stop
967         company2_id = period_to.company_id.id
968         if company1_id != company2_id:
969             raise osv.except_osv(_('Error'), _('You should have chosen periods that belongs to the same company'))
970         if period_date_start > period_date_stop:
971             raise osv.except_osv(_('Error'), _('Start period should be smaller then End period'))
972         #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).
973         if period_from.special:
974             return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id)])
975         return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id), ('special', '=', False)])
976
977 account_period()
978
979 class account_journal_period(osv.osv):
980     _name = "account.journal.period"
981     _description = "Journal Period"
982
983     def _icon_get(self, cr, uid, ids, field_name, arg=None, context=None):
984         result = {}.fromkeys(ids, 'STOCK_NEW')
985         for r in self.read(cr, uid, ids, ['state']):
986             result[r['id']] = {
987                 'draft': 'STOCK_NEW',
988                 'printed': 'STOCK_PRINT_PREVIEW',
989                 'done': 'STOCK_DIALOG_AUTHENTICATION',
990             }.get(r['state'], 'STOCK_NEW')
991         return result
992
993     _columns = {
994         'name': fields.char('Journal-Period Name', size=64, required=True),
995         'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
996         'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
997         'icon': fields.function(_icon_get, string='Icon', type='char', size=32),
998         '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."),
999         'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
1000                                   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.'),
1001         'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
1002         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
1003     }
1004
1005     def _check(self, cr, uid, ids, context=None):
1006         for obj in self.browse(cr, uid, ids, context=context):
1007             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))
1008             res = cr.fetchall()
1009             if res:
1010                 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
1011         return True
1012
1013     def write(self, cr, uid, ids, vals, context=None):
1014         self._check(cr, uid, ids, context=context)
1015         return super(account_journal_period, self).write(cr, uid, ids, vals, context=context)
1016
1017     def create(self, cr, uid, vals, context=None):
1018         period_id = vals.get('period_id',False)
1019         if period_id:
1020             period = self.pool.get('account.period').browse(cr, uid, period_id, context=context)
1021             vals['state']=period.state
1022         return super(account_journal_period, self).create(cr, uid, vals, context)
1023
1024     def unlink(self, cr, uid, ids, context=None):
1025         self._check(cr, uid, ids, context=context)
1026         return super(account_journal_period, self).unlink(cr, uid, ids, context=context)
1027
1028     _defaults = {
1029         'state': 'draft',
1030         'active': True,
1031     }
1032     _order = "period_id"
1033
1034 account_journal_period()
1035
1036 class account_fiscalyear(osv.osv):
1037     _inherit = "account.fiscalyear"
1038     _description = "Fiscal Year"
1039     _columns = {
1040         'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1041     }
1042
1043     def copy(self, cr, uid, id, default={}, context=None):
1044         default.update({
1045             'period_ids': [],
1046             'end_journal_period_id': False
1047         })
1048         return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1049
1050 account_fiscalyear()
1051 #----------------------------------------------------------
1052 # Entries
1053 #----------------------------------------------------------
1054 class account_move(osv.osv):
1055     _name = "account.move"
1056     _description = "Account Entry"
1057     _order = 'id desc'
1058
1059     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1060         """
1061         Returns a list of tupples containing id, name, as internally it is called {def name_get}
1062         result format: {[(id, name), (id, name), ...]}
1063
1064         @param cr: A database cursor
1065         @param user: ID of the user currently logged in
1066         @param name: name to search
1067         @param args: other arguments
1068         @param operator: default operator is 'ilike', it can be changed
1069         @param context: context arguments, like lang, time zone
1070         @param limit: Returns first 'n' ids of complete result, default is 80.
1071
1072         @return: Returns a list of tuples containing id and name
1073         """
1074
1075         if not args:
1076           args = []
1077         ids = []
1078         if name:
1079             ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1080
1081         if not ids and name and type(name) == int:
1082             ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1083
1084         if not ids:
1085             ids += self.search(cr, user, args, limit=limit, context=context)
1086
1087         return self.name_get(cr, user, ids, context=context)
1088
1089     def name_get(self, cursor, user, ids, context=None):
1090         if isinstance(ids, (int, long)):
1091             ids = [ids]
1092         if not ids:
1093             return []
1094         res = []
1095         data_move = self.pool.get('account.move').browse(cursor, user, ids, context=context)
1096         for move in data_move:
1097             if move.state=='draft':
1098                 name = '*' + str(move.id)
1099             else:
1100                 name = move.name
1101             res.append((move.id, name))
1102         return res
1103
1104     def _get_period(self, cr, uid, context=None):
1105         periods = self.pool.get('account.period').find(cr, uid)
1106         if periods:
1107             return periods[0]
1108         return False
1109
1110     def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1111         if not ids: return {}
1112         cr.execute( 'SELECT move_id, SUM(debit) '\
1113                     'FROM account_move_line '\
1114                     'WHERE move_id IN %s '\
1115                     'GROUP BY move_id', (tuple(ids),))
1116         result = dict(cr.fetchall())
1117         for id in ids:
1118             result.setdefault(id, 0.0)
1119         return result
1120
1121     def _search_amount(self, cr, uid, obj, name, args, context):
1122         ids = set()
1123         for cond in args:
1124             amount = cond[2]
1125             if isinstance(cond[2],(list,tuple)):
1126                 if cond[1] in ['in','not in']:
1127                     amount = tuple(cond[2])
1128                 else:
1129                     continue
1130             else:
1131                 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1132                     continue
1133
1134             cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1135             res_ids = set(id[0] for id in cr.fetchall())
1136             ids = ids and (ids & res_ids) or res_ids
1137         if ids:
1138             return [('id', 'in', tuple(ids))]
1139         return [('id', '=', '0')]
1140
1141     _columns = {
1142         'name': fields.char('Number', size=64, required=True),
1143         'ref': fields.char('Reference', size=64),
1144         'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1145         'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1146         'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'State', required=True, readonly=True,
1147             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.'),
1148         'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1149         '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.'),
1150         'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1151         'amount': fields.function(_amount_compute, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1152         'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}, select=True),
1153         'narration':fields.text('Narration'),
1154         'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1155     }
1156     _defaults = {
1157         'name': '/',
1158         'state': 'draft',
1159         'period_id': _get_period,
1160         'date': lambda *a: time.strftime('%Y-%m-%d'),
1161         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1162     }
1163
1164     def _check_centralisation(self, cursor, user, ids, context=None):
1165         for move in self.browse(cursor, user, ids, context=context):
1166             if move.journal_id.centralisation:
1167                 move_ids = self.search(cursor, user, [
1168                     ('period_id', '=', move.period_id.id),
1169                     ('journal_id', '=', move.journal_id.id),
1170                     ])
1171                 if len(move_ids) > 1:
1172                     return False
1173         return True
1174
1175     def _check_period_journal(self, cursor, user, ids, context=None):
1176         for move in self.browse(cursor, user, ids, context=context):
1177             for line in move.line_id:
1178                 if line.period_id.id != move.period_id.id:
1179                     return False
1180                 if line.journal_id.id != move.journal_id.id:
1181                     return False
1182         return True
1183
1184     _constraints = [
1185         (_check_centralisation,
1186             'You cannot create more than one move per period on centralized journal',
1187             ['journal_id']),
1188         (_check_period_journal,
1189             'You cannot create entries on different periods/journals in the same move',
1190             ['line_id']),
1191     ]
1192
1193     def post(self, cr, uid, ids, context=None):
1194         if context is None:
1195             context = {}
1196         invoice = context.get('invoice', False)
1197         valid_moves = self.validate(cr, uid, ids, context)
1198
1199         if not valid_moves:
1200             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" !'))
1201         obj_sequence = self.pool.get('ir.sequence')
1202         for move in self.browse(cr, uid, valid_moves, context=context):
1203             if move.name =='/':
1204                 new_name = False
1205                 journal = move.journal_id
1206
1207                 if invoice and invoice.internal_number:
1208                     new_name = invoice.internal_number
1209                 else:
1210                     if journal.sequence_id:
1211                         c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1212                         new_name = obj_sequence.get_id(cr, uid, journal.sequence_id.id, context=c)
1213                     else:
1214                         raise osv.except_osv(_('Error'), _('No sequence defined on the journal !'))
1215
1216                 if new_name:
1217                     self.write(cr, uid, [move.id], {'name':new_name})
1218
1219         cr.execute('UPDATE account_move '\
1220                    'SET state=%s '\
1221                    'WHERE id IN %s',
1222                    ('posted', tuple(valid_moves),))
1223         return True
1224
1225     def button_validate(self, cursor, user, ids, context=None):
1226         for move in self.browse(cursor, user, ids, context=context):
1227             top = None
1228             for line in move.line_id:
1229                 account = line.account_id
1230                 while account:
1231                     account2 = account
1232                     account = account.parent_id
1233                 if not top:
1234                     top = account2.id
1235                 elif top<>account2.id:
1236                     raise osv.except_osv(_('Error !'), _('You cannot validate a Journal Entry unless all journal items are in same chart of accounts !'))
1237         return self.post(cursor, user, ids, context=context)
1238
1239     def button_cancel(self, cr, uid, ids, context=None):
1240         for line in self.browse(cr, uid, ids, context=context):
1241             if not line.journal_id.update_posted:
1242                 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.'))
1243         if ids:
1244             cr.execute('UPDATE account_move '\
1245                        'SET state=%s '\
1246                        'WHERE id IN %s', ('draft', tuple(ids),))
1247         return True
1248
1249     def write(self, cr, uid, ids, vals, context=None):
1250         if context is None:
1251             context = {}
1252         c = context.copy()
1253         c['novalidate'] = True
1254         result = super(osv.osv, self).write(cr, uid, ids, vals, c)
1255         self.validate(cr, uid, ids, context=context)
1256         return result
1257
1258     #
1259     # TODO: Check if period is closed !
1260     #
1261     def create(self, cr, uid, vals, context=None):
1262         if context is None:
1263             context = {}
1264         if 'line_id' in vals and context.get('copy'):
1265             for l in vals['line_id']:
1266                 if not l[0]:
1267                     l[2].update({
1268                         'reconcile_id':False,
1269                         'reconcil_partial_id':False,
1270                         'analytic_lines':False,
1271                         'invoice':False,
1272                         'ref':False,
1273                         'balance':False,
1274                         'account_tax_id':False,
1275                     })
1276
1277             if 'journal_id' in vals and vals.get('journal_id', False):
1278                 for l in vals['line_id']:
1279                     if not l[0]:
1280                         l[2]['journal_id'] = vals['journal_id']
1281                 context['journal_id'] = vals['journal_id']
1282             if 'period_id' in vals:
1283                 for l in vals['line_id']:
1284                     if not l[0]:
1285                         l[2]['period_id'] = vals['period_id']
1286                 context['period_id'] = vals['period_id']
1287             else:
1288                 default_period = self._get_period(cr, uid, context)
1289                 for l in vals['line_id']:
1290                     if not l[0]:
1291                         l[2]['period_id'] = default_period
1292                 context['period_id'] = default_period
1293
1294         if 'line_id' in vals:
1295             c = context.copy()
1296             c['novalidate'] = True
1297             result = super(account_move, self).create(cr, uid, vals, c)
1298             self.validate(cr, uid, [result], context)
1299         else:
1300             result = super(account_move, self).create(cr, uid, vals, context)
1301         return result
1302
1303     def copy(self, cr, uid, id, default={}, context=None):
1304         if context is None:
1305             context = {}
1306         default.update({
1307             'state':'draft',
1308             'name':'/',
1309         })
1310         context.update({
1311             'copy':True
1312         })
1313         return super(account_move, self).copy(cr, uid, id, default, context)
1314
1315     def unlink(self, cr, uid, ids, context=None, check=True):
1316         if context is None:
1317             context = {}
1318         toremove = []
1319         obj_move_line = self.pool.get('account.move.line')
1320         for move in self.browse(cr, uid, ids, context=context):
1321             if move['state'] != 'draft':
1322                 raise osv.except_osv(_('UserError'),
1323                         _('You can not delete posted movement: "%s"!') % \
1324                                 move['name'])
1325             line_ids = map(lambda x: x.id, move.line_id)
1326             context['journal_id'] = move.journal_id.id
1327             context['period_id'] = move.period_id.id
1328             obj_move_line._update_check(cr, uid, line_ids, context)
1329             obj_move_line.unlink(cr, uid, line_ids, context=context)
1330             toremove.append(move.id)
1331         result = super(account_move, self).unlink(cr, uid, toremove, context)
1332         return result
1333
1334     def _compute_balance(self, cr, uid, id, context=None):
1335         move = self.browse(cr, uid, id, context=context)
1336         amount = 0
1337         for line in move.line_id:
1338             amount+= (line.debit - line.credit)
1339         return amount
1340
1341     def _centralise(self, cr, uid, move, mode, context=None):
1342         assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1343         currency_obj = self.pool.get('res.currency') 
1344         if context is None:
1345             context = {}
1346
1347         if mode=='credit':
1348             account_id = move.journal_id.default_debit_account_id.id
1349             mode2 = 'debit'
1350             if not account_id:
1351                 raise osv.except_osv(_('UserError'),
1352                         _('There is no default default debit account defined \n' \
1353                                 'on journal "%s"') % move.journal_id.name)
1354         else:
1355             account_id = move.journal_id.default_credit_account_id.id
1356             mode2 = 'credit'
1357             if not account_id:
1358                 raise osv.except_osv(_('UserError'),
1359                         _('There is no default default credit account defined \n' \
1360                                 'on journal "%s"') % move.journal_id.name)
1361
1362         # find the first line of this move with the current mode
1363         # or create it if it doesn't exist
1364         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1365         res = cr.fetchone()
1366         if res:
1367             line_id = res[0]
1368         else:
1369             context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1370             line_id = self.pool.get('account.move.line').create(cr, uid, {
1371                 'name': _(mode.capitalize()+' Centralisation'),
1372                 'centralisation': mode,
1373                 'account_id': account_id,
1374                 'move_id': move.id,
1375                 'journal_id': move.journal_id.id,
1376                 'period_id': move.period_id.id,
1377                 'date': move.period_id.date_stop,
1378                 'debit': 0.0,
1379                 'credit': 0.0,
1380             }, context)
1381
1382         # find the first line of this move with the other mode
1383         # so that we can exclude it from our calculation
1384         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1385         res = cr.fetchone()
1386         if res:
1387             line_id2 = res[0]
1388         else:
1389             line_id2 = 0
1390
1391         cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1392         result = cr.fetchone()[0] or 0.0
1393         cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1394
1395         #adjust also the amount in currency if needed
1396         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,))
1397         for row in cr.dictfetchall():
1398             currency_id = currency_obj.browse(cr, uid, row['currency_id'], context=context)
1399             if not currency_obj.is_zero(cr, uid, currency_id, row['amount_currency']):
1400                 amount_currency = row['amount_currency'] * -1
1401                 account_id = amount_currency > 0 and move.journal_id.default_debit_account_id.id or move.journal_id.default_credit_account_id.id
1402                 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']))
1403                 res = cr.fetchone()
1404                 if res:
1405                     cr.execute('update account_move_line set amount_currency=%s , account_id=%s where id=%s', (amount_currency, account_id, res[0]))
1406                 else:
1407                     context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1408                     line_id = self.pool.get('account.move.line').create(cr, uid, {
1409                         'name': _('Currency Adjustment'),
1410                         'centralisation': 'currency',
1411                         'account_id': account_id,
1412                         'move_id': move.id,
1413                         'journal_id': move.journal_id.id,
1414                         'period_id': move.period_id.id,
1415                         'date': move.period_id.date_stop,
1416                         'debit': 0.0,
1417                         'credit': 0.0,
1418                         'currency_id': row['currency_id'],
1419                         'amount_currency': amount_currency,
1420                     }, context)
1421
1422         return True
1423
1424     #
1425     # Validate a balanced move. If it is a centralised journal, create a move.
1426     #
1427     def validate(self, cr, uid, ids, context=None):
1428         if context and ('__last_update' in context):
1429             del context['__last_update']
1430
1431         valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1432         obj_analytic_line = self.pool.get('account.analytic.line')
1433         obj_move_line = self.pool.get('account.move.line')
1434         for move in self.browse(cr, uid, ids, context):
1435             # Unlink old analytic lines on move_lines
1436             for obj_line in move.line_id:
1437                 for obj in obj_line.analytic_lines:
1438                     obj_analytic_line.unlink(cr,uid,obj.id)
1439
1440             journal = move.journal_id
1441             amount = 0
1442             line_ids = []
1443             line_draft_ids = []
1444             company_id = None
1445             for line in move.line_id:
1446                 amount += line.debit - line.credit
1447                 line_ids.append(line.id)
1448                 if line.state=='draft':
1449                     line_draft_ids.append(line.id)
1450
1451                 if not company_id:
1452                     company_id = line.account_id.company_id.id
1453                 if not company_id == line.account_id.company_id.id:
1454                     raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1455
1456                 if line.account_id.currency_id and line.currency_id:
1457                     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):
1458                         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))
1459
1460             if abs(amount) < 10 ** -4:
1461                 # If the move is balanced
1462                 # Add to the list of valid moves
1463                 # (analytic lines will be created later for valid moves)
1464                 valid_moves.append(move)
1465
1466                 # Check whether the move lines are confirmed
1467
1468                 if not line_draft_ids:
1469                     continue
1470                 # Update the move lines (set them as valid)
1471
1472                 obj_move_line.write(cr, uid, line_draft_ids, {
1473                     'journal_id': move.journal_id.id,
1474                     'period_id': move.period_id.id,
1475                     'state': 'valid'
1476                 }, context, check=False)
1477
1478                 account = {}
1479                 account2 = {}
1480
1481                 if journal.type in ('purchase','sale'):
1482                     for line in move.line_id:
1483                         code = amount = 0
1484                         key = (line.account_id.id, line.tax_code_id.id)
1485                         if key in account2:
1486                             code = account2[key][0]
1487                             amount = account2[key][1] * (line.debit + line.credit)
1488                         elif line.account_id.id in account:
1489                             code = account[line.account_id.id][0]
1490                             amount = account[line.account_id.id][1] * (line.debit + line.credit)
1491                         if (code or amount) and not (line.tax_code_id or line.tax_amount):
1492                             obj_move_line.write(cr, uid, [line.id], {
1493                                 'tax_code_id': code,
1494                                 'tax_amount': amount
1495                             }, context, check=False)
1496             elif journal.centralisation:
1497                 # If the move is not balanced, it must be centralised...
1498
1499                 # Add to the list of valid moves
1500                 # (analytic lines will be created later for valid moves)
1501                 valid_moves.append(move)
1502
1503                 #
1504                 # Update the move lines (set them as valid)
1505                 #
1506                 self._centralise(cr, uid, move, 'debit', context=context)
1507                 self._centralise(cr, uid, move, 'credit', context=context)
1508                 obj_move_line.write(cr, uid, line_draft_ids, {
1509                     'state': 'valid'
1510                 }, context, check=False)
1511             else:
1512                 # We can't validate it (it's unbalanced)
1513                 # Setting the lines as draft
1514                 obj_move_line.write(cr, uid, line_ids, {
1515                     'journal_id': move.journal_id.id,
1516                     'period_id': move.period_id.id,
1517                     'state': 'draft'
1518                 }, context, check=False)
1519         # Create analytic lines for the valid moves
1520         for record in valid_moves:
1521             obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1522
1523         valid_moves = [move.id for move in valid_moves]
1524         return len(valid_moves) > 0 and valid_moves or False
1525
1526 account_move()
1527
1528 class account_move_reconcile(osv.osv):
1529     _name = "account.move.reconcile"
1530     _description = "Account Reconciliation"
1531     _columns = {
1532         'name': fields.char('Name', size=64, required=True),
1533         'type': fields.char('Type', size=16, required=True),
1534         'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1535         'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1536         'create_date': fields.date('Creation date', readonly=True),
1537     }
1538     _defaults = {
1539         'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1540     }
1541     def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None):
1542         total = 0.0
1543         for rec in self.browse(cr, uid, ids, context=context):
1544             for line in rec.line_partial_ids:
1545                 total += (line.debit or 0.0) - (line.credit or 0.0)
1546         if not total:
1547             self.pool.get('account.move.line').write(cr, uid,
1548                 map(lambda x: x.id, rec.line_partial_ids),
1549                 {'reconcile_id': rec.id }
1550             )
1551         return True
1552
1553     def name_get(self, cr, uid, ids, context=None):
1554         if not ids:
1555             return []
1556         result = []
1557         for r in self.browse(cr, uid, ids, context=context):
1558             total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1559             if total:
1560                 name = '%s (%.2f)' % (r.name, total)
1561                 result.append((r.id,name))
1562             else:
1563                 result.append((r.id,r.name))
1564         return result
1565
1566 account_move_reconcile()
1567
1568 #----------------------------------------------------------
1569 # Tax
1570 #----------------------------------------------------------
1571 """
1572 a documenter
1573 child_depend: la taxe depend des taxes filles
1574 """
1575 class account_tax_code(osv.osv):
1576     """
1577     A code for the tax object.
1578
1579     This code is used for some tax declarations.
1580     """
1581     def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1582         parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1583         if context.get('based_on', 'invoices') == 'payments':
1584             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1585                     FROM account_move_line AS line, \
1586                         account_move AS move \
1587                         LEFT JOIN account_invoice invoice ON \
1588                             (invoice.move_id = move.id) \
1589                     WHERE line.tax_code_id IN %s '+where+' \
1590                         AND move.id = line.move_id \
1591                         AND ((invoice.state = \'paid\') \
1592                             OR (invoice.id IS NULL)) \
1593                             GROUP BY line.tax_code_id',
1594                                 (parent_ids,) + where_params)
1595         else:
1596             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1597                     FROM account_move_line AS line, \
1598                     account_move AS move \
1599                     WHERE line.tax_code_id IN %s '+where+' \
1600                     AND move.id = line.move_id \
1601                     GROUP BY line.tax_code_id',
1602                        (parent_ids,) + where_params)
1603         res=dict(cr.fetchall())
1604         obj_precision = self.pool.get('decimal.precision')
1605         res2 = {} 
1606         for record in self.browse(cr, uid, ids, context=context):
1607             def _rec_get(record):
1608                 amount = res.get(record.id, 0.0)
1609                 for rec in record.child_ids:
1610                     amount += _rec_get(rec) * rec.sign
1611                 return amount
1612             res2[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1613         return res2
1614
1615     def _sum_year(self, cr, uid, ids, name, args, context=None):
1616         if context is None:
1617             context = {}
1618         move_state = ('posted', )
1619         if context.get('state', 'all') == 'all':
1620             move_state = ('draft', 'posted', )
1621         if context.get('fiscalyear_id', False):
1622             fiscalyear_id = context['fiscalyear_id']
1623         else:
1624             fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1625         where = ''
1626         where_params = ()
1627         if fiscalyear_id:
1628             pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1629             if pids:
1630                 where = ' AND line.period_id IN %s AND move.state IN %s '
1631                 where_params = (tuple(pids), move_state)
1632         return self._sum(cr, uid, ids, name, args, context,
1633                 where=where, where_params=where_params)
1634
1635     def _sum_period(self, cr, uid, ids, name, args, context):
1636         if context is None:
1637             context = {}
1638         move_state = ('posted', )
1639         if context.get('state', False) == 'all':
1640             move_state = ('draft', 'posted', )
1641         if context.get('period_id', False):
1642             period_id = context['period_id']
1643         else:
1644             period_id = self.pool.get('account.period').find(cr, uid)
1645             if not period_id:
1646                 return dict.fromkeys(ids, 0.0)
1647             period_id = period_id[0]
1648         return self._sum(cr, uid, ids, name, args, context,
1649                 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1650
1651     _name = 'account.tax.code'
1652     _description = 'Tax Code'
1653     _rec_name = 'code'
1654     _columns = {
1655         'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1656         'code': fields.char('Case Code', size=64),
1657         'info': fields.text('Description'),
1658         'sum': fields.function(_sum_year, string="Year Sum"),
1659         'sum_period': fields.function(_sum_period, string="Period Sum"),
1660         'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1661         'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1662         'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1663         'company_id': fields.many2one('res.company', 'Company', required=True),
1664         '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.'),
1665         '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"),
1666     }
1667
1668     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1669         if not args:
1670             args = []
1671         if context is None:
1672             context = {}
1673         ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1674         return self.name_get(cr, user, ids, context)
1675
1676     def name_get(self, cr, uid, ids, context=None):
1677         if isinstance(ids, (int, long)):
1678             ids = [ids]
1679         if not ids:
1680             return []
1681         if isinstance(ids, (int, long)):
1682             ids = [ids]
1683         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1684         return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1685                 for x in reads]
1686
1687     def _default_company(self, cr, uid, context=None):
1688         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1689         if user.company_id:
1690             return user.company_id.id
1691         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1692     _defaults = {
1693         'company_id': _default_company,
1694         'sign': 1.0,
1695         'notprintable': False,
1696     }
1697
1698     def copy(self, cr, uid, id, default=None, context=None):
1699         if default is None:
1700             default = {}
1701         default = default.copy()
1702         default.update({'line_ids': []})
1703         return super(account_tax_code, self).copy(cr, uid, id, default, context)
1704
1705     _check_recursion = check_cycle
1706     _constraints = [
1707         (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1708     ]
1709     _order = 'code'
1710
1711 account_tax_code()
1712
1713 class account_tax(osv.osv):
1714     """
1715     A tax object.
1716
1717     Type: percent, fixed, none, code
1718         PERCENT: tax = price * amount
1719         FIXED: tax = price + amount
1720         NONE: no tax line
1721         CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1722             return result in the context
1723             Ex: result=round(price_unit*0.21,4)
1724     """
1725
1726     def get_precision_tax():
1727         def change_digit_tax(cr):
1728             res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account')
1729             return (16, res+2)
1730         return change_digit_tax
1731
1732     _name = 'account.tax'
1733     _description = 'Tax'
1734     _columns = {
1735         'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1736         '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."),
1737         'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For taxes of type percentage, enter % ratio between 0-1."),
1738         'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the tax without removing it."),
1739         'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1740             help="The computation method for the tax amount."),
1741         'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1742             help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1743         '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."),
1744         'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1745         'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1746         'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1747         'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1748         '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."),
1749         'python_compute':fields.text('Python Code'),
1750         'python_compute_inv':fields.text('Python Code (reverse)'),
1751         'python_applicable':fields.text('Python Code'),
1752
1753         #
1754         # Fields used for the VAT declaration
1755         #
1756         'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1757         'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1758         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1759         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1760
1761         # Same fields for refund invoices
1762
1763         'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1764         'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1765         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1766         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1767         '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"),
1768         'company_id': fields.many2one('res.company', 'Company', required=True),
1769         'description': fields.char('Tax Code',size=32),
1770         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1771         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1772
1773     }
1774
1775     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1776         """
1777         Returns a list of tupples containing id, name, as internally it is called {def name_get}
1778         result format: {[(id, name), (id, name), ...]}
1779
1780         @param cr: A database cursor
1781         @param user: ID of the user currently logged in
1782         @param name: name to search
1783         @param args: other arguments
1784         @param operator: default operator is 'ilike', it can be changed
1785         @param context: context arguments, like lang, time zone
1786         @param limit: Returns first 'n' ids of complete result, default is 80.
1787
1788         @return: Returns a list of tupples containing id and name
1789         """
1790         if not args:
1791             args = []
1792         if context is None:
1793             context = {}
1794         ids = []
1795         if name:
1796             ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context)
1797             if not ids:
1798                 ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context)
1799         else:
1800             ids = self.search(cr, user, args, limit=limit, context=context or {})
1801         return self.name_get(cr, user, ids, context=context)
1802
1803     def write(self, cr, uid, ids, vals, context=None):
1804         if vals.get('type', False) and vals['type'] in ('none', 'code'):
1805             vals.update({'amount': 0.0})
1806         return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1807
1808     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1809         journal_pool = self.pool.get('account.journal')
1810
1811         if context and context.has_key('type'):
1812             if context.get('type') in ('out_invoice','out_refund'):
1813                 args += [('type_tax_use','in',['sale','all'])]
1814             elif context.get('type') in ('in_invoice','in_refund'):
1815                 args += [('type_tax_use','in',['purchase','all'])]
1816
1817         if context and context.has_key('journal_id'):
1818             journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1819             if journal.type in ('sale', 'purchase'):
1820                 args += [('type_tax_use','in',[journal.type,'all'])]
1821
1822         return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1823
1824     def name_get(self, cr, uid, ids, context=None):
1825         if not ids:
1826             return []
1827         res = []
1828         for record in self.read(cr, uid, ids, ['description','name'], context=context):
1829             name = record['description'] and record['description'] or record['name']
1830             res.append((record['id'],name ))
1831         return res
1832
1833     def _default_company(self, cr, uid, context=None):
1834         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1835         if user.company_id:
1836             return user.company_id.id
1837         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1838
1839     _defaults = {
1840         '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''',
1841         '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''',
1842         'applicable_type': 'true',
1843         'type': 'percent',
1844         'amount': 0,
1845         'price_include': 0,
1846         'active': 1,
1847         'type_tax_use': 'all',
1848         'sequence': 1,
1849         'ref_tax_sign': 1,
1850         'ref_base_sign': 1,
1851         'tax_sign': 1,
1852         'base_sign': 1,
1853         'include_base_amount': False,
1854         'company_id': _default_company,
1855     }
1856     _order = 'sequence'
1857
1858     def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1859         res = []
1860         obj_partener_address = self.pool.get('res.partner.address')
1861         for tax in taxes:
1862             if tax.applicable_type=='code':
1863                 localdict = {'price_unit':price_unit, 'address':obj_partener_address.browse(cr, uid, address_id), 'product':product, 'partner':partner}
1864                 exec tax.python_applicable in localdict
1865                 if localdict.get('result', False):
1866                     res.append(tax)
1867             else:
1868                 res.append(tax)
1869         return res
1870
1871     def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1872         taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1873         res = []
1874         cur_price_unit=price_unit
1875         obj_partener_address = self.pool.get('res.partner.address')
1876         for tax in taxes:
1877             # we compute the amount for the current tax object and append it to the result
1878             data = {'id':tax.id,
1879                     'name':tax.description and tax.description + " - " + tax.name or tax.name,
1880                     'account_collected_id':tax.account_collected_id.id,
1881                     'account_paid_id':tax.account_paid_id.id,
1882                     'base_code_id': tax.base_code_id.id,
1883                     'ref_base_code_id': tax.ref_base_code_id.id,
1884                     'sequence': tax.sequence,
1885                     'base_sign': tax.base_sign,
1886                     'tax_sign': tax.tax_sign,
1887                     'ref_base_sign': tax.ref_base_sign,
1888                     'ref_tax_sign': tax.ref_tax_sign,
1889                     'price_unit': cur_price_unit,
1890                     'tax_code_id': tax.tax_code_id.id,
1891                     'ref_tax_code_id': tax.ref_tax_code_id.id,
1892             }
1893             res.append(data)
1894             if tax.type=='percent':
1895                 amount = cur_price_unit * tax.amount
1896                 data['amount'] = amount
1897
1898             elif tax.type=='fixed':
1899                 data['amount'] = tax.amount
1900                 data['tax_amount']=quantity
1901                # data['amount'] = quantity
1902             elif tax.type=='code':
1903                 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1904                 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1905                 exec tax.python_compute in localdict
1906                 amount = localdict['result']
1907                 data['amount'] = amount
1908             elif tax.type=='balance':
1909                 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1910                 data['balance'] = cur_price_unit
1911
1912             amount2 = data.get('amount', 0.0)
1913             if tax.child_ids:
1914                 if tax.child_depend:
1915                     latest = res.pop()
1916                 amount = amount2
1917                 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1918                 res.extend(child_tax)
1919                 if tax.child_depend:
1920                     for r in res:
1921                         for name in ('base','ref_base'):
1922                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1923                                 r[name+'_code_id'] = latest[name+'_code_id']
1924                                 r[name+'_sign'] = latest[name+'_sign']
1925                                 r['price_unit'] = latest['price_unit']
1926                                 latest[name+'_code_id'] = False
1927                         for name in ('tax','ref_tax'):
1928                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1929                                 r[name+'_code_id'] = latest[name+'_code_id']
1930                                 r[name+'_sign'] = latest[name+'_sign']
1931                                 r['amount'] = data['amount']
1932                                 latest[name+'_code_id'] = False
1933             if tax.include_base_amount:
1934                 cur_price_unit+=amount2
1935         return res
1936
1937     def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1938         """
1939         RETURN: {
1940                 'total': 0.0,                # Total without taxes
1941                 'total_included: 0.0,        # Total with taxes
1942                 'taxes': []                  # List of taxes, see compute for the format
1943             }
1944         """
1945         precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1946         totalin = totalex = round(price_unit * quantity, precision)
1947         tin = []
1948         tex = []
1949         for tax in taxes:
1950             if tax.price_include:
1951                 tin.append(tax)
1952             else:
1953                 tex.append(tax)
1954         tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
1955         for r in tin:
1956             totalex -= r.get('amount', 0.0)
1957         totlex_qty = 0.0
1958         try:
1959             totlex_qty = totalex/quantity
1960         except:
1961             pass
1962         tex = self._compute(cr, uid, tex, totlex_qty, quantity, address_id=address_id, product=product, partner=partner)
1963         for r in tex:
1964             totalin += r.get('amount', 0.0)
1965         return {
1966             'total': totalex,
1967             'total_included': totalin,
1968             'taxes': tin + tex
1969         }
1970
1971     def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1972         logger = netsvc.Logger()
1973         logger.notifyChannel("warning", netsvc.LOG_WARNING,
1974             "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
1975         return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
1976
1977     def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1978         """
1979         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1980
1981         RETURN:
1982             [ tax ]
1983             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1984             one tax for each tax id in IDS and their children
1985         """
1986         res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1987         total = 0.0
1988         precision_pool = self.pool.get('decimal.precision')
1989         for r in res:
1990             if r.get('balance',False):
1991                 r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
1992             else:
1993                 r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
1994                 total += r['amount']
1995         return res
1996
1997     def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1998         taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1999         obj_partener_address = self.pool.get('res.partner.address')
2000         res = []
2001         taxes.reverse()
2002         cur_price_unit = price_unit
2003
2004         tax_parent_tot = 0.0
2005         for tax in taxes:
2006             if (tax.type=='percent') and not tax.include_base_amount:
2007                 tax_parent_tot += tax.amount
2008
2009         for tax in taxes:
2010             if (tax.type=='fixed') and not tax.include_base_amount:
2011                 cur_price_unit -= tax.amount
2012
2013         for tax in taxes:
2014             if tax.type=='percent':
2015                 if tax.include_base_amount:
2016                     amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
2017                 else:
2018                     amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
2019
2020             elif tax.type=='fixed':
2021                 amount = tax.amount
2022
2023             elif tax.type=='code':
2024                 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
2025                 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
2026                 exec tax.python_compute_inv in localdict
2027                 amount = localdict['result']
2028             elif tax.type=='balance':
2029                 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2030
2031             if tax.include_base_amount:
2032                 cur_price_unit -= amount
2033                 todo = 0
2034             else:
2035                 todo = 1
2036             res.append({
2037                 'id': tax.id,
2038                 'todo': todo,
2039                 'name': tax.name,
2040                 'amount': amount,
2041                 'account_collected_id': tax.account_collected_id.id,
2042                 'account_paid_id': tax.account_paid_id.id,
2043                 'base_code_id': tax.base_code_id.id,
2044                 'ref_base_code_id': tax.ref_base_code_id.id,
2045                 'sequence': tax.sequence,
2046                 'base_sign': tax.base_sign,
2047                 'tax_sign': tax.tax_sign,
2048                 'ref_base_sign': tax.ref_base_sign,
2049                 'ref_tax_sign': tax.ref_tax_sign,
2050                 'price_unit': cur_price_unit,
2051                 'tax_code_id': tax.tax_code_id.id,
2052                 'ref_tax_code_id': tax.ref_tax_code_id.id,
2053             })
2054             if tax.child_ids:
2055                 if tax.child_depend:
2056                     del res[-1]
2057                     amount = price_unit
2058
2059             parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
2060             res.extend(parent_tax)
2061
2062         total = 0.0
2063         for r in res:
2064             if r['todo']:
2065                 total += r['amount']
2066         for r in res:
2067             r['price_unit'] -= total
2068             r['todo'] = 0
2069         return res
2070
2071     def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
2072         """
2073         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2074         Price Unit is a VAT included price
2075
2076         RETURN:
2077             [ tax ]
2078             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2079             one tax for each tax id in IDS and their children
2080         """
2081         res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
2082         total = 0.0
2083         obj_precision = self.pool.get('decimal.precision')
2084         for r in res:
2085             prec = obj_precision.precision_get(cr, uid, 'Account')
2086             if r.get('balance',False):
2087                 r['amount'] = round(r['balance'] * quantity, prec) - total
2088             else:
2089                 r['amount'] = round(r['amount'] * quantity, prec)
2090                 total += r['amount']
2091         return res
2092
2093 account_tax()
2094
2095 # ---------------------------------------------------------
2096 # Account Entries Models
2097 # ---------------------------------------------------------
2098
2099 class account_model(osv.osv):
2100     _name = "account.model"
2101     _description = "Account Model"
2102     _columns = {
2103         'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2104         'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2105         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2106         'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2107         'legend': fields.text('Legend', readonly=True, size=100),
2108     }
2109
2110     _defaults = {
2111         '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'),
2112     }
2113     def generate(self, cr, uid, ids, datas={}, context=None):
2114         move_ids = []
2115         entry = {}
2116         account_move_obj = self.pool.get('account.move')
2117         account_move_line_obj = self.pool.get('account.move.line')
2118         pt_obj = self.pool.get('account.payment.term')
2119
2120         if context is None:
2121             context = {}
2122
2123         if datas.get('date', False):
2124             context.update({'date': datas['date']})
2125
2126         period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
2127         if not period_id:
2128             raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
2129         period_id = period_id[0]
2130
2131         for model in self.browse(cr, uid, ids, context=context):
2132             entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')}
2133             move_id = account_move_obj.create(cr, uid, {
2134                 'ref': entry['name'],
2135                 'period_id': period_id,
2136                 'journal_id': model.journal_id.id,
2137                 'date': context.get('date',time.strftime('%Y-%m-%d'))
2138             })
2139             move_ids.append(move_id)
2140             for line in model.lines_id:
2141                 analytic_account_id = False
2142                 if line.analytic_account_id:
2143                     if not model.journal_id.analytic_journal_id:
2144                         raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2145                     analytic_account_id = line.analytic_account_id.id
2146                 val = {
2147                     'move_id': move_id,
2148                     'journal_id': model.journal_id.id,
2149                     'period_id': period_id,
2150                     'analytic_account_id': analytic_account_id
2151                 }
2152
2153                 date_maturity = time.strftime('%Y-%m-%d')
2154                 if line.date_maturity == 'partner':
2155                     if not line.partner_id:
2156                         raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2157                                                                 "\nPlease define partner on it!")%(line.name, model.name))
2158                     if line.partner_id.property_payment_term:
2159                         payment_term_id = line.partner_id.property_payment_term.id
2160                         pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2161                         if pterm_list:
2162                             pterm_list = [l[0] for l in pterm_list]
2163                             pterm_list.sort()
2164                             date_maturity = pterm_list[-1]
2165
2166                 val.update({
2167                     'name': line.name,
2168                     'quantity': line.quantity,
2169                     'debit': line.debit,
2170                     'credit': line.credit,
2171                     'account_id': line.account_id.id,
2172                     'move_id': move_id,
2173                     'partner_id': line.partner_id.id,
2174                     'date': context.get('date',time.strftime('%Y-%m-%d')),
2175                     'date_maturity': date_maturity
2176                 })
2177                 c = context.copy()
2178                 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
2179                 account_move_line_obj.create(cr, uid, val, context=c)
2180
2181         return move_ids
2182
2183 account_model()
2184
2185 class account_model_line(osv.osv):
2186     _name = "account.model.line"
2187     _description = "Account Model Entries"
2188     _columns = {
2189         'name': fields.char('Name', size=64, required=True),
2190         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones."),
2191         'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries."),
2192         'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2193         'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2194         'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2195         'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2196         'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2197         'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2198         'currency_id': fields.many2one('res.currency', 'Currency'),
2199         'partner_id': fields.many2one('res.partner', 'Partner'),
2200         '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."),
2201     }
2202     _order = 'sequence'
2203     _sql_constraints = [
2204         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
2205         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
2206     ]
2207 account_model_line()
2208
2209 # ---------------------------------------------------------
2210 # Account Subscription
2211 # ---------------------------------------------------------
2212
2213
2214 class account_subscription(osv.osv):
2215     _name = "account.subscription"
2216     _description = "Account Subscription"
2217     _columns = {
2218         'name': fields.char('Name', size=64, required=True),
2219         'ref': fields.char('Reference', size=16),
2220         'model_id': fields.many2one('account.model', 'Model', required=True),
2221         'date_start': fields.date('Start Date', required=True),
2222         'period_total': fields.integer('Number of Periods', required=True),
2223         'period_nbr': fields.integer('Period', required=True),
2224         'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2225         'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
2226         'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2227     }
2228     _defaults = {
2229         'date_start': lambda *a: time.strftime('%Y-%m-%d'),
2230         'period_type': 'month',
2231         'period_total': 12,
2232         'period_nbr': 1,
2233         'state': 'draft',
2234     }
2235     def state_draft(self, cr, uid, ids, context=None):
2236         self.write(cr, uid, ids, {'state':'draft'})
2237         return False
2238
2239     def check(self, cr, uid, ids, context=None):
2240         todone = []
2241         for sub in self.browse(cr, uid, ids, context=context):
2242             ok = True
2243             for line in sub.lines_id:
2244                 if not line.move_id.id:
2245                     ok = False
2246                     break
2247             if ok:
2248                 todone.append(sub.id)
2249         if todone:
2250             self.write(cr, uid, todone, {'state':'done'})
2251         return False
2252
2253     def remove_line(self, cr, uid, ids, context=None):
2254         toremove = []
2255         for sub in self.browse(cr, uid, ids, context=context):
2256             for line in sub.lines_id:
2257                 if not line.move_id.id:
2258                     toremove.append(line.id)
2259         if toremove:
2260             self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2261         self.write(cr, uid, ids, {'state':'draft'})
2262         return False
2263
2264     def compute(self, cr, uid, ids, context=None):
2265         for sub in self.browse(cr, uid, ids, context=context):
2266             ds = sub.date_start
2267             for i in range(sub.period_total):
2268                 self.pool.get('account.subscription.line').create(cr, uid, {
2269                     'date': ds,
2270                     'subscription_id': sub.id,
2271                 })
2272                 if sub.period_type=='day':
2273                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2274                 if sub.period_type=='month':
2275                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2276                 if sub.period_type=='year':
2277                     ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2278         self.write(cr, uid, ids, {'state':'running'})
2279         return True
2280
2281 account_subscription()
2282
2283 class account_subscription_line(osv.osv):
2284     _name = "account.subscription.line"
2285     _description = "Account Subscription Line"
2286     _columns = {
2287         'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2288         'date': fields.date('Date', required=True),
2289         'move_id': fields.many2one('account.move', 'Entry'),
2290     }
2291
2292     def move_create(self, cr, uid, ids, context=None):
2293         tocheck = {}
2294         all_moves = []
2295         obj_model = self.pool.get('account.model')
2296         for line in self.browse(cr, uid, ids, context=context):
2297             datas = {
2298                 'date': line.date,
2299             }
2300             move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2301             tocheck[line.subscription_id.id] = True
2302             self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2303             all_moves.extend(move_ids)
2304         if tocheck:
2305             self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2306         return all_moves
2307
2308     _rec_name = 'date'
2309
2310 account_subscription_line()
2311
2312 #  ---------------------------------------------------------------
2313 #   Account Templates: Account, Tax, Tax Code and chart. + Wizard
2314 #  ---------------------------------------------------------------
2315
2316 class account_tax_template(osv.osv):
2317     _name = 'account.tax.template'
2318 account_tax_template()
2319
2320 class account_account_template(osv.osv):
2321     _order = "code"
2322     _name = "account.account.template"
2323     _description ='Templates for Accounts'
2324
2325     _columns = {
2326         'name': fields.char('Name', size=128, required=True, select=True),
2327         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2328         'code': fields.char('Code', size=64, select=1),
2329         'type': fields.selection([
2330             ('receivable','Receivable'),
2331             ('payable','Payable'),
2332             ('view','View'),
2333             ('consolidation','Consolidation'),
2334             ('liquidity','Liquidity'),
2335             ('other','Regular'),
2336             ('closed','Closed'),
2337             ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2338             "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2339             "can have children accounts for multi-company consolidations, payable/receivable are for "\
2340             "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2341         'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2342             help="These types are defined according to your country. The type contains more information "\
2343             "about the account and its specificities."),
2344         'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2345         'shortcut': fields.char('Shortcut', size=12),
2346         'note': fields.text('Note'),
2347         'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2348         'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2349         'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2350         'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2351     }
2352
2353     _defaults = {
2354         'reconcile': False,
2355         'type': 'view',
2356         'nocreate': False,
2357     }
2358
2359     def _check_type(self, cr, uid, ids, context=None):
2360         if context is None:
2361             context = {}
2362         accounts = self.browse(cr, uid, ids, context=context)
2363         for account in accounts:
2364             if account.parent_id and account.parent_id.type != 'view':
2365                 return False
2366         return True
2367
2368     _check_recursion = check_cycle
2369     _constraints = [
2370         (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id']),
2371         (_check_type, 'Configuration Error! \nYou cannot define children to an account with internal type different of "View"! ', ['type']),
2372
2373     ]
2374
2375     def name_get(self, cr, uid, ids, context=None):
2376         if not ids:
2377             return []
2378         reads = self.read(cr, uid, ids, ['name','code'], context=context)
2379         res = []
2380         for record in reads:
2381             name = record['name']
2382             if record['code']:
2383                 name = record['code']+' '+name
2384             res.append((record['id'],name ))
2385         return res
2386
2387 account_account_template()
2388
2389 class account_add_tmpl_wizard(osv.osv_memory):
2390     """Add one more account from the template.
2391
2392     With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2393     _name = 'account.addtmpl.wizard'
2394
2395     def _get_def_cparent(self, cr, uid, context=None):
2396         acc_obj = self.pool.get('account.account')
2397         tmpl_obj = self.pool.get('account.account.template')
2398         tids = tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2399         if not tids or not tids[0]['parent_id']:
2400             return False
2401         ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2402         res = None
2403         if not ptids or not ptids[0]['code']:
2404             raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
2405             res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2406         return res and res[0] or False
2407
2408     _columns = {
2409         'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2410     }
2411     _defaults = {
2412         'cparent_id': _get_def_cparent,
2413     }
2414
2415     def action_create(self,cr,uid,ids,context=None):
2416         if context is None:
2417             context = {}
2418         acc_obj = self.pool.get('account.account')
2419         tmpl_obj = self.pool.get('account.account.template')
2420         data = self.read(cr, uid, ids)
2421         company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2422         account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2423         vals = {
2424             'name': account_template.name,
2425             'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2426             'code': account_template.code,
2427             'type': account_template.type,
2428             'user_type': account_template.user_type and account_template.user_type.id or False,
2429             'reconcile': account_template.reconcile,
2430             'shortcut': account_template.shortcut,
2431             'note': account_template.note,
2432             'parent_id': data[0]['cparent_id'],
2433             'company_id': company_id,
2434             }
2435         acc_obj.create(cr, uid, vals)
2436         return {'type':'state', 'state': 'end' }
2437
2438     def action_cancel(self, cr, uid, ids, context=None):
2439         return { 'type': 'state', 'state': 'end' }
2440
2441 account_add_tmpl_wizard()
2442
2443 class account_tax_code_template(osv.osv):
2444
2445     _name = 'account.tax.code.template'
2446     _description = 'Tax Code Template'
2447     _order = 'code'
2448     _rec_name = 'code'
2449     _columns = {
2450         'name': fields.char('Tax Case Name', size=64, required=True),
2451         'code': fields.char('Case Code', size=64),
2452         'info': fields.text('Description'),
2453         'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2454         'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2455         'sign': fields.float('Sign For Parent', required=True),
2456         '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"),
2457     }
2458
2459     _defaults = {
2460         'sign': 1.0,
2461         'notprintable': False,
2462     }
2463
2464     def name_get(self, cr, uid, ids, context=None):
2465         if not ids:
2466             return []
2467         if isinstance(ids, (int, long)):
2468             ids = [ids]
2469         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2470         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2471                 for x in reads]
2472
2473     _check_recursion = check_cycle
2474     _constraints = [
2475         (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2476     ]
2477     _order = 'code,name'
2478 account_tax_code_template()
2479
2480
2481 class account_chart_template(osv.osv):
2482     _name="account.chart.template"
2483     _description= "Templates for Account Chart"
2484
2485     _columns={
2486         'name': fields.char('Name', size=64, required=True),
2487         'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
2488         'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
2489         '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'),
2490         'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
2491         'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2492         'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2493         'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2494         'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2495         'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2496         'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2497         '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'),
2498         'property_account_income_opening': fields.many2one('account.account.template','Opening Entries Income Account'),
2499         'property_account_expense_opening': fields.many2one('account.account.template','Opening Entries Expense Account'),
2500     }
2501
2502 account_chart_template()
2503
2504 class account_tax_template(osv.osv):
2505
2506     _name = 'account.tax.template'
2507     _description = 'Templates for Taxes'
2508
2509     _columns = {
2510         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2511         'name': fields.char('Tax Name', size=64, required=True),
2512         '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."),
2513         'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2514         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True),
2515         '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."),
2516         '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."),
2517         'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2518         'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2519         'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2520         '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."),
2521         'python_compute':fields.text('Python Code'),
2522         'python_compute_inv':fields.text('Python Code (reverse)'),
2523         'python_applicable':fields.text('Python Code'),
2524
2525         #
2526         # Fields used for the VAT declaration
2527         #
2528         'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2529         'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2530         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2531         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2532
2533         # Same fields for refund invoices
2534
2535         'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2536         'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2537         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2538         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2539         '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."),
2540         'description': fields.char('Internal Name', size=32),
2541         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,),
2542         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
2543     }
2544
2545     def name_get(self, cr, uid, ids, context=None):
2546         if not ids:
2547             return []
2548         res = []
2549         for record in self.read(cr, uid, ids, ['description','name'], context=context):
2550             name = record['description'] and record['description'] or record['name']
2551             res.append((record['id'],name ))
2552         return res
2553
2554     def _default_company(self, cr, uid, context=None):
2555         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2556         if user.company_id:
2557             return user.company_id.id
2558         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2559
2560     _defaults = {
2561         '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''',
2562         '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''',
2563         'applicable_type': 'true',
2564         'type': 'percent',
2565         'amount': 0,
2566         'sequence': 1,
2567         'ref_tax_sign': 1,
2568         'ref_base_sign': 1,
2569         'tax_sign': 1,
2570         'base_sign': 1,
2571         'include_base_amount': False,
2572         'type_tax_use': 'all',
2573         'price_include': 0,
2574     }
2575     _order = 'sequence'
2576
2577 account_tax_template()
2578
2579 # Fiscal Position Templates
2580
2581 class account_fiscal_position_template(osv.osv):
2582     _name = 'account.fiscal.position.template'
2583     _description = 'Template for Fiscal Position'
2584
2585     _columns = {
2586         'name': fields.char('Fiscal Position Template', size=64, required=True),
2587         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2588         'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2589         'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2590     }
2591
2592 account_fiscal_position_template()
2593
2594 class account_fiscal_position_tax_template(osv.osv):
2595     _name = 'account.fiscal.position.tax.template'
2596     _description = 'Template Tax Fiscal Position'
2597     _rec_name = 'position_id'
2598
2599     _columns = {
2600         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2601         'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2602         'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2603     }
2604
2605 account_fiscal_position_tax_template()
2606
2607 class account_fiscal_position_account_template(osv.osv):
2608     _name = 'account.fiscal.position.account.template'
2609     _description = 'Template Account Fiscal Mapping'
2610     _rec_name = 'position_id'
2611     _columns = {
2612         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2613         'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2614         'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2615     }
2616
2617 account_fiscal_position_account_template()
2618
2619     # Multi charts of Accounts wizard
2620
2621 class wizard_multi_charts_accounts(osv.osv_memory):
2622     """
2623     Create a new account chart for a company.
2624     Wizards ask for:
2625         * a company
2626         * an account chart template
2627         * a number of digits for formatting code of non-view accounts
2628         * a list of bank accounts owned by the company
2629     Then, the wizard:
2630         * generates all accounts from the template and assigns them to the right company
2631         * generates all taxes and tax codes, changing account assignations
2632         * generates all accounting properties and assigns them correctly
2633     """
2634     _name='wizard.multi.charts.accounts'
2635     _inherit = 'res.config'
2636
2637     _columns = {
2638         'company_id':fields.many2one('res.company', 'Company', required=True),
2639         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2640         'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts', required=True),
2641         'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2642         '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."),
2643         "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
2644         "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
2645     }
2646     def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
2647         res = {}
2648         res['value'] = {}
2649         res['value']["sale_tax"] = False
2650         res['value']["purchase_tax"] = False
2651         if chart_template_id:
2652             # 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
2653             sale_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2654                                           , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence, id desc")
2655             purchase_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2656                                           , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence, id desc")
2657
2658             res['value']["sale_tax"] = sale_tax_ids and sale_tax_ids[0] or False
2659             res['value']["purchase_tax"] = purchase_tax_ids and purchase_tax_ids[0] or False
2660         return res
2661
2662     def _get_purchase_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             purchase_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2667                                           , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence")
2668             return purchase_tax_ids and purchase_tax_ids[0] or False
2669         return False
2670
2671     def _get_sale_tax(self, cr, uid, context=None):
2672         ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2673         if ids:
2674             chart_template_id = ids[0]
2675             sale_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2676                                           , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence")
2677             return sale_tax_ids and sale_tax_ids[0] or False
2678         return False
2679
2680     def _get_chart(self, cr, uid, context=None):
2681         ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2682         if ids:
2683             return ids[0]
2684         return False
2685
2686     def _get_default_accounts(self, cr, uid, context=None):
2687         return [{'acc_name': _('Current'),'account_type':'bank'},
2688                     {'acc_name': _('Deposit'),'account_type':'bank'},
2689                     {'acc_name': _('Cash'),'account_type':'cash'}]
2690
2691     _defaults = {
2692         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, [uid], c)[0].company_id.id,
2693         'chart_template_id': _get_chart,
2694         'bank_accounts_id': _get_default_accounts,
2695         'sale_tax': _get_sale_tax,
2696         'purchase_tax': _get_purchase_tax,
2697         'code_digits': 6,
2698         'seq_journal': True
2699     }
2700
2701     def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2702         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)
2703         cmp_select = []
2704         company_ids = self.pool.get('res.company').search(cr, uid, [], context=context)
2705         #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)
2706         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",))
2707         configured_cmp = [r[0] for r in cr.fetchall()]
2708         unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
2709         for field in res['fields']:
2710             if field == 'company_id':
2711                 res['fields'][field]['domain'] = unconfigured_cmp
2712                 res['fields'][field]['selection'] = [('', '')]
2713                 if unconfigured_cmp:
2714                     cmp_select = [(line.id, line.name) for line in self.pool.get('res.company').browse(cr, uid, unconfigured_cmp)]
2715                     res['fields'][field]['selection'] = cmp_select
2716         return res
2717
2718     def execute(self, cr, uid, ids, context=None):
2719         obj_multi = self.browse(cr, uid, ids[0])
2720         obj_acc = self.pool.get('account.account')
2721         obj_acc_tax = self.pool.get('account.tax')
2722         obj_journal = self.pool.get('account.journal')
2723         obj_acc_template = self.pool.get('account.account.template')
2724         obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2725         obj_fiscal_position = self.pool.get('account.fiscal.position')
2726         obj_data = self.pool.get('ir.model.data')
2727         analytic_journal_obj = self.pool.get('account.analytic.journal')
2728         obj_tax_code = self.pool.get('account.tax.code')
2729         obj_tax_code_template = self.pool.get('account.tax.code.template')
2730         ir_values_obj = self.pool.get('ir.values')
2731         # Creating Account
2732         obj_acc_root = obj_multi.chart_template_id.account_root_id
2733         tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2734         company_id = obj_multi.company_id.id
2735
2736         #new code
2737         acc_template_ref = {}
2738         tax_template_ref = {}
2739         tax_code_template_ref = {}
2740         todo_dict = {}
2741
2742         #create all the tax code
2743         children_tax_code_template = obj_tax_code_template.search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2744         children_tax_code_template.sort()
2745         for tax_code_template in obj_tax_code_template.browse(cr, uid, children_tax_code_template, context=context):
2746             vals = {
2747                 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2748                 'code': tax_code_template.code,
2749                 'info': tax_code_template.info,
2750                 '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,
2751                 'company_id': company_id,
2752                 'sign': tax_code_template.sign,
2753             }
2754             new_tax_code = obj_tax_code.create(cr, uid, vals)
2755             #recording the new tax code to do the mapping
2756             tax_code_template_ref[tax_code_template.id] = new_tax_code
2757
2758         #create all the tax
2759         tax_template_to_tax = {}
2760         for tax in obj_multi.chart_template_id.tax_template_ids:
2761             #create it
2762             vals_tax = {
2763                 'name':tax.name,
2764                 'sequence': tax.sequence,
2765                 'amount':tax.amount,
2766                 'type':tax.type,
2767                 'applicable_type': tax.applicable_type,
2768                 'domain':tax.domain,
2769                 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2770                 'child_depend': tax.child_depend,
2771                 'python_compute': tax.python_compute,
2772                 'python_compute_inv': tax.python_compute_inv,
2773                 'python_applicable': tax.python_applicable,
2774                 '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,
2775                 '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,
2776                 'base_sign': tax.base_sign,
2777                 'tax_sign': tax.tax_sign,
2778                 '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,
2779                 '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,
2780                 'ref_base_sign': tax.ref_base_sign,
2781                 'ref_tax_sign': tax.ref_tax_sign,
2782                 'include_base_amount': tax.include_base_amount,
2783                 'description':tax.description,
2784                 'company_id': company_id,
2785                 'type_tax_use': tax.type_tax_use,
2786                 'price_include': tax.price_include
2787             }
2788             new_tax = obj_acc_tax.create(cr, uid, vals_tax)
2789             tax_template_to_tax[tax.id] = new_tax
2790             #as the accounts have not been created yet, we have to wait before filling these fields
2791             todo_dict[new_tax] = {
2792                 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2793                 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2794             }
2795             tax_template_ref[tax.id] = new_tax
2796         #deactivate the parent_store functionnality on account_account for rapidity purpose
2797         ctx = context and context.copy() or {}
2798         ctx['defer_parent_store_computation'] = True
2799
2800         children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2801         children_acc_template.sort()
2802         for account_template in obj_acc_template.browse(cr, uid, children_acc_template, context=context):
2803             tax_ids = []
2804             for tax in account_template.tax_ids:
2805                 tax_ids.append(tax_template_ref[tax.id])
2806             #create the account_account
2807
2808             dig = obj_multi.code_digits
2809             code_main = account_template.code and len(account_template.code) or 0
2810             code_acc = account_template.code or ''
2811             if code_main>0 and code_main<=dig and account_template.type != 'view':
2812                 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2813             vals={
2814                 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2815                 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2816                 'code': code_acc,
2817                 'type': account_template.type,
2818                 'user_type': account_template.user_type and account_template.user_type.id or False,
2819                 'reconcile': account_template.reconcile,
2820                 'shortcut': account_template.shortcut,
2821                 'note': account_template.note,
2822                 '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,
2823                 'tax_ids': [(6,0,tax_ids)],
2824                 'company_id': company_id,
2825             }
2826             new_account = obj_acc.create(cr, uid, vals, context=ctx)
2827             acc_template_ref[account_template.id] = new_account
2828
2829
2830         #reactivate the parent_store functionnality on account_account
2831         obj_acc._parent_store_compute(cr)
2832
2833         for key,value in todo_dict.items():
2834             if value['account_collected_id'] or value['account_paid_id']:
2835                 obj_acc_tax.write(cr, uid, [key], {
2836                     'account_collected_id': acc_template_ref.get(value['account_collected_id'], False),
2837                     'account_paid_id': acc_template_ref.get(value['account_paid_id'], False),
2838                 })
2839
2840         # Creating Journals
2841         data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_sp_journal_view')])
2842         data = obj_data.browse(cr, uid, data_id[0], context=context)
2843         view_id = data.res_id
2844
2845         #Sales Journal
2846         analytical_sale_ids = analytic_journal_obj.search(cr,uid,[('type','=','sale')])
2847         analytical_journal_sale = analytical_sale_ids and analytical_sale_ids[0] or False
2848
2849         vals_journal = {
2850             'name': _('Sales Journal'),
2851             'type': 'sale',
2852             'code': _('SAJ'),
2853             'view_id': view_id,
2854             'company_id': company_id,
2855             'analytic_journal_id': analytical_journal_sale,
2856         }
2857
2858         if obj_multi.chart_template_id.property_account_receivable:
2859             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2860             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2861
2862         obj_journal.create(cr,uid,vals_journal)
2863
2864         # Purchase Journal
2865         analytical_purchase_ids = analytic_journal_obj.search(cr,uid,[('type','=','purchase')])
2866         analytical_journal_purchase = analytical_purchase_ids and analytical_purchase_ids[0] or False
2867
2868         vals_journal = {
2869             'name': _('Purchase Journal'),
2870             'type': 'purchase',
2871             'code': _('EXJ'),
2872             'view_id': view_id,
2873             'company_id':  company_id,
2874             'analytic_journal_id': analytical_journal_purchase,
2875         }
2876
2877         if obj_multi.chart_template_id.property_account_payable:
2878             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2879             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2880         obj_journal.create(cr,uid,vals_journal)
2881
2882         # Creating Journals Sales Refund and Purchase Refund
2883         data_id = obj_data.search(cr, uid, [('model', '=', 'account.journal.view'), ('name', '=', 'account_sp_refund_journal_view')], context=context)
2884         data = obj_data.browse(cr, uid, data_id[0], context=context)
2885         view_id = data.res_id
2886
2887         #Sales Refund Journal
2888         vals_journal = {
2889             'name': _('Sales Refund Journal'),
2890             'type': 'sale_refund',
2891             'code': _('SCNJ'),
2892             'view_id': view_id,
2893             'analytic_journal_id': analytical_journal_sale,
2894             'company_id': company_id
2895         }
2896
2897         if obj_multi.chart_template_id.property_account_receivable:
2898             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2899             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2900
2901         obj_journal.create(cr, uid, vals_journal, context=context)
2902
2903         # Purchase Refund Journal
2904         vals_journal = {
2905             'name': _('Purchase Refund Journal'),
2906             'type': 'purchase_refund',
2907             'code': _('ECNJ'),
2908             'view_id': view_id,
2909             'analytic_journal_id': analytical_journal_purchase,
2910             'company_id': company_id
2911         }
2912
2913         if obj_multi.chart_template_id.property_account_payable:
2914             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2915             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2916
2917         obj_journal.create(cr, uid, vals_journal, context=context)
2918
2919         # Miscellaneous Journal
2920         data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_view')])
2921         data = obj_data.browse(cr, uid, data_id[0], context=context)
2922         view_id = data.res_id
2923
2924         analytical_miscellaneous_ids = analytic_journal_obj.search(cr, uid, [('type', '=', 'situation')], context=context)
2925         analytical_journal_miscellaneous = analytical_miscellaneous_ids and analytical_miscellaneous_ids[0] or False
2926
2927         vals_journal = {
2928             'name': _('Miscellaneous Journal'),
2929             'type': 'general',
2930             'code': _('MISC'),
2931             'view_id': view_id,
2932             'analytic_journal_id': analytical_journal_miscellaneous,
2933             'company_id': company_id
2934         }
2935
2936         obj_journal.create(cr, uid, vals_journal, context=context)
2937
2938         # Opening Entries Journal
2939         if obj_multi.chart_template_id.property_account_income_opening and obj_multi.chart_template_id.property_account_expense_opening:
2940             vals_journal = {
2941                 'name': _('Opening Entries Journal'),
2942                 'type': 'situation',
2943                 'code': _('OPEJ'),
2944                 'view_id': view_id,
2945                 'company_id': company_id,
2946                 'centralisation': True,
2947                 'default_credit_account_id': acc_template_ref[obj_multi.chart_template_id.property_account_income_opening.id],
2948                 'default_debit_account_id': acc_template_ref[obj_multi.chart_template_id.property_account_expense_opening.id]
2949                 }
2950             obj_journal.create(cr, uid, vals_journal, context=context)
2951
2952         # Bank Journals
2953         data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')])
2954         data = obj_data.browse(cr, uid, data_id[0], context=context)
2955         view_id_cash = data.res_id
2956
2957         data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view_multi')])
2958         data = obj_data.browse(cr, uid, data_id[0], context=context)
2959         view_id_cur = data.res_id
2960         ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2961
2962         current_num = 1
2963         valid = True
2964         for line in obj_multi.bank_accounts_id:
2965             #create the account_account for this bank journal
2966             tmp = line.acc_name
2967             dig = obj_multi.code_digits
2968             if not ref_acc_bank.code:
2969                 raise osv.except_osv(_('Configuration Error !'), _('The bank account defined on the selected chart of account hasn\'t a code.'))
2970             while True:
2971                 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)), '0')) + str(current_num)
2972                 ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)])
2973                 if not ids:
2974                     break
2975                 else:
2976                     current_num += 1
2977             vals = {
2978                 'name': tmp,
2979                 'currency_id': line.currency_id and line.currency_id.id or False,
2980                 'code': new_code,
2981                 'type': 'liquidity',
2982                 'user_type': account_template.user_type and account_template.user_type.id or False,
2983                 'reconcile': True,
2984                 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2985                 'company_id': company_id,
2986             }
2987             acc_cash_id  = obj_acc.create(cr,uid,vals)
2988
2989             #create the bank journal
2990             vals_journal = {
2991                 'name': vals['name'],
2992                 'code': _('BNK') + str(current_num),
2993                 'type': line.account_type == 'cash' and 'cash' or 'bank',
2994                 'company_id': company_id,
2995                 'analytic_journal_id': False,
2996                 'currency_id': False,
2997             }
2998             if line.currency_id:
2999                 vals_journal['view_id'] = view_id_cur
3000                 vals_journal['currency'] = line.currency_id.id
3001             else:
3002                 vals_journal['view_id'] = view_id_cash
3003             vals_journal['default_credit_account_id'] = acc_cash_id
3004             vals_journal['default_debit_account_id'] = acc_cash_id
3005             obj_journal.create(cr, uid, vals_journal)
3006             current_num += 1
3007             valid = True
3008
3009         #create the properties
3010         property_obj = self.pool.get('ir.property')
3011         fields_obj = self.pool.get('ir.model.fields')
3012
3013         todo_list = [
3014             ('property_account_receivable','res.partner','account.account'),
3015             ('property_account_payable','res.partner','account.account'),
3016             ('property_account_expense_categ','product.category','account.account'),
3017             ('property_account_income_categ','product.category','account.account'),
3018             ('property_account_expense','product.template','account.account'),
3019             ('property_account_income','product.template','account.account'),
3020             ('property_reserve_and_surplus_account','res.company','account.account')
3021         ]
3022         for record in todo_list:
3023             r = []
3024             r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
3025             account = getattr(obj_multi.chart_template_id, record[0])
3026             field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
3027             vals = {
3028                 'name': record[0],
3029                 'company_id': company_id,
3030                 'fields_id': field[0],
3031                 'value': account and 'account.account,' + str(acc_template_ref[account.id]) or False,
3032             }
3033
3034             if r:
3035                 #the property exist: modify it
3036                 property_obj.write(cr, uid, r, vals)
3037             else:
3038                 #create the property
3039                 property_obj.create(cr, uid, vals)
3040
3041         fp_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)])
3042
3043         if fp_ids:
3044             obj_tax_fp = self.pool.get('account.fiscal.position.tax')
3045             obj_ac_fp = self.pool.get('account.fiscal.position.account')
3046
3047             for position in obj_fiscal_position_template.browse(cr, uid, fp_ids, context=context):
3048
3049                 vals_fp = {
3050                     'company_id': company_id,
3051                     'name': position.name,
3052                 }
3053                 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
3054
3055                 for tax in position.tax_ids:
3056                     vals_tax = {
3057                         'tax_src_id': tax_template_ref[tax.tax_src_id.id],
3058                         'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
3059                         'position_id': new_fp,
3060                     }
3061                     obj_tax_fp.create(cr, uid, vals_tax)
3062
3063                 for acc in position.account_ids:
3064                     vals_acc = {
3065                         'account_src_id': acc_template_ref[acc.account_src_id.id],
3066                         'account_dest_id': acc_template_ref[acc.account_dest_id.id],
3067                         'position_id': new_fp,
3068                     }
3069                     obj_ac_fp.create(cr, uid, vals_acc)
3070
3071         if obj_multi.sale_tax:
3072             ir_values_obj.set(cr, uid, key='default', key2=False, name="taxes_id", company=obj_multi.company_id.id,
3073                             models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.sale_tax.id]])
3074         if obj_multi.purchase_tax:
3075             ir_values_obj.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=obj_multi.company_id.id,
3076                             models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.purchase_tax.id]])
3077
3078 wizard_multi_charts_accounts()
3079
3080 class account_bank_accounts_wizard(osv.osv_memory):
3081     _name='account.bank.accounts.wizard'
3082
3083     _columns = {
3084         'acc_name': fields.char('Account Name.', size=64, required=True),
3085         'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
3086         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
3087         'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
3088     }
3089
3090 account_bank_accounts_wizard()
3091
3092 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: