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