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