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