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