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