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