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