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