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