[FIX] account: avoid a traceback if credit/debit/balance sum can be computed
[odoo/odoo.git] / addons / account / account.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22 import time
23 import netsvc
24
25 from osv import fields, osv
26
27 from tools.misc import currency
28 from tools.translate import _
29 import pooler
30 import mx.DateTime
31 from mx.DateTime import RelativeDateTime, now, DateTime, localtime
32
33 from tools import config
34
35 class account_payment_term(osv.osv):
36     _name = "account.payment.term"
37     _description = "Payment Term"
38     _columns = {
39         'name': fields.char('Payment Term', size=64, translate=True, required=True),
40         'active': fields.boolean('Active'),
41         'note': fields.text('Description', translate=True),
42         'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
43     }
44     _defaults = {
45         'active': lambda *a: 1,
46     }
47     _order = "name"
48
49     def compute(self, cr, uid, id, value, date_ref=False, context={}):
50         if not date_ref:
51             date_ref = now().strftime('%Y-%m-%d')
52         pt = self.browse(cr, uid, id, context)
53         amount = value
54         result = []
55         for line in pt.line_ids:
56             if line.value == 'fixed':
57                 amt = round(line.value_amount, int(config['price_accuracy']))
58             elif line.value == 'procent':
59                 amt = round(value * line.value_amount, int(config['price_accuracy']))
60             elif line.value == 'balance':
61                 amt = round(amount, int(config['price_accuracy']))
62             if amt:
63                 next_date = mx.DateTime.strptime(date_ref, '%Y-%m-%d') + RelativeDateTime(days=line.days)
64                 if line.days2 < 0:
65                     next_date += RelativeDateTime(day=line.days2)
66                 if line.days2 > 0:
67                     next_date += RelativeDateTime(day=line.days2, months=1)
68                 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
69                 amount -= amt
70         return result
71
72 account_payment_term()
73
74 class account_payment_term_line(osv.osv):
75     _name = "account.payment.term.line"
76     _description = "Payment Term Line"
77     _columns = {
78         'name': fields.char('Line Name', size=32, required=True),
79         '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"),
80         'value': fields.selection([('procent', 'Percent'),
81                                    ('balance', 'Balance'),
82                                    ('fixed', 'Fixed Amount')], 'Value',
83                                    required=True, help="""Example: 14 days 2%, 30 days net
84 1. Line 1: percent 0.02 14 days
85 2. Line 2: balance 30 days"""),
86
87         'value_amount': fields.float('Value Amount', help="For Value percent enter % ratio between 0-1."),
88         'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
89             "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
90         '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)."),
91         'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
92     }
93     _defaults = {
94         'value': lambda *a: 'balance',
95         'sequence': lambda *a: 5,
96         'days2': lambda *a: 0,
97     }
98     _order = "sequence"
99
100     def _check_percent(self, cr, uid, ids, context={}):
101         obj = self.browse(cr, uid, ids[0])
102         if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
103             return False
104         return True
105
106     _constraints = [
107         (_check_percent, _('Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% '), ['value_amount']),
108     ]
109     
110 account_payment_term_line()
111
112
113 class account_account_type(osv.osv):
114     _name = "account.account.type"
115     _description = "Account Type"
116     _columns = {
117         'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
118         'code': fields.char('Code', size=32, required=True),
119         'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of account types."),
120         'partner_account': fields.boolean('Partner account'),
121         'close_method': fields.selection([('none', 'None'), ('balance', 'Balance'), ('detail', 'Detail'), ('unreconciled', 'Unreconciled')], 'Deferral Method', required=True),
122         'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign on Reports', required=True, help='Allows you to change the sign of the balance amount displayed in the reports, so that you can see positive figures instead of negative ones in expenses accounts.'),
123     }
124     _defaults = {
125         'close_method': lambda *a: 'none',
126         'sequence': lambda *a: 5,
127         'sign': lambda *a: 1,
128     }
129     _order = "sequence"
130 account_account_type()
131
132 def _code_get(self, cr, uid, context={}):
133     acc_type_obj = self.pool.get('account.account.type')
134     ids = acc_type_obj.search(cr, uid, [])
135     res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context)
136     return [(r['code'], r['name']) for r in res]
137
138 #----------------------------------------------------------
139 # Accounts
140 #----------------------------------------------------------
141
142 class account_tax(osv.osv):
143     _name = 'account.tax'
144 account_tax()
145
146 class account_account(osv.osv):
147     _order = "parent_left"
148     _parent_order = "code"
149     _name = "account.account"
150     _description = "Account"
151     _parent_store = True
152
153     def search(self, cr, uid, args, offset=0, limit=None, order=None,
154             context=None, count=False):
155         if context is None:
156             context = {}
157         pos = 0
158
159         while pos < len(args):
160
161             if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
162                 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
163             if args[pos][0] == 'journal_id':
164                 if not args[pos][2]:
165                     del args[pos]
166                     continue
167                 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
168                 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
169                     args[pos] = ('type','not in',('consolidation','view'))
170                     continue
171                 ids3 = map(lambda x: x.id, jour.type_control_ids)
172                 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
173                 ids1 += map(lambda x: x.id, jour.account_control_ids)
174                 args[pos] = ('id', 'in', ids1)
175             pos += 1
176
177         if context and context.has_key('consolidate_childs'): #add consolidated childs of accounts
178             ids = super(account_account, self).search(cr, uid, args, offset, limit,
179                 order, context=context, count=count)
180             for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
181                 ids.append(consolidate_child.id)
182             return ids
183
184         return super(account_account, self).search(cr, uid, args, offset, limit,
185                 order, context=context, count=count)
186
187     def _get_children_and_consol(self, cr, uid, ids, context={}):
188         #this function search for all the children and all consolidated children (recursively) of the given account ids
189         ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
190         ids3 = []
191         for rec in self.browse(cr, uid, ids2, context=context):
192             for child in rec.child_consol_ids:
193                 ids3.append(child.id)
194         if ids3:
195             ids3 = self._get_children_and_consol(cr, uid, ids3, context)
196         return ids2 + ids3
197
198     def __compute(self, cr, uid, ids, field_names, arg, context={}, query=''):
199         #compute the balance/debit/credit accordingly to the value of field_name for the given account ids
200         mapping = {
201             'balance': "COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance ",
202             'debit': "COALESCE(SUM(l.debit), 0) as debit ",
203             'credit': "COALESCE(SUM(l.credit), 0) as credit "
204         }
205         #get all the necessary accounts
206         ids2 = self._get_children_and_consol(cr, uid, ids, context)
207         acc_set = ",".join(map(str, ids2))
208         #compute for each account the balance/debit/credit from the move lines
209         accounts = {}
210         if ids2:
211             aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
212
213             wheres = [""]
214             if query:
215                 wheres.append(query.strip())
216             if aml_query:
217                 wheres.append(aml_query.strip())
218             query = " AND ".join(wheres)
219
220             cr.execute(("SELECT l.account_id as id, " +\
221                     ' , '.join(map(lambda x: mapping[x], field_names)) +
222                     "FROM " \
223                         "account_move_line l " \
224                     "WHERE " \
225                         "l.account_id IN (%s) " \
226                         + query +
227                     " GROUP BY l.account_id") % (acc_set, ))
228
229             for res in cr.dictfetchall():
230                 accounts[res['id']] = res
231
232
233         # consolidate accounts with direct children
234         brs = list(self.browse(cr, uid, ids2, context=context))
235         sums = {}
236         while brs:
237             current = brs[0]
238             can_compute = True
239             for child in current.child_id:
240                 if child.id not in sums:
241                     can_compute = False
242                     try:
243                         brs.insert(0, brs.pop(brs.index(child)))
244                     except ValueError:
245                         brs.insert(0, child)
246             if can_compute:
247                 brs.pop(0)
248                 for fn in field_names:
249                     sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
250                     if current.child_id:
251                         sums[current.id][fn] += sum(sums[child.id][fn] for child in current.child_id)
252         res = {}
253         null_result = dict((fn, 0.0) for fn in field_names)
254         for id in ids:
255             res[id] = sums.get(id, null_result)
256         return res
257
258     def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
259         result = {}
260         for rec in self.browse(cr, uid, ids, context):
261             result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
262         return result
263
264     def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
265         result = {}
266         for record in self.browse(cr, uid, ids, context):
267             if record.child_parent_ids:
268                 result[record.id] = [x.id for x in record.child_parent_ids]
269             else:
270                 result[record.id] = []
271
272             if record.child_consol_ids:
273                 for acc in record.child_consol_ids:
274                     if acc.id not in result[record.id]:
275                         result[record.id].append(acc.id)
276
277         return result
278
279     _columns = {
280         'name': fields.char('Name', size=128, required=True, select=True),
281         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Force all moves for this account to have this secondary currency."),
282         'code': fields.char('Code', size=64, required=True),
283         'type': fields.selection([
284             ('receivable', 'Receivable'),
285             ('payable', 'Payable'),
286             ('view', 'View'),
287             ('consolidation', 'Consolidation'),
288             ('other', 'Others'),
289             ('closed', 'Closed'),
290         ], 'Internal Type', required=True,),
291
292         'user_type': fields.many2one('account.account.type', 'Account Type', required=True),
293         'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade'),
294         'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
295         'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
296         'child_id': fields.function(_get_child_ids, method=True, type='many2many', relation="account.account", string="Child Accounts"),
297         'balance': fields.function(__compute, digits=(16, int(config['price_accuracy'])), method=True, string='Balance', multi='balance'),
298         'credit': fields.function(__compute, digits=(16, int(config['price_accuracy'])), method=True, string='Credit', multi='balance'),
299         'debit': fields.function(__compute, digits=(16, int(config['price_accuracy'])), method=True, string='Debit', multi='balance'),
300         'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."),
301         'shortcut': fields.char('Shortcut', size=12),
302         'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
303             'account_id', 'tax_id', 'Default Taxes'),
304         'note': fields.text('Note'),
305         'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
306         'company_id': fields.many2one('res.company', 'Company', required=True),
307         'active': fields.boolean('Active', select=2),
308
309         'parent_left': fields.integer('Parent Left', select=1),
310         'parent_right': fields.integer('Parent Right', select=1),
311         'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
312             help=
313             'This will select how the current currency rate for outgoing transactions is computed. '\
314             'In most countries the legal method is "average" but only a few software systems are able to '\
315             'manage this. So if you import from another software system you may have to use the rate at date. ' \
316             'Incoming transactions always use the rate at date.', \
317             required=True),
318         'check_history': fields.boolean('Display History',
319             help="Check this box if you want to print all entries when printing the General Ledger, "\
320             "otherwise it will only print its balance."),
321     }
322
323     def _default_company(self, cr, uid, context={}):
324         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
325         if user.company_id:
326             return user.company_id.id
327         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
328
329     _defaults = {
330         'type': lambda *a : 'view',
331         'reconcile': lambda *a: False,
332         'company_id': _default_company,
333         'active': lambda *a: True,
334         'check_history': lambda *a: True,
335         'currency_mode': lambda *a: 'current'
336     }
337
338     def _check_recursion(self, cr, uid, ids):
339         obj_self = self.browse(cr, uid, ids[0])
340         p_id = obj_self.parent_id and obj_self.parent_id.id
341         if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
342             return False
343         while(ids):
344             cr.execute('select distinct child_id from account_account_consol_rel where parent_id in ('+','.join(map(str, ids))+')')
345             child_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
346             c_ids = child_ids
347             if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
348                 return False
349             while len(c_ids):
350                 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
351                 if p_id and (p_id in s_ids):
352                     return False
353                 c_ids = s_ids
354             ids = child_ids
355         return True
356
357     _constraints = [
358         (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
359     ]
360     _sql_constraints = [
361         ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
362     ]
363     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
364         if not args:
365             args = []
366         if not context:
367             context = {}
368         args = args[:]
369         ids = []
370         try:
371             if name and str(name).startswith('partner:'):
372                 part_id = int(name.split(':')[1])
373                 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
374                 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
375                 name = False
376             if name and str(name).startswith('type:'):
377                 type = name.split(':')[1]
378                 args += [('type', '=', type)]
379                 name = False
380         except:
381             pass
382         if name:
383             ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
384             if not ids:
385                 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
386             if not ids:
387                 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
388         else:
389             ids = self.search(cr, user, args, context=context, limit=limit)
390         return self.name_get(cr, user, ids, context=context)
391
392     def name_get(self, cr, uid, ids, context={}):
393         if not len(ids):
394             return []
395         reads = self.read(cr, uid, ids, ['name', 'code'], context)
396         res = []
397         for record in reads:
398             name = record['name']
399             if record['code']:
400                 name = record['code'] + ' '+name
401             res.append((record['id'], name))
402         return res
403
404     def copy(self, cr, uid, id, default={}, context={}, done_list=[], local=False):
405         account = self.browse(cr, uid, id, context=context)
406         new_child_ids = []
407         if not default:
408             default = {}
409         default = default.copy()
410         default['code'] = (account['code'] or '') + '(copy)'
411         if not local:
412             done_list = []
413         if account.id in done_list:
414             return False
415         done_list.append(account.id)
416         if account:
417             for child in account.child_id:
418                 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
419                 if child_ids:
420                     new_child_ids.append(child_ids)
421             default['child_parent_ids'] = [(6, 0, new_child_ids)]
422         else:
423             default['child_parent_ids'] = False
424         return super(account_account, self).copy(cr, uid, id, default, context=context)
425
426     def _check_moves(self, cr, uid, ids, method, context):
427         line_obj = self.pool.get('account.move.line')
428         account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
429         if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
430             if method == 'write':
431                 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
432             elif method == 'unlink':
433                 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
434         return True
435     
436     def write(self, cr, uid, ids, vals, context=None):
437         if not context:
438             context = {}
439         if 'active' in vals and not vals['active']:
440             self._check_moves(cr, uid, ids, "write", context)
441         return super(account_account, self).write(cr, uid, ids, vals, context=context)
442     
443     def unlink(self, cr, uid, ids, context={}):
444         self._check_moves(cr, uid, ids, "unlink", context)
445         return super(account_account, self).unlink(cr, uid, ids, context)
446     
447 account_account()
448
449 class account_journal_view(osv.osv):
450     _name = "account.journal.view"
451     _description = "Journal View"
452     _columns = {
453         'name': fields.char('Journal View', size=64, required=True),
454         'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
455     }
456     _order = "name"
457 account_journal_view()
458
459
460 class account_journal_column(osv.osv):
461     def _col_get(self, cr, user, context={}):
462         result = []
463         cols = self.pool.get('account.move.line')._columns
464         for col in cols:
465             result.append( (col, cols[col].string) )
466         result.sort()
467         return result
468     _name = "account.journal.column"
469     _description = "Journal Column"
470     _columns = {
471         'name': fields.char('Column Name', size=64, required=True),
472         'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
473         'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
474         'sequence': fields.integer('Sequence'),
475         'required': fields.boolean('Required'),
476         'readonly': fields.boolean('Readonly'),
477     }
478     _order = "sequence"
479 account_journal_column()
480
481 class account_journal(osv.osv):
482     _name = "account.journal"
483     _description = "Journal"
484     _columns = {
485         'name': fields.char('Journal Name', size=64, required=True, translate=True),
486         'code': fields.char('Code', size=16),
487         'type': fields.selection([('sale', 'Sale'), ('purchase', 'Purchase'), ('cash', 'Cash'), ('general', 'General'), ('situation', 'Situation')], 'Type', size=32, required=True),
488         'refund_journal': fields.boolean('Refund Journal'),
489
490         'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
491         'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
492
493         'active': fields.boolean('Active'),
494         'view_id': fields.many2one('account.journal.view', 'View', required=True, help="Gives the view used when writing or browsing entries in this journal. The view tell Open ERP 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."),
495         'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]"),
496         'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]"),
497         '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."),
498         'update_posted': fields.boolean('Allow Cancelling Entries'),
499         '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."),
500         'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals", required=True),
501         'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
502         'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
503         'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
504         'entry_posted': fields.boolean('Skip \'Draft\' State for Created Entries', help='Check this box if you don\'t want new account moves to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation.'),
505         'company_id': fields.related('default_credit_account_id','company_id',type='many2one', relation="res.company", string="Company"),
506         'invoice_sequence_id': fields.many2one('ir.sequence', 'Invoice Sequence', \
507             help="The sequence used for invoice numbers in this journal."),
508     }
509
510     _defaults = {
511         'active': lambda *a: 1,
512         'user_id': lambda self,cr,uid,context: uid,
513     }
514     def create(self, cr, uid, vals, context={}):
515         journal_id = super(account_journal, self).create(cr, uid, vals, context)
516 #       journal_name = self.browse(cr, uid, [journal_id])[0].code
517 #       periods = self.pool.get('account.period')
518 #       ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
519 #       for period in periods.browse(cr, uid, ids):
520 #           self.pool.get('account.journal.period').create(cr, uid, {
521 #               'name': (journal_name or '')+':'+(period.code or ''),
522 #               'journal_id': journal_id,
523 #               'period_id': period.id
524 #           })
525         return journal_id
526
527     def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
528         if not args:
529             args=[]
530         if not context:
531             context={}
532         ids = []
533         if name:
534             ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
535         if not ids:
536             ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
537         return self.name_get(cr, user, ids, context=context)
538 account_journal()
539
540 class account_fiscalyear(osv.osv):
541     _name = "account.fiscalyear"
542     _description = "Fiscal Year"
543     _columns = {
544         'name': fields.char('Fiscal Year', size=64, required=True),
545         'code': fields.char('Code', size=6, required=True),
546         'company_id': fields.many2one('res.company', 'Company',
547             help="Keep empty if the fiscal year belongs to several companies."),
548         'date_start': fields.date('Start Date', required=True),
549         'date_stop': fields.date('End Date', required=True),
550         'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
551         'state': fields.selection([('draft','Draft'), ('done','Done')], 'Status', readonly=True),
552     }
553
554     _defaults = {
555         'state': lambda *a: 'draft',
556     }
557     _order = "date_start"
558
559     def _check_duration(self,cr,uid,ids):
560         obj_fy=self.browse(cr,uid,ids[0])
561         if obj_fy.date_stop < obj_fy.date_start:
562             return False
563         return True
564
565     _constraints = [
566         (_check_duration, 'Error ! The duration of the Fiscal Year is invalid. ', ['date_stop'])
567     ]
568
569     def create_period3(self,cr, uid, ids, context={}):
570         return self.create_period(cr, uid, ids, context, 3)
571
572     def create_period(self,cr, uid, ids, context={}, interval=1):
573         for fy in self.browse(cr, uid, ids, context):
574             ds = mx.DateTime.strptime(fy.date_start, '%Y-%m-%d')
575             while ds.strftime('%Y-%m-%d')<fy.date_stop:
576                 de = ds + RelativeDateTime(months=interval, days=-1)
577
578                 if de.strftime('%Y-%m-%d')>fy.date_stop:
579                     de=mx.DateTime.strptime(fy.date_stop, '%Y-%m-%d')
580
581                 self.pool.get('account.period').create(cr, uid, {
582                     'name': ds.strftime('%m/%Y'),
583                     'code': ds.strftime('%m/%Y'),
584                     'date_start': ds.strftime('%Y-%m-%d'),
585                     'date_stop': de.strftime('%Y-%m-%d'),
586                     'fiscalyear_id': fy.id,
587                 })
588                 ds = ds + RelativeDateTime(months=interval)
589         return True
590
591     def find(self, cr, uid, dt=None, exception=True, context={}):
592         if not dt:
593             dt = time.strftime('%Y-%m-%d')
594         ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
595         if not ids:
596             if exception:
597                 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
598             else:
599                 return False
600         return ids[0]
601 account_fiscalyear()
602
603 class account_period(osv.osv):
604     _name = "account.period"
605     _description = "Account period"
606     _columns = {
607         'name': fields.char('Period Name', size=64, required=True),
608         'code': fields.char('Code', size=12),
609         'special': fields.boolean('Opening/Closing Period', size=12,
610             help="These periods can overlap."),
611         'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
612         'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
613         'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
614         'state': fields.selection([('draft','Draft'), ('done','Done')], 'Status', readonly=True)
615     }
616     _defaults = {
617         'state': lambda *a: 'draft',
618     }
619     _order = "date_start"
620
621     def _check_duration(self,cr,uid,ids,context={}):
622         obj_period=self.browse(cr,uid,ids[0])
623         if obj_period.date_stop < obj_period.date_start:
624             return False
625         return True
626
627     def _check_year_limit(self,cr,uid,ids,context={}):
628         for obj_period in self.browse(cr,uid,ids):
629             if obj_period.special:
630                 continue
631
632             if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
633                obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
634                obj_period.fiscalyear_id.date_start > obj_period.date_start or \
635                obj_period.fiscalyear_id.date_start > obj_period.date_stop:
636                 return False
637
638             pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
639             for period in self.browse(cr, uid, pids):
640                 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
641                     return False
642         return True
643
644     _constraints = [
645         (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
646         (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
647     ]
648
649     def next(self, cr, uid, period, step, context={}):
650         ids = self.search(cr, uid, [('date_start','>',period.date_start)])
651         if len(ids)>=step:
652             return ids[step-1]
653         return False
654
655     def find(self, cr, uid, dt=None, context={}):
656         if not dt:
657             dt = time.strftime('%Y-%m-%d')
658 #CHECKME: shouldn't we check the state of the period?
659         ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
660         if not ids:
661             raise osv.except_osv(_('Error !'), _('No period defined for this date !\nPlease create a fiscal year.'))
662         return ids
663
664     def action_draft(self, cr, uid, ids, *args):
665         users_roles = self.pool.get('res.users').browse(cr, uid, uid).roles_id
666         for role in users_roles:
667             if role.name=='Period':
668                 mode = 'draft'
669                 for id in ids:
670                     cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
671                     cr.execute('update account_period set state=%s where id=%s', (mode, id))
672         return True
673
674 account_period()
675
676 class account_journal_period(osv.osv):
677     _name = "account.journal.period"
678     _description = "Journal - Period"
679
680     def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
681         result = {}.fromkeys(ids, 'STOCK_NEW')
682         for r in self.read(cr, uid, ids, ['state']):
683             result[r['id']] = {
684                 'draft': 'STOCK_NEW',
685                 'printed': 'STOCK_PRINT_PREVIEW',
686                 'done': 'STOCK_DIALOG_AUTHENTICATION',
687             }.get(r['state'], 'STOCK_NEW')
688         return result
689
690     _columns = {
691         'name': fields.char('Journal-Period Name', size=64, required=True),
692         'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
693         'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
694         'icon': fields.function(_icon_get, method=True, string='Icon', type='char', size=32),
695         'active': fields.boolean('Active', required=True),
696         'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'Status', required=True, readonly=True),
697         'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
698     }
699
700     def _check(self, cr, uid, ids, context={}):
701         for obj in self.browse(cr, uid, ids, context):
702             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))
703             res = cr.fetchall()
704             if res:
705                 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
706         return True
707
708     def write(self, cr, uid, ids, vals, context={}):
709         self._check(cr, uid, ids, context)
710         return super(account_journal_period, self).write(cr, uid, ids, vals, context)
711
712     def create(self, cr, uid, vals, context={}):
713         period_id=vals.get('period_id',False)
714         if period_id:
715             period = self.pool.get('account.period').browse(cr, uid,period_id)
716             vals['state']=period.state
717         return super(account_journal_period, self).create(cr, uid, vals, context)
718
719     def unlink(self, cr, uid, ids, context={}):
720         self._check(cr, uid, ids, context)
721         return super(account_journal_period, self).unlink(cr, uid, ids, context)
722
723     _defaults = {
724         'state': lambda *a: 'draft',
725         'active': lambda *a: True,
726     }
727     _order = "period_id"
728
729 account_journal_period()
730
731 class account_fiscalyear(osv.osv):
732     _inherit = "account.fiscalyear"
733     _description = "Fiscal Year"
734     _columns = {
735         'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
736     }
737
738 account_fiscalyear()
739 #----------------------------------------------------------
740 # Entries
741 #----------------------------------------------------------
742 class account_move(osv.osv):
743     _name = "account.move"
744     _description = "Account Entry"
745     _order = 'id desc'
746
747     def name_get(self, cursor, user, ids, context=None):
748         if not len(ids):
749             return []
750         res=[]
751         data_move = self.pool.get('account.move').browse(cursor,user,ids)
752         for move in data_move:
753             if move.state=='draft':
754                 name = '*' + str(move.id)
755             else:
756                 name = move.name
757             res.append((move.id, name))
758         return res
759
760
761     def _get_period(self, cr, uid, context):
762         periods = self.pool.get('account.period').find(cr, uid)
763         if periods:
764             return periods[0]
765         else:
766             return False
767
768     def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
769         if not ids: return {}
770         cr.execute('select move_id,sum(debit) from account_move_line where move_id in ('+','.join(map(str,ids))+') group by move_id')
771         result = dict(cr.fetchall())
772         for id in ids:
773             result.setdefault(id, 0.0)
774         return result
775     
776     def _search_amount(self, cr, uid, obj, name, args, context):
777         ids = []
778         cr.execute('select move_id,sum(debit) from account_move_line group by move_id')
779         result = dict(cr.fetchall())
780
781         for item in args:
782             if item[1] == '>=':
783                 res = [('id', 'in', [k for k,v in result.iteritems() if v >= item[2]])]
784             else:
785                 res = [('id', 'in', [k for k,v in result.iteritems() if v <= item[2]])]    
786             ids += res
787
788         if not ids:    
789             return [('id', '>', '0')]
790
791         return ids
792
793     _columns = {
794         'name': fields.char('Number', size=64, required=True),
795         'ref': fields.char('Ref', size=64),
796         'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
797         'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
798         'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'Status', required=True, readonly=True),
799         'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
800         'to_check': fields.boolean('To Be Verified'),
801         'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner"),
802         'amount': fields.function(_amount_compute, method=True, string='Amount', digits=(16,int(config['price_accuracy'])), type='float', fnct_search=_search_amount),
803         'date': fields.date('Date', required=True),
804         'type': fields.selection([
805             ('pay_voucher','Cash Payment'),
806             ('bank_pay_voucher','Bank Payment'),
807             ('rec_voucher','Cash Receipt'),
808             ('bank_rec_voucher','Bank Receipt'),
809             ('cont_voucher','Contra'),
810             ('journal_sale_vou','Journal Sale'),
811             ('journal_pur_voucher','Journal Purchase'),
812             ('journal_voucher','Journal Voucher'),
813         ],'Type', readonly=True, select=True, states={'draft':[('readonly',False)]}),
814     }
815     _defaults = {
816         'name': lambda *a: '/',
817         'state': lambda *a: 'draft',
818         'period_id': _get_period,
819         'type' : lambda *a : 'journal_voucher',
820         'date': lambda *a:time.strftime('%Y-%m-%d'),
821     }
822
823     def _check_centralisation(self, cursor, user, ids):
824         for move in self.browse(cursor, user, ids):
825             if move.journal_id.centralisation:
826                 move_ids = self.search(cursor, user, [
827                     ('period_id', '=', move.period_id.id),
828                     ('journal_id', '=', move.journal_id.id),
829                     ])
830                 if len(move_ids) > 1:
831                     return False
832         return True
833
834     def _check_period_journal(self, cursor, user, ids):
835         for move in self.browse(cursor, user, ids):
836             for line in move.line_id:
837                 if line.period_id.id != move.period_id.id:
838                     return False
839                 if line.journal_id.id != move.journal_id.id:
840                     return False
841         return True
842
843     _constraints = [
844         (_check_centralisation,
845             'You cannot create more than one move per period on centralized journal',
846             ['journal_id']),
847         (_check_period_journal,
848             'You cannot create entries on different periods/journals in the same move',
849             ['line_id']),
850     ]
851     def post(self, cr, uid, ids, context=None):
852         if self.validate(cr, uid, ids, context) and len(ids):
853             for move in self.browse(cr, uid, ids):
854                 if move.name =='/':
855                     new_name = False
856                     journal = move.journal_id
857                     if journal.sequence_id:
858                         c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
859                         new_name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id, context=c)
860                     else:
861                         raise osv.except_osv(_('Error'), _('No sequence defined in the journal !'))
862                     if new_name:
863                         self.write(cr, uid, [move.id], {'name':new_name})
864
865             cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('posted',))
866         else:
867             raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non-balanced entry !'))
868         return True
869
870     def button_validate(self, cursor, user, ids, context=None):
871         return self.post(cursor, user, ids, context=context)
872
873     def button_cancel(self, cr, uid, ids, context={}):
874         for line in self.browse(cr, uid, ids, context):
875             if not line.journal_id.update_posted:
876                 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.'))
877         if len(ids):
878             cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('draft',))
879         return True
880
881     def write(self, cr, uid, ids, vals, context={}):
882         c = context.copy()
883         c['novalidate'] = True
884         result = super(osv.osv, self).write(cr, uid, ids, vals, c)
885         self.validate(cr, uid, ids, context)
886         return result
887
888     #
889     # TODO: Check if period is closed !
890     #
891     def create(self, cr, uid, vals, context={}):
892         if 'line_id' in vals:
893             if 'journal_id' in vals:
894                 for l in vals['line_id']:
895                     if not l[0]:
896                         l[2]['journal_id'] = vals['journal_id']
897                 context['journal_id'] = vals['journal_id']
898             if 'period_id' in vals:
899                 for l in vals['line_id']:
900                     if not l[0]:
901                         l[2]['period_id'] = vals['period_id']
902                 context['period_id'] = vals['period_id']
903             else:
904                 default_period = self._get_period(cr, uid, context)
905                 for l in vals['line_id']:
906                     if not l[0]:
907                         l[2]['period_id'] = default_period
908                 context['period_id'] = default_period
909
910         accnt_journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'])
911         if 'line_id' in vals:
912             c = context.copy()
913             c['novalidate'] = True
914             result = super(account_move, self).create(cr, uid, vals, c)
915             self.validate(cr, uid, [result], context)
916         else:
917             result = super(account_move, self).create(cr, uid, vals, context)
918         return result
919
920     def copy(self, cr, uid, id, default=None, context=None):
921         if default is None:
922             default = {}
923         default = default.copy()
924         default.update({'state':'draft', 'name':'/',})
925         return super(account_move, self).copy(cr, uid, id, default, context)
926
927     def unlink(self, cr, uid, ids, context={}, check=True):
928         toremove = []
929         for move in self.browse(cr, uid, ids, context):
930             if move['state'] != 'draft':
931                 raise osv.except_osv(_('UserError'),
932                         _('You can not delete posted movement: "%s"!') % \
933                                 move['name'])
934             line_ids = map(lambda x: x.id, move.line_id)
935             context['journal_id'] = move.journal_id.id
936             context['period_id'] = move.period_id.id
937             self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
938             self.pool.get('account.move.line').unlink(cr, uid, line_ids, context=context)
939             toremove.append(move.id)
940         result = super(account_move, self).unlink(cr, uid, toremove, context)
941         return result
942
943     def _compute_balance(self, cr, uid, id, context={}):
944         move = self.browse(cr, uid, [id])[0]
945         amount = 0
946         for line in move.line_id:
947             amount+= (line.debit - line.credit)
948         return amount
949
950     def _centralise(self, cr, uid, move, mode, context=None):
951         if context is None:
952             context = {}
953
954         if mode=='credit':
955             account_id = move.journal_id.default_debit_account_id.id
956             mode2 = 'debit'
957             if not account_id:
958                 raise osv.except_osv(_('UserError'),
959                         _('There is no default default debit account defined \n' \
960                                 'on journal "%s"') % move.journal_id.name)
961         else:
962             account_id = move.journal_id.default_credit_account_id.id
963             mode2 = 'credit'
964             if not account_id:
965                 raise osv.except_osv(_('UserError'),
966                         _('There is no default default credit account defined \n' \
967                                 'on journal "%s"') % move.journal_id.name)
968
969         # find the first line of this move with the current mode
970         # or create it if it doesn't exist
971         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
972         res = cr.fetchone()
973         if res:
974             line_id = res[0]
975         else:
976             context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
977             line_id = self.pool.get('account.move.line').create(cr, uid, {
978                 'name': _(mode.capitalize()+' Centralisation'),
979                 'centralisation': mode,
980                 'account_id': account_id,
981                 'move_id': move.id,
982                 'journal_id': move.journal_id.id,
983                 'period_id': move.period_id.id,
984                 'date': move.period_id.date_stop,
985                 'debit': 0.0,
986                 'credit': 0.0,
987             }, context)
988
989         # find the first line of this move with the other mode
990         # so that we can exclude it from our calculation
991         cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
992         res = cr.fetchone()
993         if res:
994             line_id2 = res[0]
995         else:
996             line_id2 = 0
997
998         cr.execute('select sum('+mode+') from account_move_line where move_id=%s and id<>%s', (move.id, line_id2))
999         result = cr.fetchone()[0] or 0.0
1000         cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1001         return True
1002
1003     #
1004     # Validate a balanced move. If it is a centralised journal, create a move.
1005     #
1006     def validate(self, cr, uid, ids, context={}):
1007         if context and ('__last_update' in context):
1008             del context['__last_update']
1009         ok = True
1010         for move in self.browse(cr, uid, ids, context):
1011             #unlink analytic lines on move_lines
1012             for obj_line in move.line_id:
1013                 for obj in obj_line.analytic_lines:
1014                     self.pool.get('account.analytic.line').unlink(cr,uid,obj.id)
1015
1016             journal = move.journal_id
1017             amount = 0
1018             line_ids = []
1019             line_draft_ids = []
1020             company_id=None
1021             for line in move.line_id:
1022                 amount += line.debit - line.credit
1023                 line_ids.append(line.id)
1024                 if line.state=='draft':
1025                     line_draft_ids.append(line.id)
1026
1027                 if not company_id:
1028                     company_id = line.account_id.company_id.id
1029                 if not company_id == line.account_id.company_id.id:
1030                     raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1031
1032                 if line.account_id.currency_id:
1033                     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 or line.currency_id):
1034                         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)))
1035
1036             if abs(amount) < 0.0001:
1037                 if not len(line_draft_ids):
1038                     continue
1039                 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1040                     'journal_id': move.journal_id.id,
1041                     'period_id': move.period_id.id,
1042                     'state': 'valid'
1043                 }, context, check=False)
1044                 todo = []
1045                 account = {}
1046                 account2 = {}
1047                 if journal.type not in ('purchase','sale'):
1048                     continue
1049
1050                 for line in move.line_id:
1051                     code = amount = 0
1052                     key = (line.account_id.id, line.tax_code_id.id)
1053                     if key in account2:
1054                         code = account2[key][0]
1055                         amount = account2[key][1] * (line.debit + line.credit)
1056                     elif line.account_id.id in account:
1057                         code = account[line.account_id.id][0]
1058                         amount = account[line.account_id.id][1] * (line.debit + line.credit)
1059                     if (code or amount) and not (line.tax_code_id or line.tax_amount):
1060                         self.pool.get('account.move.line').write(cr, uid, [line.id], {
1061                             'tax_code_id': code,
1062                             'tax_amount': amount
1063                         }, context, check=False)
1064                 #
1065                 # Compute VAT
1066                 #
1067                 continue
1068             if journal.centralisation:
1069                 self._centralise(cr, uid, move, 'debit', context=context)
1070                 self._centralise(cr, uid, move, 'credit', context=context)
1071                 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1072                     'state': 'valid'
1073                 }, context, check=False)
1074                 continue
1075             else:
1076                 self.pool.get('account.move.line').write(cr, uid, line_ids, {
1077                     'journal_id': move.journal_id.id,
1078                     'period_id': move.period_id.id,
1079                     #'tax_code_id': False,
1080                     #'tax_amount': False,
1081                     'state': 'draft'
1082                 }, context, check=False)
1083                 ok = False
1084         if ok:
1085             list_ids = []
1086             for tmp in move.line_id:
1087                 list_ids.append(tmp.id)
1088             self.pool.get('account.move.line').create_analytic_lines(cr, uid, list_ids, context)
1089         return ok
1090 account_move()
1091
1092 class account_move_reconcile(osv.osv):
1093     _name = "account.move.reconcile"
1094     _description = "Account Reconciliation"
1095     _columns = {
1096         'name': fields.char('Name', size=64, required=True),
1097         'type': fields.char('Type', size=16, required=True),
1098         'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1099         'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1100         'create_date': fields.date('Creation date', readonly=True),
1101     }
1102     _defaults = {
1103         'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1104     }
1105     def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1106         total = 0.0 
1107         for rec in self.browse(cr, uid, ids, context):
1108             for line in rec.line_partial_ids:
1109                 total += (line.debit or 0.0) - (line.credit or 0.0)
1110         if not total:
1111             self.pool.get('account.move.line').write(cr, uid,
1112                 map(lambda x: x.id, rec.line_partial_ids),
1113                 {'reconcile_id': rec.id }
1114             )
1115         return True
1116
1117     def name_get(self, cr, uid, ids, context=None):
1118         if not len(ids):
1119             return []
1120         result = []
1121         for r in self.browse(cr, uid, ids, context):
1122             total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1123             if total:
1124                 name = '%s (%.2f)' % (r.name, total)
1125                 result.append((r.id,name))
1126             else:
1127                 result.append((r.id,r.name))
1128         return result
1129
1130
1131 account_move_reconcile()
1132
1133 #----------------------------------------------------------
1134 # Tax
1135 #----------------------------------------------------------
1136 """
1137 a documenter
1138 child_depend: la taxe depend des taxes filles
1139 """
1140 class account_tax_code(osv.osv):
1141     """
1142     A code for the tax object.
1143
1144     This code is used for some tax declarations.
1145     """
1146     def _sum(self, cr, uid, ids, name, args, context, where =''):
1147         ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
1148         acc_set = ",".join(map(str, ids2))
1149         if context.get('based_on', 'invoices') == 'payments':
1150             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1151                     FROM account_move_line AS line, \
1152                         account_move AS move \
1153                         LEFT JOIN account_invoice invoice ON \
1154                             (invoice.move_id = move.id) \
1155                     WHERE line.tax_code_id in ('+acc_set+') '+where+' \
1156                         AND move.id = line.move_id \
1157                         AND ((invoice.state = \'paid\') \
1158                             OR (invoice.id IS NULL)) \
1159                     GROUP BY line.tax_code_id')
1160         else:
1161             cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1162                     FROM account_move_line AS line \
1163                     WHERE line.tax_code_id in ('+acc_set+') '+where+' \
1164                     GROUP BY line.tax_code_id')
1165         res=dict(cr.fetchall())
1166         for record in self.browse(cr, uid, ids, context):
1167             def _rec_get(record):
1168                 amount = res.get(record.id, 0.0)
1169                 for rec in record.child_ids:
1170                     amount += _rec_get(rec) * rec.sign
1171                 return amount
1172             res[record.id] = round(_rec_get(record), int(config['price_accuracy']))
1173         return res
1174
1175     def _sum_year(self, cr, uid, ids, name, args, context):
1176         if 'fiscalyear_id' in context and context['fiscalyear_id']:
1177             fiscalyear_id = context['fiscalyear_id']
1178         else:
1179             fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1180         where = ''
1181         if fiscalyear_id:
1182             pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1183             if pids:
1184                 where = ' and period_id in (' + (','.join(pids))+')'
1185         return self._sum(cr, uid, ids, name, args, context,
1186                 where=where)
1187
1188     def _sum_period(self, cr, uid, ids, name, args, context):
1189         if 'period_id' in context and context['period_id']:
1190             period_id = context['period_id']
1191         else:
1192             period_id = self.pool.get('account.period').find(cr, uid)
1193             if not len(period_id):
1194                 return dict.fromkeys(ids, 0.0)
1195             period_id = period_id[0]
1196         return self._sum(cr, uid, ids, name, args, context,
1197                 where=' and line.period_id='+str(period_id))
1198
1199     _name = 'account.tax.code'
1200     _description = 'Tax Code'
1201     _rec_name = 'code'
1202     _columns = {
1203         'name': fields.char('Tax Case Name', size=64, required=True),
1204         'code': fields.char('Case Code', size=64),
1205         'info': fields.text('Description'),
1206         'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1207         'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1208         'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1209         'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1210         'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1211         'company_id': fields.many2one('res.company', 'Company', required=True),
1212         'sign': fields.float('Sign for parent', required=True),
1213         '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"),
1214     }
1215
1216
1217     def name_get(self, cr, uid, ids, context=None):
1218         if not len(ids):
1219             return []
1220         if isinstance(ids, (int, long)):
1221             ids = [ids]
1222         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1223         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1224                 for x in reads]
1225
1226     def _default_company(self, cr, uid, context={}):
1227         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1228         if user.company_id:
1229             return user.company_id.id
1230         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1231     _defaults = {
1232         'company_id': _default_company,
1233         'sign': lambda *args: 1.0,
1234         'notprintable': lambda *a: False,
1235     }
1236     def _check_recursion(self, cr, uid, ids):
1237         level = 100
1238         while len(ids):
1239             cr.execute('select distinct parent_id from account_tax_code where id in ('+','.join(map(str,ids))+')')
1240             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1241             if not level:
1242                 return False
1243             level -= 1
1244         return True
1245
1246     _constraints = [
1247         (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1248     ]
1249     _order = 'code,name'
1250 account_tax_code()
1251
1252 class account_tax(osv.osv):
1253     """
1254     A tax object.
1255
1256     Type: percent, fixed, none, code
1257         PERCENT: tax = price * amount
1258         FIXED: tax = price + amount
1259         NONE: no tax line
1260         CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1261             return result in the context
1262             Ex: result=round(price_unit*0.21,4)
1263     """
1264     _name = 'account.tax'
1265     _description = 'Tax'
1266     _columns = {
1267         'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1268         '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."),
1269         'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
1270         'active': fields.boolean('Active'),
1271         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'),('balance','Balance')], 'Tax Type', required=True,
1272             help="The computation method for the tax amount."),
1273         'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True,
1274             help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1275         '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."),
1276         'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1277         'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1278         'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1279         'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1280         '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."),
1281         'python_compute':fields.text('Python Code'),
1282         'python_compute_inv':fields.text('Python Code (reverse)'),
1283         'python_applicable':fields.text('Python Code'),
1284         'tax_group': fields.selection([('vat','VAT'),('other','Other')], 'Tax Group', help="If a default tax is given in the partner it only overrides taxes from accounts (or products) in the same group."),
1285
1286         #
1287         # Fields used for the VAT declaration
1288         #
1289         'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1290         'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1291         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1292         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1293
1294         # Same fields for refund invoices
1295
1296         'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1297         'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1298         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1299         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1300         'include_base_amount': fields.boolean('Include in base amount', help="Indicate if the amount of tax must be included in the base amount for the computation of the next taxes"),
1301         'company_id': fields.many2one('res.company', 'Company', required=True),
1302         'description': fields.char('Tax Code',size=32),
1303         'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1304         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1305
1306     }
1307     def search(self, cr, uid, args, offset=0, limit=None, order=None,
1308             context=None, count=False):
1309         if context and context.has_key('type'):
1310             if context['type'] in ('out_invoice','out_refund'):
1311                 args.append(('type_tax_use','in',['sale','all']))
1312             elif context['type'] in ('in_invoice','in_refund'):
1313                 args.append(('type_tax_use','in',['purchase','all']))
1314         return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1315
1316     def name_get(self, cr, uid, ids, context={}):
1317         if not len(ids):
1318             return []
1319         res = []
1320         for record in self.read(cr, uid, ids, ['description','name'], context):
1321             name = record['description'] and record['description'] or record['name']
1322             res.append((record['id'],name ))
1323         return res
1324
1325     def _default_company(self, cr, uid, context={}):
1326         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1327         if user.company_id:
1328             return user.company_id.id
1329         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1330     _defaults = {
1331         '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''',
1332         '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''',
1333         'applicable_type': lambda *a: 'true',
1334         'type': lambda *a: 'percent',
1335         'amount': lambda *a: 0,
1336         'price_include': lambda *a: 0,
1337         'active': lambda *a: 1,
1338         'type_tax_use': lambda *a: 'all',
1339         'sequence': lambda *a: 1,
1340         'tax_group': lambda *a: 'vat',
1341         'ref_tax_sign': lambda *a: 1,
1342         'ref_base_sign': lambda *a: 1,
1343         'tax_sign': lambda *a: 1,
1344         'base_sign': lambda *a: 1,
1345         'include_base_amount': lambda *a: False,
1346         'company_id': _default_company,
1347     }
1348     _order = 'sequence'
1349
1350     def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1351         res = []
1352         for tax in taxes:
1353             if tax.applicable_type=='code':
1354                 localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id), 'product':product, 'partner':partner}
1355                 exec tax.python_applicable in localdict
1356                 if localdict.get('result', False):
1357                     res.append(tax)
1358             else:
1359                 res.append(tax)
1360         return res
1361
1362     def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1363         taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1364
1365         res = []
1366         cur_price_unit=price_unit
1367         for tax in taxes:
1368             # we compute the amount for the current tax object and append it to the result
1369
1370             data = {'id':tax.id,
1371                             'name':tax.description and tax.description + " - " + tax.name or tax.name,
1372                             'account_collected_id':tax.account_collected_id.id,
1373                             'account_paid_id':tax.account_paid_id.id,
1374                             'base_code_id': tax.base_code_id.id,
1375                             'ref_base_code_id': tax.ref_base_code_id.id,
1376                             'sequence': tax.sequence,
1377                             'base_sign': tax.base_sign,
1378                             'tax_sign': tax.tax_sign,
1379                             'ref_base_sign': tax.ref_base_sign,
1380                             'ref_tax_sign': tax.ref_tax_sign,
1381                             'price_unit': cur_price_unit,
1382                             'tax_code_id': tax.tax_code_id.id,
1383                             'ref_tax_code_id': tax.ref_tax_code_id.id,
1384             }
1385             res.append(data)
1386             if tax.type=='percent':
1387                 amount = cur_price_unit * tax.amount
1388                 data['amount'] = amount
1389
1390             elif tax.type=='fixed':
1391                 data['amount'] = tax.amount
1392                 data['tax_amount']=quantity
1393                # data['amount'] = quantity
1394             elif tax.type=='code':
1395                 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1396                 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1397                 exec tax.python_compute in localdict
1398                 amount = localdict['result']
1399                 data['amount'] = amount
1400             elif tax.type=='balance':
1401                 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1402                 data['balance'] = cur_price_unit
1403
1404             amount2 = data['amount']
1405             if len(tax.child_ids):
1406                 if tax.child_depend:
1407                     latest = res.pop()
1408                 amount = amount2
1409                 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1410                 res.extend(child_tax)
1411                 if tax.child_depend:
1412                     for r in res:
1413                         for name in ('base','ref_base'):
1414                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1415                                 r[name+'_code_id'] = latest[name+'_code_id']
1416                                 r[name+'_sign'] = latest[name+'_sign']
1417                                 r['price_unit'] = latest['price_unit']
1418                                 latest[name+'_code_id'] = False
1419                         for name in ('tax','ref_tax'):
1420                             if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1421                                 r[name+'_code_id'] = latest[name+'_code_id']
1422                                 r[name+'_sign'] = latest[name+'_sign']
1423                                 r['amount'] = data['amount']
1424                                 latest[name+'_code_id'] = False
1425             if tax.include_base_amount:
1426                 cur_price_unit+=amount2
1427         return res
1428
1429     def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1430
1431         """
1432         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1433
1434         RETURN:
1435             [ tax ]
1436             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1437             one tax for each tax id in IDS and their childs
1438         """
1439         res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1440         total = 0.0
1441         for r in res:
1442             if r.get('balance',False):
1443                 r['amount'] = round(r['balance'] * quantity, int(config['price_accuracy'])) - total
1444             else:
1445                 r['amount'] = round(r['amount'] * quantity, int(config['price_accuracy']))
1446                 total += r['amount']
1447
1448         return res
1449
1450     def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1451         taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1452
1453         res = []
1454         taxes.reverse()
1455         cur_price_unit = price_unit
1456
1457         tax_parent_tot = 0.0
1458         for tax in taxes:
1459             if (tax.type=='percent') and not tax.include_base_amount:
1460                 tax_parent_tot += tax.amount
1461                 
1462         for tax in taxes:
1463             if (tax.type=='fixed') and not tax.include_base_amount:
1464                 cur_price_unit -= tax.amount
1465                 
1466         for tax in taxes:
1467             if tax.type=='percent':
1468                 if tax.include_base_amount:
1469                     amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1470                 else:
1471                     amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1472
1473             elif tax.type=='fixed':
1474                 amount = tax.amount
1475
1476             elif tax.type=='code':
1477                 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1478                 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1479                 exec tax.python_compute_inv in localdict
1480                 amount = localdict['result']
1481             elif tax.type=='balance':
1482                 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1483 #                data['balance'] = cur_price_unit
1484
1485
1486             if tax.include_base_amount:
1487                 cur_price_unit -= amount
1488                 todo = 0
1489             else:
1490                 todo = 1
1491             res.append({
1492                 'id': tax.id,
1493                 'todo': todo,
1494                 'name': tax.name,
1495                 'amount': amount,
1496                 'account_collected_id': tax.account_collected_id.id,
1497                 'account_paid_id': tax.account_paid_id.id,
1498                 'base_code_id': tax.base_code_id.id,
1499                 'ref_base_code_id': tax.ref_base_code_id.id,
1500                 'sequence': tax.sequence,
1501                 'base_sign': tax.base_sign,
1502                 'tax_sign': tax.tax_sign,
1503                 'ref_base_sign': tax.ref_base_sign,
1504                 'ref_tax_sign': tax.ref_tax_sign,
1505                 'price_unit': cur_price_unit,
1506                 'tax_code_id': tax.tax_code_id.id,
1507                 'ref_tax_code_id': tax.ref_tax_code_id.id,
1508             })
1509             if len(tax.child_ids):
1510                 if tax.child_depend:
1511                     del res[-1]
1512                     amount = price_unit
1513
1514             parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1515             res.extend(parent_tax)
1516
1517         total = 0.0
1518         for r in res:
1519             if r['todo']:
1520                 total += r['amount']
1521         for r in res:
1522             r['price_unit'] -= total
1523             r['todo'] = 0
1524         return res
1525
1526     def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1527         """
1528         Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1529         Price Unit is a VAT included price
1530
1531         RETURN:
1532             [ tax ]
1533             tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1534             one tax for each tax id in IDS and their childs
1535         """
1536         res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1537         total = 0.0
1538         for r in res:
1539             if r.get('balance',False):
1540                 r['amount'] = round(r['balance'] * quantity, int(config['price_accuracy'])) - total
1541             else:
1542                 r['amount'] = round(r['amount'] * quantity, int(config['price_accuracy']))
1543                 total += r['amount']
1544         return res
1545 account_tax()
1546
1547 # ---------------------------------------------------------
1548 # Account Entries Models
1549 # ---------------------------------------------------------
1550
1551 class account_model(osv.osv):
1552     _name = "account.model"
1553     _description = "Account Model"
1554     _columns = {
1555         'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
1556         'ref': fields.char('Ref', size=64),
1557         'journal_id': fields.many2one('account.journal', 'Journal', required=True),
1558         'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
1559         'legend' :fields.text('Legend',readonly=True,size=100),
1560     }
1561
1562     _defaults = {
1563         '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'),
1564     }
1565     def generate(self, cr, uid, ids, datas={}, context={}):
1566         move_ids = []
1567         for model in self.browse(cr, uid, ids, context):
1568             context.update({'date':datas['date']})
1569             period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date',False))
1570             if not period_id:
1571                 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
1572             period_id = period_id[0]
1573             move_id = self.pool.get('account.move').create(cr, uid, {
1574                 'ref': model.ref,
1575                 'period_id': period_id,
1576                 'journal_id': model.journal_id.id,
1577                 'date': context.get('date',time.strftime('%Y-%m-%d'))
1578             })
1579             move_ids.append(move_id)
1580             for line in model.lines_id:
1581                 val = {
1582                     'move_id': move_id,
1583                     'journal_id': model.journal_id.id,
1584                     'period_id': period_id
1585                 }
1586                 val.update({
1587                     'name': line.name,
1588                     'quantity': line.quantity,
1589                     'debit': line.debit,
1590                     'credit': line.credit,
1591                     'account_id': line.account_id.id,
1592                     'move_id': move_id,
1593                     'ref': line.ref,
1594                     'partner_id': line.partner_id.id,
1595                     'date': context.get('date',time.strftime('%Y-%m-%d')),
1596                     'date_maturity': time.strftime('%Y-%m-%d')
1597                 })
1598                 c = context.copy()
1599                 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
1600                 self.pool.get('account.move.line').create(cr, uid, val, context=c)
1601         return move_ids
1602 account_model()
1603
1604 class account_model_line(osv.osv):
1605     _name = "account.model.line"
1606     _description = "Account Model Entries"
1607     _columns = {
1608         'name': fields.char('Name', size=64, required=True),
1609         'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
1610         'quantity': fields.float('Quantity', digits=(16, int(config['price_accuracy'])), help="The optional quantity on entries"),
1611         'debit': fields.float('Debit', digits=(16, int(config['price_accuracy']))),
1612         'credit': fields.float('Credit', digits=(16, int(config['price_accuracy']))),
1613
1614         'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
1615
1616         'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
1617
1618         'ref': fields.char('Ref.', size=16),
1619
1620         'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
1621         'currency_id': fields.many2one('res.currency', 'Currency'),
1622
1623         'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
1624         '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 chosse between the date of the creation action or the the date of the creation of the entries plus the partner payment terms."),
1625         'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
1626     }
1627     _defaults = {
1628         'date': lambda *a: 'today'
1629     }
1630     _order = 'sequence'
1631     _sql_constraints = [
1632         ('credit_debit1', 'CHECK (credit*debit=0)',  'Wrong credit or debit value in model !'),
1633         ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model !'),
1634     ]
1635 account_model_line()
1636
1637 # ---------------------------------------------------------
1638 # Account Subscription
1639 # ---------------------------------------------------------
1640
1641
1642 class account_subscription(osv.osv):
1643     _name = "account.subscription"
1644     _description = "Account Subscription"
1645     _columns = {
1646         'name': fields.char('Name', size=64, required=True),
1647         'ref': fields.char('Ref', size=16),
1648         'model_id': fields.many2one('account.model', 'Model', required=True),
1649
1650         'date_start': fields.date('Start Date', required=True),
1651         'period_total': fields.integer('Number of Periods', required=True),
1652         'period_nbr': fields.integer('Period', required=True),
1653         'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
1654         'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', required=True, readonly=True),
1655
1656         'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
1657     }
1658     _defaults = {
1659         'date_start': lambda *a: time.strftime('%Y-%m-%d'),
1660         'period_type': lambda *a: 'month',
1661         'period_total': lambda *a: 12,
1662         'period_nbr': lambda *a: 1,
1663         'state': lambda *a: 'draft',
1664     }
1665     def state_draft(self, cr, uid, ids, context={}):
1666         self.write(cr, uid, ids, {'state':'draft'})
1667         return False
1668
1669     def check(self, cr, uid, ids, context={}):
1670         todone = []
1671         for sub in self.browse(cr, uid, ids, context):
1672             ok = True
1673             for line in sub.lines_id:
1674                 if not line.move_id.id:
1675                     ok = False
1676                     break
1677             if ok:
1678                 todone.append(sub.id)
1679         if len(todone):
1680             self.write(cr, uid, todone, {'state':'done'})
1681         return False
1682
1683     def remove_line(self, cr, uid, ids, context={}):
1684         toremove = []
1685         for sub in self.browse(cr, uid, ids, context):
1686             for line in sub.lines_id:
1687                 if not line.move_id.id:
1688                     toremove.append(line.id)
1689         if len(toremove):
1690             self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
1691         self.write(cr, uid, ids, {'state':'draft'})
1692         return False
1693
1694     def compute(self, cr, uid, ids, context={}):
1695         for sub in self.browse(cr, uid, ids, context):
1696             ds = sub.date_start
1697             for i in range(sub.period_total):
1698                 self.pool.get('account.subscription.line').create(cr, uid, {
1699                     'date': ds,
1700                     'subscription_id': sub.id,
1701                 })
1702                 if sub.period_type=='day':
1703                     ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(days=sub.period_nbr)).strftime('%Y-%m-%d')
1704                 if sub.period_type=='month':
1705                     ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(months=sub.period_nbr)).strftime('%Y-%m-%d')
1706                 if sub.period_type=='year':
1707                     ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(years=sub.period_nbr)).strftime('%Y-%m-%d')
1708         self.write(cr, uid, ids, {'state':'running'})
1709         return True
1710 account_subscription()
1711
1712 class account_subscription_line(osv.osv):
1713     _name = "account.subscription.line"
1714     _description = "Account Subscription Line"
1715     _columns = {
1716         'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
1717         'date': fields.date('Date', required=True),
1718         'move_id': fields.many2one('account.move', 'Entry'),
1719     }
1720     _defaults = {
1721     }
1722     def move_create(self, cr, uid, ids, context={}):
1723         tocheck = {}
1724         for line in self.browse(cr, uid, ids, context):
1725             datas = {
1726                 'date': line.date,
1727             }
1728             ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
1729             tocheck[line.subscription_id.id] = True
1730             self.write(cr, uid, [line.id], {'move_id':ids[0]})
1731         if tocheck:
1732             self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
1733         return True
1734     _rec_name = 'date'
1735 account_subscription_line()
1736
1737
1738 class account_config_wizard(osv.osv_memory):
1739     _name = 'account.config.wizard'
1740
1741     def _get_charts(self, cr, uid, context):
1742         module_obj=self.pool.get('ir.module.module')
1743         ids=module_obj.search(cr, uid, [('category_id', '=', 'Account Charts'), ('state', '<>', 'installed')])
1744         res=[(m.id, m.shortdesc) for m in module_obj.browse(cr, uid, ids)]
1745         res.append((-1, 'None'))
1746         res.sort(key=lambda x: x[1])
1747         return res
1748
1749     _columns = {
1750         'name':fields.char('Name', required=True, size=64, help="Name of the fiscal year as displayed on screens."),
1751         'code':fields.char('Code', required=True, size=64, help="Name of the fiscal year as displayed in reports."),
1752         'date1': fields.date('Start Date', required=True),
1753         'date2': fields.date('End Date', required=True),
1754         'period':fields.selection([('month','Month'),('3months','3 Months')], 'Periods', required=True),
1755         'charts' : fields.selection(_get_charts, 'Charts of Account',required=True)
1756     }
1757     _defaults = {
1758         'code': lambda *a: time.strftime('%Y'),
1759         'name': lambda *a: time.strftime('%Y'),
1760         'date1': lambda *a: time.strftime('%Y-01-01'),
1761         'date2': lambda *a: time.strftime('%Y-12-31'),
1762         'period':lambda *a:'month',
1763     }
1764     def action_cancel(self,cr,uid,ids,conect=None):
1765         return {
1766                 'view_type': 'form',
1767                 "view_mode": 'form',
1768                 'res_model': 'ir.actions.configuration.wizard',
1769                 'type': 'ir.actions.act_window',
1770                 'target':'new',
1771         }
1772
1773     def install_account_chart(self, cr, uid, ids, context=None):
1774         for res in self.read(cr,uid,ids):
1775             chart_id = res['charts']
1776             if chart_id > 0:
1777                 mod_obj = self.pool.get('ir.module.module')
1778                 mod_obj.button_install(cr, uid, [chart_id], context=context)
1779         cr.commit()
1780         db, pool = pooler.restart_pool(cr.dbname, update_module=True)
1781
1782     def action_create(self, cr, uid,ids, context=None):
1783         for res in self.read(cr,uid,ids):
1784             if 'date1' in res and 'date2' in res:
1785                 res_obj = self.pool.get('account.fiscalyear')
1786                 start_date=res['date1']
1787                 end_date=res['date2']
1788                 name=res['name']#DateTime.strptime(start_date, '%Y-%m-%d').strftime('%m.%Y') + '-' + DateTime.strptime(end_date, '%Y-%m-%d').strftime('%m.%Y')
1789                 vals={
1790                     'name':name,
1791                     'code':name,
1792                     'date_start':start_date,
1793                     'date_stop':end_date,
1794                 }
1795                 new_id=res_obj.create(cr, uid, vals, context=context)
1796                 if res['period']=='month':
1797                     res_obj.create_period(cr,uid,[new_id])
1798                 elif res['period']=='3months':
1799                     res_obj.create_period3(cr,uid,[new_id])
1800         self.install_account_chart(cr,uid,ids)
1801         return {
1802                 'view_type': 'form',
1803                 "view_mode": 'form',
1804                 'res_model': 'ir.actions.configuration.wizard',
1805                 'type': 'ir.actions.act_window',
1806                 'target':'new',
1807         }
1808
1809
1810
1811 account_config_wizard()
1812
1813
1814 #  ---------------------------------------------------------------
1815 #   Account Templates : Account, Tax, Tax Code and chart. + Wizard
1816 #  ---------------------------------------------------------------
1817
1818 class account_tax_template(osv.osv):
1819     _name = 'account.tax.template'
1820 account_tax_template()
1821
1822 class account_account_template(osv.osv):
1823     _order = "code"
1824     _name = "account.account.template"
1825     _description ='Templates for Accounts'
1826
1827     _columns = {
1828         'name': fields.char('Name', size=128, required=True, select=True),
1829         'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Force all moves for this account to have this secondary currency."),
1830         'code': fields.char('Code', size=64),
1831         'type': fields.selection([
1832             ('receivable','Receivable'),
1833             ('payable','Payable'),
1834             ('view','View'),
1835             ('consolidation','Consolidation'),
1836             ('other','Others'),
1837             ('closed','Closed'),
1838             ], 'Internal Type', required=True,help="This type is used to differenciate types with "\
1839             "special effects in Open ERP: view can not have entries, consolidation are accounts that "\
1840             "can have children accounts for multi-company consolidations, payable/receivable are for "\
1841             "partners accounts (for debit/credit computations), closed for deprecated accounts."),
1842         'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
1843             help="These types are defined according to your country. The type contain more information "\
1844             "about the account and it's specificities."),
1845         'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
1846         'shortcut': fields.char('Shortcut', size=12),
1847         'note': fields.text('Note'),
1848         'parent_id': fields.many2one('account.account.template','Parent Account Template', ondelete='cascade'),
1849         'child_parent_ids':fields.one2many('account.account.template','parent_id','Children'),
1850         'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel','account_id','tax_id', 'Default Taxes'),
1851     }
1852
1853     _defaults = {
1854         'reconcile': lambda *a: False,
1855         'type' : lambda *a :'view',
1856     }
1857
1858     def _check_recursion(self, cr, uid, ids):
1859         level = 100
1860         while len(ids):
1861             cr.execute('select parent_id from account_account_template where id in ('+','.join(map(str,ids))+')')
1862             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1863             if not level:
1864                 return False
1865             level -= 1
1866         return True
1867
1868     _constraints = [
1869         (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
1870     ]
1871
1872
1873     def name_get(self, cr, uid, ids, context={}):
1874         if not len(ids):
1875             return []
1876         reads = self.read(cr, uid, ids, ['name','code'], context)
1877         res = []
1878         for record in reads:
1879             name = record['name']
1880             if record['code']:
1881                 name = record['code']+' '+name
1882             res.append((record['id'],name ))
1883         return res
1884
1885 account_account_template()
1886
1887 class account_tax_code_template(osv.osv):
1888
1889     _name = 'account.tax.code.template'
1890     _description = 'Tax Code Template'
1891     _order = 'code'
1892     _rec_name = 'code'
1893     _columns = {
1894         'name': fields.char('Tax Case Name', size=64, required=True),
1895         'code': fields.char('Case Code', size=64),
1896         'info': fields.text('Description'),
1897         'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
1898         'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
1899         'sign': fields.float('Sign for parent', required=True),
1900         '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"),
1901     }
1902
1903     _defaults = {
1904         'sign': lambda *args: 1.0,
1905         'notprintable': lambda *a: False,
1906     }
1907
1908     def name_get(self, cr, uid, ids, context=None):
1909         if not len(ids):
1910             return []
1911         if isinstance(ids, (int, long)):
1912             ids = [ids]
1913         reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1914         return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1915                 for x in reads]
1916
1917     def _check_recursion(self, cr, uid, ids):
1918         level = 100
1919         while len(ids):
1920             cr.execute('select distinct parent_id from account_tax_code_template where id in ('+','.join(map(str,ids))+')')
1921             ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1922             if not level:
1923                 return False
1924             level -= 1
1925         return True
1926
1927     _constraints = [
1928         (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
1929     ]
1930     _order = 'code,name'
1931 account_tax_code_template()
1932
1933
1934 class account_chart_template(osv.osv):
1935     _name="account.chart.template"
1936     _description= "Templates for Account Chart"
1937
1938     _columns={
1939         'name': fields.char('Name', size=64, required=True),
1940         'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
1941         'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
1942         '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'),
1943         'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
1944         'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
1945         'property_account_payable': fields.many2one('account.account.template','Payable Account'),
1946         'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
1947         'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
1948         'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
1949         'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
1950     }
1951
1952 account_chart_template()
1953
1954 class account_tax_template(osv.osv):
1955
1956     _name = 'account.tax.template'
1957     _description = 'Templates for Taxes'
1958
1959     _columns = {
1960         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
1961         'name': fields.char('Tax Name', size=64, required=True),
1962         '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."),
1963         'amount': fields.float('Amount', required=True, digits=(14,4)),
1964         'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
1965         'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True),
1966         '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."),
1967         'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
1968         'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
1969         'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
1970         'child_depend':fields.boolean('Tax on Children', help="Indicate if the tax computation is based on the value computed for the computation of child taxes or based on the total amount."),
1971         'python_compute':fields.text('Python Code'),
1972         'python_compute_inv':fields.text('Python Code (reverse)'),
1973         'python_applicable':fields.text('Python Code'),
1974         'tax_group': fields.selection([('vat','VAT'),('other','Other')], 'Tax Group', help="If a default tax if given in the partner it only override taxes from account (or product) of the same group."),
1975
1976         #
1977         # Fields used for the VAT declaration
1978         #
1979         'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
1980         'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
1981         'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1982         'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1983
1984         # Same fields for refund invoices
1985
1986         'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
1987         'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1988         'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1989         'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1990         '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."),
1991         'description': fields.char('Internal Name', size=32),
1992         'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,)
1993     }
1994
1995     def name_get(self, cr, uid, ids, context={}):
1996         if not len(ids):
1997             return []
1998         res = []
1999         for record in self.read(cr, uid, ids, ['description','name'], context):
2000             name = record['description'] and record['description'] or record['name']
2001             res.append((record['id'],name ))
2002         return res
2003
2004     def _default_company(self, cr, uid, context={}):
2005         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2006         if user.company_id:
2007             return user.company_id.id
2008         return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2009
2010     _defaults = {
2011         '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''',
2012         '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''',
2013         'applicable_type': lambda *a: 'true',
2014         'type': lambda *a: 'percent',
2015         'amount': lambda *a: 0,
2016         'sequence': lambda *a: 1,
2017         'tax_group': lambda *a: 'vat',
2018         'ref_tax_sign': lambda *a: 1,
2019         'ref_base_sign': lambda *a: 1,
2020         'tax_sign': lambda *a: 1,
2021         'base_sign': lambda *a: 1,
2022         'include_base_amount': lambda *a: False,
2023         'type_tax_use': lambda *a: 'all',
2024     }
2025     _order = 'sequence'
2026
2027
2028 account_tax_template()
2029
2030 # Fiscal Position Templates
2031
2032 class account_fiscal_position_template(osv.osv):
2033     _name = 'account.fiscal.position.template'
2034     _description = 'Template for Fiscal Position'
2035
2036     _columns = {
2037         'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
2038         'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2039         'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2040         'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2041     }
2042
2043 account_fiscal_position_template()
2044
2045 class account_fiscal_position_tax_template(osv.osv):
2046     _name = 'account.fiscal.position.tax.template'
2047     _description = 'Fiscal Position Template Tax Mapping'
2048     _rec_name = 'position_id'
2049
2050     _columns = {
2051         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2052         'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2053         'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2054     }
2055
2056 account_fiscal_position_tax_template()
2057
2058 class account_fiscal_position_account_template(osv.osv):
2059     _name = 'account.fiscal.position.account.template'
2060     _description = 'Fiscal Position Template Account Mapping'
2061     _rec_name = 'position_id'
2062     _columns = {
2063         'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2064         'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2065         'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2066     }
2067
2068 account_fiscal_position_account_template()
2069
2070     # Multi charts of Accounts wizard
2071
2072 class wizard_multi_charts_accounts(osv.osv_memory):
2073     """
2074     Create a new account chart for a company.
2075     Wizards ask for:
2076         * a company
2077         * an account chart template
2078         * a number of digits for formatting code of non-view accounts
2079         * a list of bank accounts owned by the company
2080     Then, the wizard:
2081         * generates all accounts from the template and assigns them to the right company
2082         * generates all taxes and tax codes, changing account assignations
2083         * generates all accounting properties and assigns them correctly
2084     """
2085     _name='wizard.multi.charts.accounts'
2086
2087     _columns = {
2088         'company_id':fields.many2one('res.company','Company',required=True),
2089         'chart_template_id': fields.many2one('account.chart.template','Chart Template',required=True),
2090         'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts',required=True),
2091         'code_digits':fields.integer('# of Digits',required=True,help="No. of Digits to use for account code"),
2092         '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."),
2093     }
2094
2095     def _get_chart(self, cr, uid, context={}):
2096         ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2097         if ids:
2098             return ids[0]
2099         return False
2100     _defaults = {
2101         'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr,uid,[uid],c)[0].company_id.id,
2102         'chart_template_id': _get_chart,
2103         'code_digits': lambda *a:6,
2104     }
2105
2106     def action_create(self, cr, uid, ids, context=None):
2107         obj_multi = self.browse(cr,uid,ids[0])
2108         obj_acc = self.pool.get('account.account')
2109         obj_acc_tax = self.pool.get('account.tax')
2110         obj_journal = self.pool.get('account.journal')
2111         obj_sequence = self.pool.get('ir.sequence')
2112         obj_acc_template = self.pool.get('account.account.template')
2113         obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2114         obj_fiscal_position = self.pool.get('account.fiscal.position')
2115
2116         # Creating Account
2117         obj_acc_root = obj_multi.chart_template_id.account_root_id
2118         tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2119         company_id = obj_multi.company_id.id
2120
2121         #new code
2122         acc_template_ref = {}
2123         tax_template_ref = {}
2124         tax_code_template_ref = {}
2125         todo_dict = {}
2126
2127         #create all the tax code
2128         children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2129         for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2130             vals={
2131                 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2132                 'code': tax_code_template.code,
2133                 'info': tax_code_template.info,
2134                 '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,
2135                 'company_id': company_id,
2136                 'sign': tax_code_template.sign,
2137             }
2138             new_tax_code = self.pool.get('account.tax.code').create(cr,uid,vals)
2139             #recording the new tax code to do the mapping
2140             tax_code_template_ref[tax_code_template.id] = new_tax_code
2141
2142         #create all the tax
2143         for tax in obj_multi.chart_template_id.tax_template_ids:
2144             #create it
2145             vals_tax = {
2146                 'name':tax.name,
2147                 'sequence': tax.sequence,
2148                 'amount':tax.amount,
2149                 'type':tax.type,
2150                 'applicable_type': tax.applicable_type,
2151                 'domain':tax.domain,
2152                 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2153                 'child_depend': tax.child_depend,
2154                 'python_compute': tax.python_compute,
2155                 'python_compute_inv': tax.python_compute_inv,
2156                 'python_applicable': tax.python_applicable,
2157                 'tax_group':tax.tax_group,
2158                 '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,
2159                 '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,
2160                 'base_sign': tax.base_sign,
2161                 'tax_sign': tax.tax_sign,
2162                 '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,
2163                 '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,
2164                 'ref_base_sign': tax.ref_base_sign,
2165                 'ref_tax_sign': tax.ref_tax_sign,
2166                 'include_base_amount': tax.include_base_amount,
2167                 'description':tax.description,
2168                 'company_id': company_id,
2169                 'type_tax_use': tax.type_tax_use
2170             }
2171             new_tax = obj_acc_tax.create(cr,uid,vals_tax)
2172             #as the accounts have not been created yet, we have to wait before filling these fields
2173             todo_dict[new_tax] = {
2174                 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2175                 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2176             }
2177             tax_template_ref[tax.id] = new_tax
2178
2179         #deactivate the parent_store functionnality on account_account for rapidity purpose
2180         self.pool._init = True
2181
2182         children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id])])
2183         children_acc_template.sort()
2184         for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2185             tax_ids = []
2186             for tax in account_template.tax_ids:
2187                 tax_ids.append(tax_template_ref[tax.id])
2188             #create the account_account
2189
2190             dig = obj_multi.code_digits
2191             code_main = account_template.code and len(account_template.code) or 0
2192             code_acc = account_template.code or ''
2193             if code_main>0 and code_main<=dig and account_template.type != 'view':
2194                 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2195             vals={
2196                 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2197                 #'sign': account_template.sign,
2198                 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2199                 'code': code_acc,
2200                 'type': account_template.type,
2201                 'user_type': account_template.user_type and account_template.user_type.id or False,
2202                 'reconcile': account_template.reconcile,
2203                 'shortcut': account_template.shortcut,
2204                 'note': account_template.note,
2205                 '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,
2206                 'tax_ids': [(6,0,tax_ids)],
2207                 'company_id': company_id,
2208             }
2209             new_account = obj_acc.create(cr,uid,vals)
2210             acc_template_ref[account_template.id] = new_account
2211         #reactivate the parent_store functionnality on account_account
2212         self.pool._init = False
2213         self.pool.get('account.account')._parent_store_compute(cr)
2214
2215         for key,value in todo_dict.items():
2216             if value['account_collected_id'] or value['account_paid_id']:
2217                 obj_acc_tax.write(cr, uid, [key], vals={
2218                     'account_collected_id': acc_template_ref[value['account_collected_id']],
2219                     'account_paid_id': acc_template_ref[value['account_paid_id']],
2220                 })
2221
2222         # Creating Journals
2223         vals_journal={}
2224         view_id = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Journal View')])[0]
2225         seq_id = obj_sequence.search(cr,uid,[('name','=','Account Journal')])[0]
2226
2227         if obj_multi.seq_journal:
2228             seq_id_sale = obj_sequence.search(cr,uid,[('name','=','Sale Journal')])[0]
2229             seq_id_purchase = obj_sequence.search(cr,uid,[('name','=','Purchase Journal')])[0]
2230         else:
2231             seq_id_sale = seq_id
2232             seq_id_purchase = seq_id
2233
2234         vals_journal['view_id'] = view_id
2235
2236         #Sales Journal
2237         vals_journal['name'] = _('Sales Journal')
2238         vals_journal['type'] = 'sale'
2239         vals_journal['code'] = _('SAJ')
2240         vals_journal['sequence_id'] = seq_id_sale
2241
2242         if obj_multi.chart_template_id.property_account_receivable:
2243             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2244             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2245
2246         obj_journal.create(cr,uid,vals_journal)
2247
2248         # Purchase Journal
2249         vals_journal['name'] = _('Purchase Journal')
2250         vals_journal['type'] = 'purchase'
2251         vals_journal['code'] = _('EXJ')
2252         vals_journal['sequence_id'] = seq_id_purchase
2253
2254         if obj_multi.chart_template_id.property_account_payable:
2255             vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2256             vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2257
2258         obj_journal.create(cr,uid,vals_journal)
2259
2260         # Bank Journals
2261         view_id_cash = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Cash Journal View')])[0]
2262         view_id_cur = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Multi-Currency Cash Journal View')])[0]
2263         ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2264
2265         current_num = 1
2266         for line in obj_multi.bank_accounts_id:
2267             #create the account_account for this bank journal
2268             tmp = self.pool.get('res.partner.bank').name_get(cr, uid, [line.acc_no.id])[0][1]
2269             dig = obj_multi.code_digits
2270             if ref_acc_bank.code:
2271                 try:
2272                     new_code = str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num)
2273                 except Exception,e:
2274                     new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)),'0')) + str(current_num)
2275             vals = {
2276                 'name': line.acc_no.bank and line.acc_no.bank.name+' '+tmp or tmp,
2277                 'currency_id': line.currency_id and line.currency_id.id or False,
2278                 'code': new_code,
2279                 'type': 'other',
2280                 'user_type': account_template.user_type and account_template.user_type.id or False,
2281                 'reconcile': True,
2282                 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2283                 'company_id': company_id,
2284             }
2285             acc_cash_id  = obj_acc.create(cr,uid,vals)
2286
2287             if obj_multi.seq_journal:
2288                 vals_seq={
2289                         'name': _('Bank Journal ') + vals['name'],
2290                         'code': 'account.journal',
2291                 }
2292                 seq_id = obj_sequence.create(cr,uid,vals_seq)
2293
2294             #create the bank journal
2295             vals_journal['name']= vals['name']
2296             vals_journal['code']= _('BNK') + str(current_num)
2297             vals_journal['sequence_id'] = seq_id
2298             vals_journal['type'] = 'cash'
2299             if line.currency_id:
2300                 vals_journal['view_id'] = view_id_cur
2301                 vals_journal['currency'] = line.currency_id.id
2302             else:
2303                 vals_journal['view_id'] = view_id_cash
2304             vals_journal['default_credit_account_id'] = acc_cash_id
2305             vals_journal['default_debit_account_id'] = acc_cash_id
2306             obj_journal.create(cr,uid,vals_journal)
2307
2308             current_num += 1
2309
2310         #create the properties
2311         property_obj = self.pool.get('ir.property')
2312         fields_obj = self.pool.get('ir.model.fields')
2313
2314         todo_list = [
2315             ('property_account_receivable','res.partner','account.account'),
2316             ('property_account_payable','res.partner','account.account'),
2317             ('property_account_expense_categ','product.category','account.account'),
2318             ('property_account_income_categ','product.category','account.account'),
2319             ('property_account_expense','product.template','account.account'),
2320             ('property_account_income','product.template','account.account')
2321         ]
2322         for record in todo_list:
2323             r = []
2324             r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2325             account = getattr(obj_multi.chart_template_id, record[0])
2326             field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2327             vals = {
2328                 'name': record[0],
2329                 'company_id': company_id,
2330                 'fields_id': field[0],
2331                 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2332             }
2333             if r:
2334                 #the property exist: modify it
2335                 property_obj.write(cr, uid, r, vals)
2336             else:
2337                 #create the property
2338                 property_obj.create(cr, uid, vals)
2339
2340         fp_ids = obj_fiscal_position_template.search(cr, uid,[('chart_template_id', '=', obj_multi.chart_template_id.id)])
2341
2342         if fp_ids:
2343             for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2344
2345                 vals_fp = {
2346                            'company_id' : company_id,
2347                            'name' : position.name,
2348                            }
2349                 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2350
2351                 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2352                 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2353
2354                 for tax in position.tax_ids:
2355                     vals_tax = {
2356                                 'tax_src_id' : tax_template_ref[tax.tax_src_id.id],
2357                                 'tax_dest_id' : tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2358                                 'position_id' : new_fp,
2359                                 }
2360                     obj_tax_fp.create(cr, uid, vals_tax)
2361
2362                 for acc in position.account_ids:
2363                     vals_acc = {
2364                                 'account_src_id' : acc_template_ref[acc.account_src_id.id],
2365                                 'account_dest_id' : acc_template_ref[acc.account_dest_id.id],
2366                                 'position_id' : new_fp,
2367                                 }
2368                     obj_ac_fp.create(cr, uid, vals_acc)
2369
2370         return {
2371                 'view_type': 'form',
2372                 "view_mode": 'form',
2373                 'res_model': 'ir.actions.configuration.wizard',
2374                 'type': 'ir.actions.act_window',
2375                 'target':'new',
2376         }
2377     def action_cancel(self,cr,uid,ids,conect=None):
2378         return {
2379                 'view_type': 'form',
2380                 "view_mode": 'form',
2381                 'res_model': 'ir.actions.configuration.wizard',
2382                 'type': 'ir.actions.act_window',
2383                 'target':'new',
2384         }
2385
2386
2387 wizard_multi_charts_accounts()
2388
2389 class account_bank_accounts_wizard(osv.osv_memory):
2390     _name='account.bank.accounts.wizard'
2391
2392     _columns = {
2393         'acc_no':fields.many2one('res.partner.bank','Account No.',required=True),
2394         'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2395         'currency_id':fields.many2one('res.currency', 'Currency'),
2396     }
2397
2398 account_bank_accounts_wizard()
2399
2400 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
2401