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