1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
23 from datetime import datetime
24 from time import mktime
25 from dateutil.relativedelta import relativedelta
26 from operator import itemgetter
30 from osv import fields, osv
31 import decimal_precision as dp
32 from tools.translate import _
34 def check_cycle(self, cr, uid, ids, context=None):
35 """ climbs the ``self._table.parent_id`` chains for 100 levels or
36 until it can't find any more parent(s)
38 Returns true if it runs out of parents (no cycle), false if
39 it can recurse 100 times without ending all chains
43 cr.execute('SELECT DISTINCT parent_id '\
44 'FROM '+self._table+' '\
46 'AND parent_id IS NOT NULL',(tuple(ids),))
47 ids = map(itemgetter(0), cr.fetchall())
53 class account_payment_term(osv.osv):
54 _name = "account.payment.term"
55 _description = "Payment Term"
57 'name': fields.char('Payment Term', size=64, translate=True, required=True),
58 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the payment term without removing it."),
59 'note': fields.text('Description', translate=True),
60 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
67 def compute(self, cr, uid, id, value, date_ref=False, context=None):
69 date_ref = datetime.now().strftime('%Y-%m-%d')
70 pt = self.browse(cr, uid, id, context=context)
73 obj_precision = self.pool.get('decimal.precision')
74 for line in pt.line_ids:
75 prec = obj_precision.precision_get(cr, uid, 'Account')
76 if line.value == 'fixed':
77 amt = round(line.value_amount, prec)
78 elif line.value == 'procent':
79 amt = round(value * line.value_amount, prec)
80 elif line.value == 'balance':
81 amt = round(amount, prec)
83 next_date = (datetime.strptime(date_ref, '%Y-%m-%d') + relativedelta(days=line.days))
85 next_first_date = next_date + relativedelta(day=1,months=1) #Getting 1st of next month
86 next_date = next_first_date + relativedelta(days=line.days2)
88 next_date += relativedelta(day=line.days2, months=1)
89 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
93 account_payment_term()
95 class account_payment_term_line(osv.osv):
96 _name = "account.payment.term.line"
97 _description = "Payment Term Line"
99 'name': fields.char('Line Name', size=32, required=True),
100 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the payment term lines from the lowest sequences to the higher ones"),
101 'value': fields.selection([('procent', 'Percent'),
102 ('balance', 'Balance'),
103 ('fixed', 'Fixed Amount')], 'Valuation',
104 required=True, help="""Select here the kind of valuation related to this payment term line. Note that you should have your last line with the type 'Balance' to ensure that the whole amount will be threated."""),
106 'value_amount': fields.float('Value Amount', digits_compute=dp.get_precision('Payment Term'), help="For Value percent enter % ratio between 0-1."),
107 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
108 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
109 'days2': fields.integer('Day of the Month', required=True, help="Day of the month, set -1 for the last day of the current month. If it's positive, it gives the day of the next month. Set 0 for net days (otherwise it's based on the beginning of the month)."),
110 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
119 def _check_percent(self, cr, uid, ids, context=None):
120 obj = self.browse(cr, uid, ids[0], context=context)
121 if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
126 (_check_percent, 'Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% ', ['value_amount']),
129 account_payment_term_line()
131 class account_account_type(osv.osv):
132 _name = "account.account.type"
133 _description = "Account Type"
135 'name': fields.char('Acc. Type Name', size=64, required=True),
136 'code': fields.char('Code', size=32, required=True),
137 '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.
139 'None' means that nothing will be done.
140 'Balance' will generally be used for cash accounts.
141 'Detail' will copy each existing journal item of the previous year, even the reconciled ones.
142 'Unreconciled' will copy only the journal items that were unreconciled on the first day of the new fiscal year."""),
143 '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.'),
144 'report_type':fields.selection([
146 ('income','Profit & Loss (Income Accounts)'),
147 ('expense','Profit & Loss (Expense Accounts)'),
148 ('asset','Balance Sheet (Assets Accounts)'),
149 ('liability','Balance Sheet (Liability Accounts)')
150 ],'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),
151 'note': fields.text('Description'),
154 'close_method': 'none',
156 'report_type': 'none',
160 account_account_type()
162 def _code_get(self, cr, uid, context=None):
163 acc_type_obj = self.pool.get('account.account.type')
164 ids = acc_type_obj.search(cr, uid, [])
165 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context=context)
166 return [(r['code'], r['name']) for r in res]
168 #----------------------------------------------------------
170 #----------------------------------------------------------
172 class account_tax(osv.osv):
173 _name = 'account.tax'
176 class account_account(osv.osv):
177 _order = "parent_left"
178 _parent_order = "code"
179 _name = "account.account"
180 _description = "Account"
182 logger = netsvc.Logger()
184 def search(self, cr, uid, args, offset=0, limit=None, order=None,
185 context=None, count=False):
190 while pos < len(args):
192 if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
193 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
194 if args[pos][0] == 'journal_id':
198 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2], context=context)
199 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
200 args[pos] = ('type','not in',('consolidation','view'))
202 ids3 = map(lambda x: x.id, jour.type_control_ids)
203 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
204 ids1 += map(lambda x: x.id, jour.account_control_ids)
205 args[pos] = ('id', 'in', ids1)
208 if context and context.has_key('consolidate_children'): #add consolidated children of accounts
209 ids = super(account_account, self).search(cr, uid, args, offset, limit,
210 order, context=context, count=count)
211 for consolidate_child in self.browse(cr, uid, context['account_id'], context=context).child_consol_ids:
212 ids.append(consolidate_child.id)
215 return super(account_account, self).search(cr, uid, args, offset, limit,
216 order, context=context, count=count)
218 def _get_children_and_consol(self, cr, uid, ids, context=None):
219 #this function search for all the children and all consolidated children (recursively) of the given account ids
220 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
222 for rec in self.browse(cr, uid, ids2, context=context):
223 for child in rec.child_consol_ids:
224 ids3.append(child.id)
226 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
229 def __compute(self, cr, uid, ids, field_names, arg=None, context=None,
230 query='', query_params=()):
231 """ compute the balance, debit and/or credit for the provided
235 `field_names`: the fields to compute (a list of any of
236 'balance', 'debit' and 'credit')
237 `arg`: unused fields.function stuff
238 `query`: additional query filter (as a string)
239 `query_params`: parameters for the provided query string
240 (__compute will handle their escaping) as a
244 'balance': "COALESCE(SUM(l.debit),0) " \
245 "- COALESCE(SUM(l.credit), 0) as balance",
246 'debit': "COALESCE(SUM(l.debit), 0) as debit",
247 'credit': "COALESCE(SUM(l.credit), 0) as credit"
249 #get all the necessary accounts
250 children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
251 #compute for each account the balance/debit/credit from the move lines
254 if children_and_consolidated:
255 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
259 wheres.append(query.strip())
260 if aml_query.strip():
261 wheres.append(aml_query.strip())
262 filters = " AND ".join(wheres)
263 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
264 'Filters: %s'%filters)
265 # IN might not work ideally in case there are too many
266 # children_and_consolidated, in that case join on a
268 # SELECT l.account_id as id FROM account_move_line l
269 # INNER JOIN (VALUES (id1), (id2), (id3), ...) AS tmp (id)
270 # ON l.account_id = tmp.id
271 # or make _get_children_and_consol return a query and join on that
272 request = ("SELECT l.account_id as id, " +\
273 ', '.join(map(mapping.__getitem__, field_names)) +
274 " FROM account_move_line l" \
275 " WHERE l.account_id IN %s " \
277 " GROUP BY l.account_id")
278 params = (tuple(children_and_consolidated),) + query_params
279 cr.execute(request, params)
280 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
281 'Status: %s'%cr.statusmessage)
283 for res in cr.dictfetchall():
284 accounts[res['id']] = res
286 # consolidate accounts with direct children
287 children_and_consolidated.reverse()
288 brs = list(self.browse(cr, uid, children_and_consolidated, context=context))
290 currency_obj = self.pool.get('res.currency')
294 # for child in current.child_id:
295 # if child.id not in sums:
296 # can_compute = False
298 # brs.insert(0, brs.pop(brs.index(child)))
300 # brs.insert(0, child)
303 for fn in field_names:
304 sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
305 for child in current.child_id:
306 if child.company_id.currency_id.id == current.company_id.currency_id.id:
307 sums[current.id][fn] += sums[child.id][fn]
309 sums[current.id][fn] += currency_obj.compute(cr, uid, child.company_id.currency_id.id, current.company_id.currency_id.id, sums[child.id][fn], context=context)
310 null_result = dict((fn, 0.0) for fn in field_names)
312 res[id] = sums.get(id, null_result)
318 def _get_company_currency(self, cr, uid, ids, field_name, arg, context=None):
320 for rec in self.browse(cr, uid, ids, context=context):
321 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.symbol)
324 def _get_child_ids(self, cr, uid, ids, field_name, arg, context=None):
326 for record in self.browse(cr, uid, ids, context=context):
327 if record.child_parent_ids:
328 result[record.id] = [x.id for x in record.child_parent_ids]
330 result[record.id] = []
332 if record.child_consol_ids:
333 for acc in record.child_consol_ids:
334 if acc.id not in result[record.id]:
335 result[record.id].append(acc.id)
339 def _get_level(self, cr, uid, ids, field_name, arg, context=None):
341 accounts = self.browse(cr, uid, ids, context=context)
342 for account in accounts:
344 if account.parent_id:
345 obj = self.browse(cr, uid, account.parent_id.id)
346 level = obj.level + 1
347 res[account.id] = level
351 'name': fields.char('Name', size=128, required=True, select=True),
352 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
353 'code': fields.char('Code', size=64, required=True, select=1),
354 'type': fields.selection([
356 ('other', 'Regular'),
357 ('receivable', 'Receivable'),
358 ('payable', 'Payable'),
359 ('liquidity','Liquidity'),
360 ('consolidation', 'Consolidation'),
361 ('closed', 'Closed'),
362 ], 'Internal Type', required=True, help="This type is used to differentiate types with "\
363 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
364 "can have children accounts for multi-company consolidations, payable/receivable are for "\
365 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
366 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
367 help="These types are defined according to your country. The type contains more information "\
368 "about the account and its specificities."),
369 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
370 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
371 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
372 'child_id': fields.function(_get_child_ids, type='many2many', relation="account.account", string="Child Accounts"),
373 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Balance', multi='balance'),
374 'credit': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Credit', multi='balance'),
375 'debit': fields.function(__compute, digits_compute=dp.get_precision('Account'), string='Debit', multi='balance'),
376 'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."),
377 'shortcut': fields.char('Shortcut', size=12),
378 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
379 'account_id', 'tax_id', 'Default Taxes'),
380 'note': fields.text('Note'),
381 'company_currency_id': fields.function(_get_company_currency, type='many2one', relation='res.currency', string='Company Currency'),
382 'company_id': fields.many2one('res.company', 'Company', required=True),
383 'active': fields.boolean('Active', select=2, help="If the active field is set to False, it will allow you to hide the account without removing it."),
385 'parent_left': fields.integer('Parent Left', select=1),
386 'parent_right': fields.integer('Parent Right', select=1),
387 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
389 'This will select how the current currency rate for outgoing transactions is computed. '\
390 'In most countries the legal method is "average" but only a few software systems are able to '\
391 'manage this. So if you import from another software system you may have to use the rate at date. ' \
392 'Incoming transactions always use the rate at date.', \
394 'level': fields.function(_get_level, string='Level', store=True, type='integer'),
401 'currency_mode': 'current',
402 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
405 def _check_recursion(self, cr, uid, ids, context=None):
406 obj_self = self.browse(cr, uid, ids[0], context=context)
407 p_id = obj_self.parent_id and obj_self.parent_id.id
408 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
411 cr.execute('SELECT DISTINCT child_id '\
412 'FROM account_account_consol_rel '\
413 'WHERE parent_id IN %s', (tuple(ids),))
414 child_ids = map(itemgetter(0), cr.fetchall())
416 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
419 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
420 if p_id and (p_id in s_ids):
426 def _check_type(self, cr, uid, ids, context=None):
429 accounts = self.browse(cr, uid, ids, context=context)
430 for account in accounts:
431 if account.child_id and account.type not in ('view', 'consolidation'):
436 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id']),
437 (_check_type, 'Configuration Error! \nYou cannot define children to an account with internal type different of "View"! ', ['type']),
440 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
442 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
448 if name and str(name).startswith('partner:'):
449 part_id = int(name.split(':')[1])
450 part = self.pool.get('res.partner').browse(cr, user, part_id, context=context)
451 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
453 if name and str(name).startswith('type:'):
454 type = name.split(':')[1]
455 args += [('type', '=', type)]
460 ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
462 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
464 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
465 if not ids and len(name.split()) >= 2:
466 #Separating code and name of account for searching
467 operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
468 ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
470 ids = self.search(cr, user, args, context=context, limit=limit)
471 return self.name_get(cr, user, ids, context=context)
473 def name_get(self, cr, uid, ids, context=None):
476 reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
479 name = record['name']
481 name = record['code'] + ' ' + name
482 res.append((record['id'], name))
485 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
486 account = self.browse(cr, uid, id, context=context)
490 default = default.copy()
491 default['code'] = (account['code'] or '') + '(copy)'
494 if account.id in done_list:
496 done_list.append(account.id)
498 for child in account.child_id:
499 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
501 new_child_ids.append(child_ids)
502 default['child_parent_ids'] = [(6, 0, new_child_ids)]
504 default['child_parent_ids'] = False
505 return super(account_account, self).copy(cr, uid, id, default, context=context)
507 def _check_moves(self, cr, uid, ids, method, context=None):
508 line_obj = self.pool.get('account.move.line')
509 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
511 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
512 if method == 'write':
513 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
514 elif method == 'unlink':
515 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
516 #Checking whether the account is set as a property to any Partner or not
517 value = 'account.account,' + str(ids[0])
518 partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
520 raise osv.except_osv(_('Warning !'), _('You cannot remove/deactivate an account which is set as a property to any Partner.'))
523 def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
524 group1 = ['payable', 'receivable', 'other']
525 group2 = ['consolidation','view']
526 line_obj = self.pool.get('account.move.line')
527 for account in self.browse(cr, uid, ids, context=context):
528 old_type = account.type
529 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
530 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
531 #Check for 'Closed' type
532 if old_type == 'closed' and new_type !='closed':
533 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains account entries!"))
534 #Check for change From group1 to group2 and vice versa
535 if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
536 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,))
539 def write(self, cr, uid, ids, vals, context=None):
543 # Dont allow changing the company_id when account_move_line already exist
544 if 'company_id' in vals:
545 move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
547 # Allow the write if the value is the same
548 for i in [i['company_id'][0] for i in self.read(cr,uid,ids,['company_id'])]:
549 if vals['company_id']!=i:
550 raise osv.except_osv(_('Warning !'), _('You cannot modify Company of account as its related record exist in Entry Lines'))
551 if 'active' in vals and not vals['active']:
552 self._check_moves(cr, uid, ids, "write", context=context)
553 if 'type' in vals.keys():
554 self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
555 return super(account_account, self).write(cr, uid, ids, vals, context=context)
557 def unlink(self, cr, uid, ids, context=None):
558 self._check_moves(cr, uid, ids, "unlink", context=context)
559 return super(account_account, self).unlink(cr, uid, ids, context=context)
563 class account_journal_view(osv.osv):
564 _name = "account.journal.view"
565 _description = "Journal View"
567 'name': fields.char('Journal View', size=64, required=True),
568 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
572 account_journal_view()
575 class account_journal_column(osv.osv):
577 def _col_get(self, cr, user, context=None):
579 cols = self.pool.get('account.move.line')._columns
581 if col in ('period_id', 'journal_id'):
583 result.append( (col, cols[col].string) )
587 _name = "account.journal.column"
588 _description = "Journal Column"
590 'name': fields.char('Column Name', size=64, required=True),
591 'field': fields.selection(_col_get, 'Field Name', required=True, size=32),
592 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
593 'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column.", readonly=True),
594 'required': fields.boolean('Required'),
595 'readonly': fields.boolean('Readonly'),
597 _order = "view_id, sequence"
599 account_journal_column()
601 class account_journal(osv.osv):
602 _name = "account.journal"
603 _description = "Journal"
605 'name': fields.char('Journal Name', size=64, required=True),
606 'code': fields.char('Code', size=5, required=True, help="The code will be used to generate the numbers of the journal entries of this journal."),
607 'type': fields.selection([('sale', 'Sale'),('sale_refund','Sale Refund'), ('purchase', 'Purchase'), ('purchase_refund','Purchase Refund'), ('cash', 'Cash'), ('bank', 'Bank and Cheques'), ('general', 'General'), ('situation', 'Opening/Closing Situation')], 'Type', size=32, required=True,
608 help="Select 'Sale' for Sale journal to be used at the time of making invoice."\
609 " Select 'Purchase' for Purchase Journal to be used at the time of approving purchase order."\
610 " Select 'Cash' to be used at the time of making payment."\
611 " Select 'General' for miscellaneous operations."\
612 " Select 'Opening/Closing Situation' to be used at the time of new fiscal year creation or end of year entries generation."),
613 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
614 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
615 '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."),
616 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
617 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
618 '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."),
619 '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"),
620 '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."),
621 '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),
622 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
623 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
624 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
625 '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.'),
626 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
627 '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'),
631 'user_id': lambda self, cr, uid, context: uid,
632 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
635 ('code_company_uniq', 'unique (code, company_id)', 'The code of the journal must be unique per company !'),
636 ('name_company_uniq', 'unique (name, company_id)', 'The name of the journal must be unique per company !'),
641 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
642 journal = self.browse(cr, uid, id, context=context)
645 default = default.copy()
646 default['code'] = (journal['code'] or '') + '(copy)'
647 default['name'] = (journal['name'] or '') + '(copy)'
648 default['sequence_id'] = False
649 return super(account_journal, self).copy(cr, uid, id, default, context=context)
651 def write(self, cr, uid, ids, vals, context=None):
654 for journal in self.browse(cr, uid, ids, context=context):
655 if 'company_id' in vals and journal.company_id.id != vals['company_id']:
656 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
658 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this journal as its related record exist in Entry Lines'))
659 return super(account_journal, self).write(cr, uid, ids, vals, context=context)
661 def create_sequence(self, cr, uid, vals, context=None):
663 Create new entry sequence for every new Joural
664 @param cr: cursor to database
665 @param user: id of current user
666 @param ids: list of record ids to be process
667 @param context: context arguments, like lang, time zone
668 @return: return a result
670 seq_pool = self.pool.get('ir.sequence')
671 seq_typ_pool = self.pool.get('ir.sequence.type')
674 code = vals['code'].lower()
680 seq_typ_pool.create(cr, uid, types)
686 'prefix': code + "/%(year)s/",
688 'number_increment': 1
690 return seq_pool.create(cr, uid, seq)
692 def create(self, cr, uid, vals, context=None):
693 if not 'sequence_id' in vals or not vals['sequence_id']:
694 vals.update({'sequence_id': self.create_sequence(cr, uid, vals, context)})
695 return super(account_journal, self).create(cr, uid, vals, context)
697 def name_get(self, cr, user, ids, context=None):
699 Returns a list of tupples containing id, name.
700 result format: {[(id, name), (id, name), ...]}
702 @param cr: A database cursor
703 @param user: ID of the user currently logged in
704 @param ids: list of ids for which name should be read
705 @param context: context arguments, like lang, time zone
707 @return: Returns a list of tupples containing id, name
709 result = self.browse(cr, user, ids, context=context)
714 name = "%s (%s)" % (rs.name, rs.currency.name)
715 res += [(rs.id, name)]
718 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
724 if context.get('journal_type', False):
725 args += [('type','=',context.get('journal_type'))]
727 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
729 ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
731 return self.name_get(cr, user, ids, context=context)
733 def onchange_type(self, cr, uid, ids, type, currency, context=None):
734 obj_data = self.pool.get('ir.model.data')
735 user_pool = self.pool.get('res.users')
738 'sale':'account_sp_journal_view',
739 'sale_refund':'account_sp_refund_journal_view',
740 'purchase':'account_sp_journal_view',
741 'purchase_refund':'account_sp_refund_journal_view',
742 'cash':'account_journal_bank_view',
743 'bank':'account_journal_bank_view',
744 'general':'account_journal_view',
745 'situation':'account_journal_view'
749 view_id = type_map.get(type, 'account_journal_view')
750 user = user_pool.browse(cr, uid, uid)
751 if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
752 view_id = 'account_journal_bank_view_multi'
753 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
754 data = obj_data.browse(cr, uid, data_id[0], context=context)
757 'centralisation':type == 'situation',
758 'view_id':data.res_id,
766 class account_fiscalyear(osv.osv):
767 _name = "account.fiscalyear"
768 _description = "Fiscal Year"
770 'name': fields.char('Fiscal Year', size=64, required=True),
771 'code': fields.char('Code', size=6, required=True),
772 'company_id': fields.many2one('res.company', 'Company', required=True),
773 'date_start': fields.date('Start Date', required=True),
774 'date_stop': fields.date('End Date', required=True),
775 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
776 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True),
780 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
782 _order = "date_start"
784 def _check_fiscal_year(self, cr, uid, ids, context=None):
785 current_fiscal_yr = self.browse(cr, uid, ids, context=context)[0]
786 obj_fiscal_ids = self.search(cr, uid, [('company_id', '=', current_fiscal_yr.company_id.id)], context=context)
787 obj_fiscal_ids.remove(ids[0])
788 data_fiscal_yr = self.browse(cr, uid, obj_fiscal_ids, context=context)
790 for old_fy in data_fiscal_yr:
791 if old_fy.company_id.id == current_fiscal_yr['company_id'].id:
792 # Condition to check if the current fiscal year falls in between any previously defined fiscal year
793 if old_fy.date_start <= current_fiscal_yr['date_start'] <= old_fy.date_stop or \
794 old_fy.date_start <= current_fiscal_yr['date_stop'] <= old_fy.date_stop:
798 def _check_duration(self, cr, uid, ids, context=None):
799 obj_fy = self.browse(cr, uid, ids[0], context=context)
800 if obj_fy.date_stop < obj_fy.date_start:
805 (_check_duration, 'Error! The duration of the Fiscal Year is invalid. ', ['date_stop']),
806 (_check_fiscal_year, 'Error! You cannot define overlapping fiscal years',['date_start', 'date_stop'])
809 def create_period3(self, cr, uid, ids, context=None):
810 return self.create_period(cr, uid, ids, context, 3)
812 def create_period(self, cr, uid, ids, context=None, interval=1):
813 period_obj = self.pool.get('account.period')
814 for fy in self.browse(cr, uid, ids, context=context):
815 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
816 period_obj.create(cr, uid, {
817 'name': _('Opening Period'),
818 'code': ds.strftime('00/%Y'),
822 'fiscalyear_id': fy.id,
824 while ds.strftime('%Y-%m-%d') < fy.date_stop:
825 de = ds + relativedelta(months=interval, days=-1)
827 if de.strftime('%Y-%m-%d') > fy.date_stop:
828 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
830 period_obj.create(cr, uid, {
831 'name': ds.strftime('%m/%Y'),
832 'code': ds.strftime('%m/%Y'),
833 'date_start': ds.strftime('%Y-%m-%d'),
834 'date_stop': de.strftime('%Y-%m-%d'),
835 'fiscalyear_id': fy.id,
837 ds = ds + relativedelta(months=interval)
840 def find(self, cr, uid, dt=None, exception=True, context=None):
842 dt = time.strftime('%Y-%m-%d')
843 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
846 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
851 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
858 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
860 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
861 return self.name_get(cr, user, ids, context=context)
865 class account_period(osv.osv):
866 _name = "account.period"
867 _description = "Account period"
869 'name': fields.char('Period Name', size=64, required=True),
870 'code': fields.char('Code', size=12),
871 'special': fields.boolean('Opening/Closing Period', size=12,
872 help="These periods can overlap."),
873 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
874 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
875 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
876 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True,
877 help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
878 'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
883 _order = "date_start, special desc"
885 def _check_duration(self,cr,uid,ids,context=None):
886 obj_period = self.browse(cr, uid, ids[0], context=context)
887 if obj_period.date_stop < obj_period.date_start:
891 def _check_year_limit(self,cr,uid,ids,context=None):
892 for obj_period in self.browse(cr, uid, ids, context=context):
893 if obj_period.special:
896 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
897 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
898 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
899 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
902 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
903 for period in self.browse(cr, uid, pids):
904 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
909 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
910 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
913 def next(self, cr, uid, period, step, context=None):
914 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
919 def find(self, cr, uid, dt=None, context=None):
921 dt = time.strftime('%Y-%m-%d')
922 #CHECKME: shouldn't we check the state of the period?
923 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
925 raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create one.')%dt)
928 def action_draft(self, cr, uid, ids, *args):
930 cr.execute('update account_journal_period set state=%s where period_id in %s', (mode, tuple(ids),))
931 cr.execute('update account_period set state=%s where id in %s', (mode, tuple(ids),))
934 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
941 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
943 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
944 return self.name_get(cr, user, ids, context=context)
946 def write(self, cr, uid, ids, vals, context=None):
947 if 'company_id' in vals:
948 move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
950 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines'))
951 return super(account_period, self).write(cr, uid, ids, vals, context=context)
953 def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
954 period_from = self.browse(cr, uid, period_from_id)
955 period_date_start = period_from.date_start
956 company1_id = period_from.company_id.id
957 period_to = self.browse(cr, uid, period_to_id)
958 period_date_stop = period_to.date_stop
959 company2_id = period_to.company_id.id
960 if company1_id != company2_id:
961 raise osv.except_osv(_('Error'), _('You should have chosen periods that belongs to the same company'))
962 if period_date_start > period_date_stop:
963 raise osv.except_osv(_('Error'), _('Start period should be smaller then End period'))
964 #for period from = january, we want to exclude the opening period (but it has same date_from, so we have to check if period_from is special or not to include that clause or not in the search).
965 if period_from.special:
966 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id)])
967 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id), ('special', '=', False)])
971 class account_journal_period(osv.osv):
972 _name = "account.journal.period"
973 _description = "Journal Period"
975 def _icon_get(self, cr, uid, ids, field_name, arg=None, context=None):
976 result = {}.fromkeys(ids, 'STOCK_NEW')
977 for r in self.read(cr, uid, ids, ['state']):
979 'draft': 'STOCK_NEW',
980 'printed': 'STOCK_PRINT_PREVIEW',
981 'done': 'STOCK_DIALOG_AUTHENTICATION',
982 }.get(r['state'], 'STOCK_NEW')
986 'name': fields.char('Journal-Period Name', size=64, required=True),
987 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
988 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
989 'icon': fields.function(_icon_get, string='Icon', type='char', size=32),
990 'active': fields.boolean('Active', required=True, help="If the active field is set to False, it will allow you to hide the journal period without removing it."),
991 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
992 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.'),
993 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
994 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
997 def _check(self, cr, uid, ids, context=None):
998 for obj in self.browse(cr, uid, ids, context=context):
999 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))
1002 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
1005 def write(self, cr, uid, ids, vals, context=None):
1006 self._check(cr, uid, ids, context=context)
1007 return super(account_journal_period, self).write(cr, uid, ids, vals, context=context)
1009 def create(self, cr, uid, vals, context=None):
1010 period_id = vals.get('period_id',False)
1012 period = self.pool.get('account.period').browse(cr, uid, period_id, context=context)
1013 vals['state']=period.state
1014 return super(account_journal_period, self).create(cr, uid, vals, context)
1016 def unlink(self, cr, uid, ids, context=None):
1017 self._check(cr, uid, ids, context=context)
1018 return super(account_journal_period, self).unlink(cr, uid, ids, context=context)
1024 _order = "period_id"
1026 account_journal_period()
1028 class account_fiscalyear(osv.osv):
1029 _inherit = "account.fiscalyear"
1030 _description = "Fiscal Year"
1032 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1035 def copy(self, cr, uid, id, default={}, context=None):
1038 'end_journal_period_id': False
1040 return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1042 account_fiscalyear()
1043 #----------------------------------------------------------
1045 #----------------------------------------------------------
1046 class account_move(osv.osv):
1047 _name = "account.move"
1048 _description = "Account Entry"
1051 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1053 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1054 result format: {[(id, name), (id, name), ...]}
1056 @param cr: A database cursor
1057 @param user: ID of the user currently logged in
1058 @param name: name to search
1059 @param args: other arguments
1060 @param operator: default operator is 'ilike', it can be changed
1061 @param context: context arguments, like lang, time zone
1062 @param limit: Returns first 'n' ids of complete result, default is 80.
1064 @return: Returns a list of tuples containing id and name
1071 ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1073 if not ids and name and type(name) == int:
1074 ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1077 ids += self.search(cr, user, args, limit=limit, context=context)
1079 return self.name_get(cr, user, ids, context=context)
1081 def name_get(self, cursor, user, ids, context=None):
1082 if isinstance(ids, (int, long)):
1087 data_move = self.pool.get('account.move').browse(cursor, user, ids, context=context)
1088 for move in data_move:
1089 if move.state=='draft':
1090 name = '*' + str(move.id)
1093 res.append((move.id, name))
1096 def _get_period(self, cr, uid, context=None):
1097 periods = self.pool.get('account.period').find(cr, uid)
1102 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1103 if not ids: return {}
1104 cr.execute( 'SELECT move_id, SUM(debit) '\
1105 'FROM account_move_line '\
1106 'WHERE move_id IN %s '\
1107 'GROUP BY move_id', (tuple(ids),))
1108 result = dict(cr.fetchall())
1110 result.setdefault(id, 0.0)
1113 def _search_amount(self, cr, uid, obj, name, args, context):
1117 if isinstance(cond[2],(list,tuple)):
1118 if cond[1] in ['in','not in']:
1119 amount = tuple(cond[2])
1123 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1126 cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1127 res_ids = set(id[0] for id in cr.fetchall())
1128 ids = ids and (ids & res_ids) or res_ids
1130 return [('id', 'in', tuple(ids))]
1131 return [('id', '=', '0')]
1134 'name': fields.char('Number', size=64, required=True),
1135 'ref': fields.char('Reference', size=64),
1136 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1137 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1138 'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'State', required=True, readonly=True,
1139 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.'),
1140 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1141 '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.'),
1142 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1143 'amount': fields.function(_amount_compute, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1144 'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}, select=True),
1145 'narration':fields.text('Narration'),
1146 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
1151 'period_id': _get_period,
1152 'date': lambda *a: time.strftime('%Y-%m-%d'),
1153 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1156 def _check_centralisation(self, cursor, user, ids, context=None):
1157 for move in self.browse(cursor, user, ids, context=context):
1158 if move.journal_id.centralisation:
1159 move_ids = self.search(cursor, user, [
1160 ('period_id', '=', move.period_id.id),
1161 ('journal_id', '=', move.journal_id.id),
1163 if len(move_ids) > 1:
1167 def _check_period_journal(self, cursor, user, ids, context=None):
1168 for move in self.browse(cursor, user, ids, context=context):
1169 for line in move.line_id:
1170 if line.period_id.id != move.period_id.id:
1172 if line.journal_id.id != move.journal_id.id:
1177 (_check_centralisation,
1178 'You cannot create more than one move per period on centralized journal',
1180 (_check_period_journal,
1181 'You cannot create entries on different periods/journals in the same move',
1185 def post(self, cr, uid, ids, context=None):
1188 invoice = context.get('invoice', False)
1189 valid_moves = self.validate(cr, uid, ids, context)
1192 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" !'))
1193 obj_sequence = self.pool.get('ir.sequence')
1194 for move in self.browse(cr, uid, valid_moves, context=context):
1197 journal = move.journal_id
1199 if invoice and invoice.internal_number:
1200 new_name = invoice.internal_number
1202 if journal.sequence_id:
1203 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1204 new_name = obj_sequence.get_id(cr, uid, journal.sequence_id.id, context=c)
1206 raise osv.except_osv(_('Error'), _('No sequence defined on the journal !'))
1209 self.write(cr, uid, [move.id], {'name':new_name})
1211 cr.execute('UPDATE account_move '\
1214 ('posted', tuple(valid_moves),))
1217 def button_validate(self, cursor, user, ids, context=None):
1218 for move in self.browse(cursor, user, ids, context=context):
1220 for line in move.line_id:
1221 account = line.account_id
1224 account = account.parent_id
1227 elif top<>account2.id:
1228 raise osv.except_osv(_('Error !'), _('You cannot validate a Journal Entry unless all journal items are in same chart of accounts !'))
1229 return self.post(cursor, user, ids, context=context)
1231 def button_cancel(self, cr, uid, ids, context=None):
1232 for line in self.browse(cr, uid, ids, context=context):
1233 if not line.journal_id.update_posted:
1234 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.'))
1236 cr.execute('UPDATE account_move '\
1238 'WHERE id IN %s', ('draft', tuple(ids),))
1241 def write(self, cr, uid, ids, vals, context=None):
1245 c['novalidate'] = True
1246 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
1247 self.validate(cr, uid, ids, context=context)
1251 # TODO: Check if period is closed !
1253 def create(self, cr, uid, vals, context=None):
1256 if 'line_id' in vals and context.get('copy'):
1257 for l in vals['line_id']:
1260 'reconcile_id':False,
1261 'reconcil_partial_id':False,
1262 'analytic_lines':False,
1266 'account_tax_id':False,
1269 if 'journal_id' in vals and vals.get('journal_id', False):
1270 for l in vals['line_id']:
1272 l[2]['journal_id'] = vals['journal_id']
1273 context['journal_id'] = vals['journal_id']
1274 if 'period_id' in vals:
1275 for l in vals['line_id']:
1277 l[2]['period_id'] = vals['period_id']
1278 context['period_id'] = vals['period_id']
1280 default_period = self._get_period(cr, uid, context)
1281 for l in vals['line_id']:
1283 l[2]['period_id'] = default_period
1284 context['period_id'] = default_period
1286 if 'line_id' in vals:
1288 c['novalidate'] = True
1289 result = super(account_move, self).create(cr, uid, vals, c)
1290 self.validate(cr, uid, [result], context)
1292 result = super(account_move, self).create(cr, uid, vals, context)
1295 def copy(self, cr, uid, id, default={}, context=None):
1305 return super(account_move, self).copy(cr, uid, id, default, context)
1307 def unlink(self, cr, uid, ids, context=None, check=True):
1311 obj_move_line = self.pool.get('account.move.line')
1312 for move in self.browse(cr, uid, ids, context=context):
1313 if move['state'] != 'draft':
1314 raise osv.except_osv(_('UserError'),
1315 _('You can not delete posted movement: "%s"!') % \
1317 line_ids = map(lambda x: x.id, move.line_id)
1318 context['journal_id'] = move.journal_id.id
1319 context['period_id'] = move.period_id.id
1320 obj_move_line._update_check(cr, uid, line_ids, context)
1321 obj_move_line.unlink(cr, uid, line_ids, context=context)
1322 toremove.append(move.id)
1323 result = super(account_move, self).unlink(cr, uid, toremove, context)
1326 def _compute_balance(self, cr, uid, id, context=None):
1327 move = self.browse(cr, uid, id, context=context)
1329 for line in move.line_id:
1330 amount+= (line.debit - line.credit)
1333 def _centralise(self, cr, uid, move, mode, context=None):
1334 assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1335 currency_obj = self.pool.get('res.currency')
1340 account_id = move.journal_id.default_debit_account_id.id
1343 raise osv.except_osv(_('UserError'),
1344 _('There is no default default debit account defined \n' \
1345 'on journal "%s"') % move.journal_id.name)
1347 account_id = move.journal_id.default_credit_account_id.id
1350 raise osv.except_osv(_('UserError'),
1351 _('There is no default default credit account defined \n' \
1352 'on journal "%s"') % move.journal_id.name)
1354 # find the first line of this move with the current mode
1355 # or create it if it doesn't exist
1356 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1361 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1362 line_id = self.pool.get('account.move.line').create(cr, uid, {
1363 'name': _(mode.capitalize()+' Centralisation'),
1364 'centralisation': mode,
1365 'account_id': account_id,
1367 'journal_id': move.journal_id.id,
1368 'period_id': move.period_id.id,
1369 'date': move.period_id.date_stop,
1374 # find the first line of this move with the other mode
1375 # so that we can exclude it from our calculation
1376 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1383 cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1384 result = cr.fetchone()[0] or 0.0
1385 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1387 #adjust also the amount in currency if needed
1388 cr.execute("select currency_id, sum(amount_currency) as amount_currency from account_move_line where move_id = %s and currency_id is not null group by currency_id", (move.id,))
1389 for row in cr.dictfetchall():
1390 currency_id = currency_obj.browse(cr, uid, row['currency_id'], context=context)
1391 if not currency_obj.is_zero(cr, uid, currency_id, row['amount_currency']):
1392 amount_currency = row['amount_currency'] * -1
1393 account_id = amount_currency > 0 and move.journal_id.default_debit_account_id.id or move.journal_id.default_credit_account_id.id
1394 cr.execute('select id from account_move_line where move_id=%s and centralisation=\'currency\' and currency_id = %slimit 1', (move.id, row['currency_id']))
1397 cr.execute('update account_move_line set amount_currency=%s , account_id=%s where id=%s', (amount_currency, account_id, res[0]))
1399 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1400 line_id = self.pool.get('account.move.line').create(cr, uid, {
1401 'name': _('Currency Adjustment'),
1402 'centralisation': 'currency',
1403 'account_id': account_id,
1405 'journal_id': move.journal_id.id,
1406 'period_id': move.period_id.id,
1407 'date': move.period_id.date_stop,
1410 'currency_id': row['currency_id'],
1411 'amount_currency': amount_currency,
1417 # Validate a balanced move. If it is a centralised journal, create a move.
1419 def validate(self, cr, uid, ids, context=None):
1420 if context and ('__last_update' in context):
1421 del context['__last_update']
1423 valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1424 obj_analytic_line = self.pool.get('account.analytic.line')
1425 obj_move_line = self.pool.get('account.move.line')
1426 for move in self.browse(cr, uid, ids, context):
1427 # Unlink old analytic lines on move_lines
1428 for obj_line in move.line_id:
1429 for obj in obj_line.analytic_lines:
1430 obj_analytic_line.unlink(cr,uid,obj.id)
1432 journal = move.journal_id
1437 for line in move.line_id:
1438 amount += line.debit - line.credit
1439 line_ids.append(line.id)
1440 if line.state=='draft':
1441 line_draft_ids.append(line.id)
1444 company_id = line.account_id.company_id.id
1445 if not company_id == line.account_id.company_id.id:
1446 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1448 if line.account_id.currency_id and line.currency_id:
1449 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):
1450 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))
1452 if abs(amount) < 10 ** -4:
1453 # If the move is balanced
1454 # Add to the list of valid moves
1455 # (analytic lines will be created later for valid moves)
1456 valid_moves.append(move)
1458 # Check whether the move lines are confirmed
1460 if not line_draft_ids:
1462 # Update the move lines (set them as valid)
1464 obj_move_line.write(cr, uid, line_draft_ids, {
1465 'journal_id': move.journal_id.id,
1466 'period_id': move.period_id.id,
1468 }, context, check=False)
1473 if journal.type in ('purchase','sale'):
1474 for line in move.line_id:
1476 key = (line.account_id.id, line.tax_code_id.id)
1478 code = account2[key][0]
1479 amount = account2[key][1] * (line.debit + line.credit)
1480 elif line.account_id.id in account:
1481 code = account[line.account_id.id][0]
1482 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1483 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1484 obj_move_line.write(cr, uid, [line.id], {
1485 'tax_code_id': code,
1486 'tax_amount': amount
1487 }, context, check=False)
1488 elif journal.centralisation:
1489 # If the move is not balanced, it must be centralised...
1491 # Add to the list of valid moves
1492 # (analytic lines will be created later for valid moves)
1493 valid_moves.append(move)
1496 # Update the move lines (set them as valid)
1498 self._centralise(cr, uid, move, 'debit', context=context)
1499 self._centralise(cr, uid, move, 'credit', context=context)
1500 obj_move_line.write(cr, uid, line_draft_ids, {
1502 }, context, check=False)
1504 # We can't validate it (it's unbalanced)
1505 # Setting the lines as draft
1506 obj_move_line.write(cr, uid, line_ids, {
1507 'journal_id': move.journal_id.id,
1508 'period_id': move.period_id.id,
1510 }, context, check=False)
1511 # Create analytic lines for the valid moves
1512 for record in valid_moves:
1513 obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1515 valid_moves = [move.id for move in valid_moves]
1516 return len(valid_moves) > 0 and valid_moves or False
1520 class account_move_reconcile(osv.osv):
1521 _name = "account.move.reconcile"
1522 _description = "Account Reconciliation"
1524 'name': fields.char('Name', size=64, required=True),
1525 'type': fields.char('Type', size=16, required=True),
1526 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1527 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1528 'create_date': fields.date('Creation date', readonly=True),
1531 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1533 def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None):
1535 for rec in self.browse(cr, uid, ids, context=context):
1536 for line in rec.line_partial_ids:
1537 total += (line.debit or 0.0) - (line.credit or 0.0)
1539 self.pool.get('account.move.line').write(cr, uid,
1540 map(lambda x: x.id, rec.line_partial_ids),
1541 {'reconcile_id': rec.id }
1545 def name_get(self, cr, uid, ids, context=None):
1549 for r in self.browse(cr, uid, ids, context=context):
1550 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1552 name = '%s (%.2f)' % (r.name, total)
1553 result.append((r.id,name))
1555 result.append((r.id,r.name))
1558 account_move_reconcile()
1560 #----------------------------------------------------------
1562 #----------------------------------------------------------
1565 child_depend: la taxe depend des taxes filles
1567 class account_tax_code(osv.osv):
1569 A code for the tax object.
1571 This code is used for some tax declarations.
1573 def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1574 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1575 if context.get('based_on', 'invoices') == 'payments':
1576 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1577 FROM account_move_line AS line, \
1578 account_move AS move \
1579 LEFT JOIN account_invoice invoice ON \
1580 (invoice.move_id = move.id) \
1581 WHERE line.tax_code_id IN %s '+where+' \
1582 AND move.id = line.move_id \
1583 AND ((invoice.state = \'paid\') \
1584 OR (invoice.id IS NULL)) \
1585 GROUP BY line.tax_code_id',
1586 (parent_ids,) + where_params)
1588 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1589 FROM account_move_line AS line, \
1590 account_move AS move \
1591 WHERE line.tax_code_id IN %s '+where+' \
1592 AND move.id = line.move_id \
1593 GROUP BY line.tax_code_id',
1594 (parent_ids,) + where_params)
1595 res=dict(cr.fetchall())
1596 obj_precision = self.pool.get('decimal.precision')
1598 for record in self.browse(cr, uid, ids, context=context):
1599 def _rec_get(record):
1600 amount = res.get(record.id, 0.0)
1601 for rec in record.child_ids:
1602 amount += _rec_get(rec) * rec.sign
1604 res2[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1607 def _sum_year(self, cr, uid, ids, name, args, context=None):
1610 move_state = ('posted', )
1611 if context.get('state', 'all') == 'all':
1612 move_state = ('draft', 'posted', )
1613 if context.get('fiscalyear_id', False):
1614 fiscalyear_id = context['fiscalyear_id']
1616 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1620 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1622 where = ' AND line.period_id IN %s AND move.state IN %s '
1623 where_params = (tuple(pids), move_state)
1624 return self._sum(cr, uid, ids, name, args, context,
1625 where=where, where_params=where_params)
1627 def _sum_period(self, cr, uid, ids, name, args, context):
1630 move_state = ('posted', )
1631 if context.get('state', False) == 'all':
1632 move_state = ('draft', 'posted', )
1633 if context.get('period_id', False):
1634 period_id = context['period_id']
1636 period_id = self.pool.get('account.period').find(cr, uid)
1638 return dict.fromkeys(ids, 0.0)
1639 period_id = period_id[0]
1640 return self._sum(cr, uid, ids, name, args, context,
1641 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1643 _name = 'account.tax.code'
1644 _description = 'Tax Code'
1647 'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1648 'code': fields.char('Case Code', size=64),
1649 'info': fields.text('Description'),
1650 'sum': fields.function(_sum_year, string="Year Sum"),
1651 'sum_period': fields.function(_sum_period, string="Period Sum"),
1652 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1653 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1654 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1655 'company_id': fields.many2one('res.company', 'Company', required=True),
1656 '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.'),
1657 '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"),
1660 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1665 ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1666 return self.name_get(cr, user, ids, context)
1668 def name_get(self, cr, uid, ids, context=None):
1669 if isinstance(ids, (int, long)):
1673 if isinstance(ids, (int, long)):
1675 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1676 return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1679 def _default_company(self, cr, uid, context=None):
1680 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1682 return user.company_id.id
1683 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1685 'company_id': _default_company,
1687 'notprintable': False,
1690 def copy(self, cr, uid, id, default=None, context=None):
1693 default = default.copy()
1694 default.update({'line_ids': []})
1695 return super(account_tax_code, self).copy(cr, uid, id, default, context)
1697 _check_recursion = check_cycle
1699 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1705 class account_tax(osv.osv):
1709 Type: percent, fixed, none, code
1710 PERCENT: tax = price * amount
1711 FIXED: tax = price + amount
1713 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1714 return result in the context
1715 Ex: result=round(price_unit*0.21,4)
1718 def get_precision_tax():
1719 def change_digit_tax(cr):
1720 res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account')
1722 return change_digit_tax
1724 _name = 'account.tax'
1725 _description = 'Tax'
1727 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1728 '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."),
1729 'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For taxes of type percentage, enter % ratio between 0-1."),
1730 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the tax without removing it."),
1731 'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1732 help="The computation method for the tax amount."),
1733 'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1734 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1735 '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."),
1736 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1737 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1738 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1739 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1740 '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."),
1741 'python_compute':fields.text('Python Code'),
1742 'python_compute_inv':fields.text('Python Code (reverse)'),
1743 'python_applicable':fields.text('Python Code'),
1746 # Fields used for the VAT declaration
1748 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1749 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1750 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1751 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1753 # Same fields for refund invoices
1755 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1756 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1757 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1758 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1759 '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"),
1760 'company_id': fields.many2one('res.company', 'Company', required=True),
1761 'description': fields.char('Tax Code',size=32),
1762 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1763 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1767 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1769 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1770 result format: {[(id, name), (id, name), ...]}
1772 @param cr: A database cursor
1773 @param user: ID of the user currently logged in
1774 @param name: name to search
1775 @param args: other arguments
1776 @param operator: default operator is 'ilike', it can be changed
1777 @param context: context arguments, like lang, time zone
1778 @param limit: Returns first 'n' ids of complete result, default is 80.
1780 @return: Returns a list of tupples containing id and name
1788 ids = self.search(cr, user, [('description', '=', name)] + args, limit=limit, context=context)
1790 ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit, context=context)
1792 ids = self.search(cr, user, args, limit=limit, context=context or {})
1793 return self.name_get(cr, user, ids, context=context)
1795 def write(self, cr, uid, ids, vals, context=None):
1796 if vals.get('type', False) and vals['type'] in ('none', 'code'):
1797 vals.update({'amount': 0.0})
1798 return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1800 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1801 journal_pool = self.pool.get('account.journal')
1803 if context and context.has_key('type'):
1804 if context.get('type') in ('out_invoice','out_refund'):
1805 args += [('type_tax_use','in',['sale','all'])]
1806 elif context.get('type') in ('in_invoice','in_refund'):
1807 args += [('type_tax_use','in',['purchase','all'])]
1809 if context and context.has_key('journal_id'):
1810 journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1811 if journal.type in ('sale', 'purchase'):
1812 args += [('type_tax_use','in',[journal.type,'all'])]
1814 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1816 def name_get(self, cr, uid, ids, context=None):
1820 for record in self.read(cr, uid, ids, ['description','name'], context=context):
1821 name = record['description'] and record['description'] or record['name']
1822 res.append((record['id'],name ))
1825 def _default_company(self, cr, uid, context=None):
1826 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1828 return user.company_id.id
1829 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1832 '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''',
1833 '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''',
1834 'applicable_type': 'true',
1839 'type_tax_use': 'all',
1845 'include_base_amount': False,
1846 'company_id': _default_company,
1850 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1852 obj_partener_address = self.pool.get('res.partner.address')
1854 if tax.applicable_type=='code':
1855 localdict = {'price_unit':price_unit, 'address':obj_partener_address.browse(cr, uid, address_id), 'product':product, 'partner':partner}
1856 exec tax.python_applicable in localdict
1857 if localdict.get('result', False):
1863 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1864 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1866 cur_price_unit=price_unit
1867 obj_partener_address = self.pool.get('res.partner.address')
1869 # we compute the amount for the current tax object and append it to the result
1870 data = {'id':tax.id,
1871 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1872 'account_collected_id':tax.account_collected_id.id,
1873 'account_paid_id':tax.account_paid_id.id,
1874 'base_code_id': tax.base_code_id.id,
1875 'ref_base_code_id': tax.ref_base_code_id.id,
1876 'sequence': tax.sequence,
1877 'base_sign': tax.base_sign,
1878 'tax_sign': tax.tax_sign,
1879 'ref_base_sign': tax.ref_base_sign,
1880 'ref_tax_sign': tax.ref_tax_sign,
1881 'price_unit': cur_price_unit,
1882 'tax_code_id': tax.tax_code_id.id,
1883 'ref_tax_code_id': tax.ref_tax_code_id.id,
1886 if tax.type=='percent':
1887 amount = cur_price_unit * tax.amount
1888 data['amount'] = amount
1890 elif tax.type=='fixed':
1891 data['amount'] = tax.amount
1892 data['tax_amount']=quantity
1893 # data['amount'] = quantity
1894 elif tax.type=='code':
1895 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1896 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1897 exec tax.python_compute in localdict
1898 amount = localdict['result']
1899 data['amount'] = amount
1900 elif tax.type=='balance':
1901 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1902 data['balance'] = cur_price_unit
1904 amount2 = data.get('amount', 0.0)
1906 if tax.child_depend:
1909 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1910 res.extend(child_tax)
1911 if tax.child_depend:
1913 for name in ('base','ref_base'):
1914 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1915 r[name+'_code_id'] = latest[name+'_code_id']
1916 r[name+'_sign'] = latest[name+'_sign']
1917 r['price_unit'] = latest['price_unit']
1918 latest[name+'_code_id'] = False
1919 for name in ('tax','ref_tax'):
1920 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1921 r[name+'_code_id'] = latest[name+'_code_id']
1922 r[name+'_sign'] = latest[name+'_sign']
1923 r['amount'] = data['amount']
1924 latest[name+'_code_id'] = False
1925 if tax.include_base_amount:
1926 cur_price_unit+=amount2
1929 def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1932 'total': 0.0, # Total without taxes
1933 'total_included: 0.0, # Total with taxes
1934 'taxes': [] # List of taxes, see compute for the format
1937 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1938 totalin = totalex = round(price_unit * quantity, precision)
1942 if tax.price_include:
1946 tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
1948 totalex -= r.get('amount', 0.0)
1951 totlex_qty = totalex/quantity
1954 tex = self._compute(cr, uid, tex, totlex_qty, quantity, address_id=address_id, product=product, partner=partner)
1956 totalin += r.get('amount', 0.0)
1959 'total_included': totalin,
1963 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1964 logger = netsvc.Logger()
1965 logger.notifyChannel("warning", netsvc.LOG_WARNING,
1966 "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
1967 return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
1969 def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1971 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1975 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1976 one tax for each tax id in IDS and their children
1978 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1980 precision_pool = self.pool.get('decimal.precision')
1982 if r.get('balance',False):
1983 r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
1985 r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
1986 total += r['amount']
1989 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1990 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1991 obj_partener_address = self.pool.get('res.partner.address')
1994 cur_price_unit = price_unit
1996 tax_parent_tot = 0.0
1998 if (tax.type=='percent') and not tax.include_base_amount:
1999 tax_parent_tot += tax.amount
2002 if (tax.type=='fixed') and not tax.include_base_amount:
2003 cur_price_unit -= tax.amount
2006 if tax.type=='percent':
2007 if tax.include_base_amount:
2008 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
2010 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
2012 elif tax.type=='fixed':
2015 elif tax.type=='code':
2016 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
2017 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
2018 exec tax.python_compute_inv in localdict
2019 amount = localdict['result']
2020 elif tax.type=='balance':
2021 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
2023 if tax.include_base_amount:
2024 cur_price_unit -= amount
2033 'account_collected_id': tax.account_collected_id.id,
2034 'account_paid_id': tax.account_paid_id.id,
2035 'base_code_id': tax.base_code_id.id,
2036 'ref_base_code_id': tax.ref_base_code_id.id,
2037 'sequence': tax.sequence,
2038 'base_sign': tax.base_sign,
2039 'tax_sign': tax.tax_sign,
2040 'ref_base_sign': tax.ref_base_sign,
2041 'ref_tax_sign': tax.ref_tax_sign,
2042 'price_unit': cur_price_unit,
2043 'tax_code_id': tax.tax_code_id.id,
2044 'ref_tax_code_id': tax.ref_tax_code_id.id,
2047 if tax.child_depend:
2051 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
2052 res.extend(parent_tax)
2057 total += r['amount']
2059 r['price_unit'] -= total
2063 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
2065 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2066 Price Unit is a VAT included price
2070 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2071 one tax for each tax id in IDS and their children
2073 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
2075 obj_precision = self.pool.get('decimal.precision')
2077 prec = obj_precision.precision_get(cr, uid, 'Account')
2078 if r.get('balance',False):
2079 r['amount'] = round(r['balance'] * quantity, prec) - total
2081 r['amount'] = round(r['amount'] * quantity, prec)
2082 total += r['amount']
2087 # ---------------------------------------------------------
2088 # Account Entries Models
2089 # ---------------------------------------------------------
2091 class account_model(osv.osv):
2092 _name = "account.model"
2093 _description = "Account Model"
2095 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2096 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2097 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2098 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2099 'legend': fields.text('Legend', readonly=True, size=100),
2103 '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'),
2105 def generate(self, cr, uid, ids, datas={}, context=None):
2108 account_move_obj = self.pool.get('account.move')
2109 account_move_line_obj = self.pool.get('account.move.line')
2110 pt_obj = self.pool.get('account.payment.term')
2115 if datas.get('date', False):
2116 context.update({'date': datas['date']})
2118 period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
2120 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
2121 period_id = period_id[0]
2123 move_date = context.get('date', time.strftime('%Y-%m-%d'))
2124 move_date = datetime.strptime(move_date,"%Y-%m-%d")
2125 for model in self.browse(cr, uid, ids, context=context):
2126 entry['name'] = model.name%{'year':move_date.strftime("%Y"), 'month':move_date.strftime("%m"), 'date': move_date.strftime("%Y-%m")}
2127 move_id = account_move_obj.create(cr, uid, {
2128 'ref': entry['name'],
2129 'period_id': period_id,
2130 'journal_id': model.journal_id.id,
2131 'date': context.get('date',time.strftime('%Y-%m-%d'))
2133 move_ids.append(move_id)
2134 for line in model.lines_id:
2135 analytic_account_id = False
2136 if line.analytic_account_id:
2137 if not model.journal_id.analytic_journal_id:
2138 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2139 analytic_account_id = line.analytic_account_id.id
2142 'journal_id': model.journal_id.id,
2143 'period_id': period_id,
2144 'analytic_account_id': analytic_account_id
2147 date_maturity = context.get('date',time.strftime('%Y-%m-%d'))
2148 if line.date_maturity == 'partner':
2149 if not line.partner_id:
2150 raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2151 "\nPlease define partner on it!")%(line.name, model.name))
2152 if line.partner_id.property_payment_term:
2153 payment_term_id = line.partner_id.property_payment_term.id
2154 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2156 pterm_list = [l[0] for l in pterm_list]
2158 date_maturity = pterm_list[-1]
2162 'quantity': line.quantity,
2163 'debit': line.debit,
2164 'credit': line.credit,
2165 'account_id': line.account_id.id,
2167 'partner_id': line.partner_id.id,
2168 'date': context.get('date',time.strftime('%Y-%m-%d')),
2169 'date_maturity': date_maturity
2172 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
2173 account_move_line_obj.create(cr, uid, val, context=c)
2179 class account_model_line(osv.osv):
2180 _name = "account.model.line"
2181 _description = "Account Model Entries"
2183 'name': fields.char('Name', size=64, required=True),
2184 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones."),
2185 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries."),
2186 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2187 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2188 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2189 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2190 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2191 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2192 'currency_id': fields.many2one('res.currency', 'Currency'),
2193 'partner_id': fields.many2one('res.partner', 'Partner'),
2194 '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."),
2197 _sql_constraints = [
2198 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
2199 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
2201 account_model_line()
2203 # ---------------------------------------------------------
2204 # Account Subscription
2205 # ---------------------------------------------------------
2208 class account_subscription(osv.osv):
2209 _name = "account.subscription"
2210 _description = "Account Subscription"
2212 'name': fields.char('Name', size=64, required=True),
2213 'ref': fields.char('Reference', size=16),
2214 'model_id': fields.many2one('account.model', 'Model', required=True),
2215 'date_start': fields.date('Start Date', required=True),
2216 'period_total': fields.integer('Number of Periods', required=True),
2217 'period_nbr': fields.integer('Period', required=True),
2218 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2219 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
2220 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2223 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
2224 'period_type': 'month',
2229 def state_draft(self, cr, uid, ids, context=None):
2230 self.write(cr, uid, ids, {'state':'draft'})
2233 def check(self, cr, uid, ids, context=None):
2235 for sub in self.browse(cr, uid, ids, context=context):
2237 for line in sub.lines_id:
2238 if not line.move_id.id:
2242 todone.append(sub.id)
2244 self.write(cr, uid, todone, {'state':'done'})
2247 def remove_line(self, cr, uid, ids, context=None):
2249 for sub in self.browse(cr, uid, ids, context=context):
2250 for line in sub.lines_id:
2251 if not line.move_id.id:
2252 toremove.append(line.id)
2254 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2255 self.write(cr, uid, ids, {'state':'draft'})
2258 def compute(self, cr, uid, ids, context=None):
2259 for sub in self.browse(cr, uid, ids, context=context):
2261 for i in range(sub.period_total):
2262 self.pool.get('account.subscription.line').create(cr, uid, {
2264 'subscription_id': sub.id,
2266 if sub.period_type=='day':
2267 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2268 if sub.period_type=='month':
2269 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2270 if sub.period_type=='year':
2271 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2272 self.write(cr, uid, ids, {'state':'running'})
2275 account_subscription()
2277 class account_subscription_line(osv.osv):
2278 _name = "account.subscription.line"
2279 _description = "Account Subscription Line"
2281 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2282 'date': fields.date('Date', required=True),
2283 'move_id': fields.many2one('account.move', 'Entry'),
2286 def move_create(self, cr, uid, ids, context=None):
2289 obj_model = self.pool.get('account.model')
2290 for line in self.browse(cr, uid, ids, context=context):
2294 move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2295 tocheck[line.subscription_id.id] = True
2296 self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2297 all_moves.extend(move_ids)
2299 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2304 account_subscription_line()
2306 # ---------------------------------------------------------------
2307 # Account Templates: Account, Tax, Tax Code and chart. + Wizard
2308 # ---------------------------------------------------------------
2310 class account_tax_template(osv.osv):
2311 _name = 'account.tax.template'
2312 account_tax_template()
2314 class account_account_template(osv.osv):
2316 _name = "account.account.template"
2317 _description ='Templates for Accounts'
2320 'name': fields.char('Name', size=128, required=True, select=True),
2321 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2322 'code': fields.char('Code', size=64, select=1),
2323 'type': fields.selection([
2324 ('receivable','Receivable'),
2325 ('payable','Payable'),
2327 ('consolidation','Consolidation'),
2328 ('liquidity','Liquidity'),
2329 ('other','Regular'),
2330 ('closed','Closed'),
2331 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2332 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2333 "can have children accounts for multi-company consolidations, payable/receivable are for "\
2334 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2335 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2336 help="These types are defined according to your country. The type contains more information "\
2337 "about the account and its specificities."),
2338 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2339 'shortcut': fields.char('Shortcut', size=12),
2340 'note': fields.text('Note'),
2341 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2342 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2343 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2344 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2353 def _check_type(self, cr, uid, ids, context=None):
2356 accounts = self.browse(cr, uid, ids, context=context)
2357 for account in accounts:
2358 if account.parent_id and account.parent_id.type != 'view':
2362 _check_recursion = check_cycle
2364 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id']),
2365 (_check_type, 'Configuration Error! \nYou cannot define children to an account with internal type different of "View"! ', ['type']),
2369 def name_get(self, cr, uid, ids, context=None):
2372 reads = self.read(cr, uid, ids, ['name','code'], context=context)
2374 for record in reads:
2375 name = record['name']
2377 name = record['code']+' '+name
2378 res.append((record['id'],name ))
2381 account_account_template()
2383 class account_add_tmpl_wizard(osv.osv_memory):
2384 """Add one more account from the template.
2386 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2387 _name = 'account.addtmpl.wizard'
2389 def _get_def_cparent(self, cr, uid, context=None):
2390 acc_obj = self.pool.get('account.account')
2391 tmpl_obj = self.pool.get('account.account.template')
2392 tids = tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2393 if not tids or not tids[0]['parent_id']:
2395 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2397 if not ptids or not ptids[0]['code']:
2398 raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
2399 res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2400 return res and res[0] or False
2403 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2406 'cparent_id': _get_def_cparent,
2409 def action_create(self,cr,uid,ids,context=None):
2412 acc_obj = self.pool.get('account.account')
2413 tmpl_obj = self.pool.get('account.account.template')
2414 data = self.read(cr, uid, ids)
2415 company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2416 account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2418 'name': account_template.name,
2419 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2420 'code': account_template.code,
2421 'type': account_template.type,
2422 'user_type': account_template.user_type and account_template.user_type.id or False,
2423 'reconcile': account_template.reconcile,
2424 'shortcut': account_template.shortcut,
2425 'note': account_template.note,
2426 'parent_id': data[0]['cparent_id'],
2427 'company_id': company_id,
2429 acc_obj.create(cr, uid, vals)
2430 return {'type':'state', 'state': 'end' }
2432 def action_cancel(self, cr, uid, ids, context=None):
2433 return { 'type': 'state', 'state': 'end' }
2435 account_add_tmpl_wizard()
2437 class account_tax_code_template(osv.osv):
2439 _name = 'account.tax.code.template'
2440 _description = 'Tax Code Template'
2444 'name': fields.char('Tax Case Name', size=64, required=True),
2445 'code': fields.char('Case Code', size=64),
2446 'info': fields.text('Description'),
2447 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2448 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2449 'sign': fields.float('Sign For Parent', required=True),
2450 '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"),
2455 'notprintable': False,
2458 def name_get(self, cr, uid, ids, context=None):
2461 if isinstance(ids, (int, long)):
2463 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2464 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2467 _check_recursion = check_cycle
2469 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2471 _order = 'code,name'
2472 account_tax_code_template()
2475 class account_chart_template(osv.osv):
2476 _name="account.chart.template"
2477 _description= "Templates for Account Chart"
2480 'name': fields.char('Name', size=64, required=True),
2481 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
2482 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
2483 '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'),
2484 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
2485 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2486 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2487 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2488 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2489 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2490 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2491 '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'),
2492 'property_account_income_opening': fields.many2one('account.account.template','Opening Entries Income Account'),
2493 'property_account_expense_opening': fields.many2one('account.account.template','Opening Entries Expense Account'),
2496 account_chart_template()
2498 class account_tax_template(osv.osv):
2500 _name = 'account.tax.template'
2501 _description = 'Templates for Taxes'
2504 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2505 'name': fields.char('Tax Name', size=64, required=True),
2506 '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."),
2507 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2508 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True),
2509 '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."),
2510 '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."),
2511 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2512 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2513 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2514 '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."),
2515 'python_compute':fields.text('Python Code'),
2516 'python_compute_inv':fields.text('Python Code (reverse)'),
2517 'python_applicable':fields.text('Python Code'),
2520 # Fields used for the VAT declaration
2522 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2523 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2524 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2525 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2527 # Same fields for refund invoices
2529 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2530 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2531 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2532 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2533 '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."),
2534 'description': fields.char('Internal Name', size=32),
2535 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,),
2536 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
2539 def name_get(self, cr, uid, ids, context=None):
2543 for record in self.read(cr, uid, ids, ['description','name'], context=context):
2544 name = record['description'] and record['description'] or record['name']
2545 res.append((record['id'],name ))
2548 def _default_company(self, cr, uid, context=None):
2549 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2551 return user.company_id.id
2552 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2555 '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''',
2556 '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''',
2557 'applicable_type': 'true',
2565 'include_base_amount': False,
2566 'type_tax_use': 'all',
2571 account_tax_template()
2573 # Fiscal Position Templates
2575 class account_fiscal_position_template(osv.osv):
2576 _name = 'account.fiscal.position.template'
2577 _description = 'Template for Fiscal Position'
2580 'name': fields.char('Fiscal Position Template', size=64, required=True),
2581 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2582 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2583 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2586 account_fiscal_position_template()
2588 class account_fiscal_position_tax_template(osv.osv):
2589 _name = 'account.fiscal.position.tax.template'
2590 _description = 'Template Tax Fiscal Position'
2591 _rec_name = 'position_id'
2594 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2595 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2596 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2599 account_fiscal_position_tax_template()
2601 class account_fiscal_position_account_template(osv.osv):
2602 _name = 'account.fiscal.position.account.template'
2603 _description = 'Template Account Fiscal Mapping'
2604 _rec_name = 'position_id'
2606 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2607 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2608 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2611 account_fiscal_position_account_template()
2613 # Multi charts of Accounts wizard
2615 class wizard_multi_charts_accounts(osv.osv_memory):
2617 Create a new account chart for a company.
2620 * an account chart template
2621 * a number of digits for formatting code of non-view accounts
2622 * a list of bank accounts owned by the company
2624 * generates all accounts from the template and assigns them to the right company
2625 * generates all taxes and tax codes, changing account assignations
2626 * generates all accounting properties and assigns them correctly
2628 _name='wizard.multi.charts.accounts'
2629 _inherit = 'res.config'
2632 'company_id':fields.many2one('res.company', 'Company', required=True),
2633 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2634 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts', required=True),
2635 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2636 '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."),
2637 "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
2638 "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
2640 def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
2643 res['value']["sale_tax"] = False
2644 res['value']["purchase_tax"] = False
2645 if chart_template_id:
2646 # default tax is given by the lowesst sequence. For same sequence we will take the latest created as it will be the case for tax created while isntalling the generic chart of account
2647 sale_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2648 , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence, id desc")
2649 purchase_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2650 , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence, id desc")
2652 res['value']["sale_tax"] = sale_tax_ids and sale_tax_ids[0] or False
2653 res['value']["purchase_tax"] = purchase_tax_ids and purchase_tax_ids[0] or False
2656 def _get_purchase_tax(self, cr, uid, context=None):
2657 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2659 chart_template_id = ids[0]
2660 purchase_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2661 , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence")
2662 return purchase_tax_ids and purchase_tax_ids[0] or False
2665 def _get_sale_tax(self, cr, uid, context=None):
2666 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2668 chart_template_id = ids[0]
2669 sale_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2670 , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence")
2671 return sale_tax_ids and sale_tax_ids[0] or False
2674 def _get_chart(self, cr, uid, context=None):
2675 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2680 def _get_default_accounts(self, cr, uid, context=None):
2681 return [{'acc_name': _('Current'),'account_type':'bank'},
2682 {'acc_name': _('Deposit'),'account_type':'bank'},
2683 {'acc_name': _('Cash'),'account_type':'cash'}]
2686 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, [uid], c)[0].company_id.id,
2687 'chart_template_id': _get_chart,
2688 'bank_accounts_id': _get_default_accounts,
2689 'sale_tax': _get_sale_tax,
2690 'purchase_tax': _get_purchase_tax,
2695 def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
2696 res = super(wizard_multi_charts_accounts, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
2698 company_ids = self.pool.get('res.company').search(cr, uid, [], context=context)
2699 #display in the widget selection of companies, only the companies that haven't been configured yet (but don't care about the demo chart of accounts)
2700 cr.execute("SELECT company_id FROM account_account WHERE active = 't' AND account_account.parent_id IS NULL AND name != %s", ("Chart For Automated Tests",))
2701 configured_cmp = [r[0] for r in cr.fetchall()]
2702 unconfigured_cmp = list(set(company_ids)-set(configured_cmp))
2703 for field in res['fields']:
2704 if field == 'company_id':
2705 res['fields'][field]['domain'] = unconfigured_cmp
2706 res['fields'][field]['selection'] = [('', '')]
2707 if unconfigured_cmp:
2708 cmp_select = [(line.id, line.name) for line in self.pool.get('res.company').browse(cr, uid, unconfigured_cmp)]
2709 res['fields'][field]['selection'] = cmp_select
2712 def execute(self, cr, uid, ids, context=None):
2713 obj_multi = self.browse(cr, uid, ids[0])
2714 obj_acc = self.pool.get('account.account')
2715 obj_acc_tax = self.pool.get('account.tax')
2716 obj_journal = self.pool.get('account.journal')
2717 obj_acc_template = self.pool.get('account.account.template')
2718 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2719 obj_fiscal_position = self.pool.get('account.fiscal.position')
2720 obj_data = self.pool.get('ir.model.data')
2721 analytic_journal_obj = self.pool.get('account.analytic.journal')
2722 obj_tax_code = self.pool.get('account.tax.code')
2723 obj_tax_code_template = self.pool.get('account.tax.code.template')
2724 ir_values_obj = self.pool.get('ir.values')
2726 obj_acc_root = obj_multi.chart_template_id.account_root_id
2727 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2728 company_id = obj_multi.company_id.id
2731 acc_template_ref = {}
2732 tax_template_ref = {}
2733 tax_code_template_ref = {}
2736 #create all the tax code
2737 children_tax_code_template = obj_tax_code_template.search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2738 children_tax_code_template.sort()
2739 for tax_code_template in obj_tax_code_template.browse(cr, uid, children_tax_code_template, context=context):
2741 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2742 'code': tax_code_template.code,
2743 'info': tax_code_template.info,
2744 '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,
2745 'company_id': company_id,
2746 'sign': tax_code_template.sign,
2748 new_tax_code = obj_tax_code.create(cr, uid, vals)
2749 #recording the new tax code to do the mapping
2750 tax_code_template_ref[tax_code_template.id] = new_tax_code
2753 tax_template_to_tax = {}
2754 for tax in obj_multi.chart_template_id.tax_template_ids:
2758 'sequence': tax.sequence,
2759 'amount':tax.amount,
2761 'applicable_type': tax.applicable_type,
2762 'domain':tax.domain,
2763 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2764 'child_depend': tax.child_depend,
2765 'python_compute': tax.python_compute,
2766 'python_compute_inv': tax.python_compute_inv,
2767 'python_applicable': tax.python_applicable,
2768 '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,
2769 '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,
2770 'base_sign': tax.base_sign,
2771 'tax_sign': tax.tax_sign,
2772 '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,
2773 '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,
2774 'ref_base_sign': tax.ref_base_sign,
2775 'ref_tax_sign': tax.ref_tax_sign,
2776 'include_base_amount': tax.include_base_amount,
2777 'description':tax.description,
2778 'company_id': company_id,
2779 'type_tax_use': tax.type_tax_use,
2780 'price_include': tax.price_include
2782 new_tax = obj_acc_tax.create(cr, uid, vals_tax)
2783 tax_template_to_tax[tax.id] = new_tax
2784 #as the accounts have not been created yet, we have to wait before filling these fields
2785 todo_dict[new_tax] = {
2786 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2787 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2789 tax_template_ref[tax.id] = new_tax
2790 #deactivate the parent_store functionnality on account_account for rapidity purpose
2791 ctx = context and context.copy() or {}
2792 ctx['defer_parent_store_computation'] = True
2794 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2795 children_acc_template.sort()
2796 for account_template in obj_acc_template.browse(cr, uid, children_acc_template, context=context):
2798 for tax in account_template.tax_ids:
2799 tax_ids.append(tax_template_ref[tax.id])
2800 #create the account_account
2802 dig = obj_multi.code_digits
2803 code_main = account_template.code and len(account_template.code) or 0
2804 code_acc = account_template.code or ''
2805 if code_main>0 and code_main<=dig and account_template.type != 'view':
2806 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2808 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2809 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2811 'type': account_template.type,
2812 'user_type': account_template.user_type and account_template.user_type.id or False,
2813 'reconcile': account_template.reconcile,
2814 'shortcut': account_template.shortcut,
2815 'note': account_template.note,
2816 '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,
2817 'tax_ids': [(6,0,tax_ids)],
2818 'company_id': company_id,
2820 new_account = obj_acc.create(cr, uid, vals, context=ctx)
2821 acc_template_ref[account_template.id] = new_account
2824 #reactivate the parent_store functionnality on account_account
2825 obj_acc._parent_store_compute(cr)
2827 for key,value in todo_dict.items():
2828 if value['account_collected_id'] or value['account_paid_id']:
2829 obj_acc_tax.write(cr, uid, [key], {
2830 'account_collected_id': acc_template_ref.get(value['account_collected_id'], False),
2831 'account_paid_id': acc_template_ref.get(value['account_paid_id'], False),
2835 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_sp_journal_view')])
2836 data = obj_data.browse(cr, uid, data_id[0], context=context)
2837 view_id = data.res_id
2840 analytical_sale_ids = analytic_journal_obj.search(cr,uid,[('type','=','sale')])
2841 analytical_journal_sale = analytical_sale_ids and analytical_sale_ids[0] or False
2844 'name': _('Sales Journal'),
2848 'company_id': company_id,
2849 'analytic_journal_id': analytical_journal_sale,
2852 if obj_multi.chart_template_id.property_account_receivable:
2853 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2854 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2856 obj_journal.create(cr,uid,vals_journal)
2859 analytical_purchase_ids = analytic_journal_obj.search(cr,uid,[('type','=','purchase')])
2860 analytical_journal_purchase = analytical_purchase_ids and analytical_purchase_ids[0] or False
2863 'name': _('Purchase Journal'),
2867 'company_id': company_id,
2868 'analytic_journal_id': analytical_journal_purchase,
2871 if obj_multi.chart_template_id.property_account_payable:
2872 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2873 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2874 obj_journal.create(cr,uid,vals_journal)
2876 # Creating Journals Sales Refund and Purchase Refund
2877 data_id = obj_data.search(cr, uid, [('model', '=', 'account.journal.view'), ('name', '=', 'account_sp_refund_journal_view')], context=context)
2878 data = obj_data.browse(cr, uid, data_id[0], context=context)
2879 view_id = data.res_id
2881 #Sales Refund Journal
2883 'name': _('Sales Refund Journal'),
2884 'type': 'sale_refund',
2887 'analytic_journal_id': analytical_journal_sale,
2888 'company_id': company_id
2891 if obj_multi.chart_template_id.property_account_receivable:
2892 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2893 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2895 obj_journal.create(cr, uid, vals_journal, context=context)
2897 # Purchase Refund Journal
2899 'name': _('Purchase Refund Journal'),
2900 'type': 'purchase_refund',
2903 'analytic_journal_id': analytical_journal_purchase,
2904 'company_id': company_id
2907 if obj_multi.chart_template_id.property_account_payable:
2908 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2909 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2911 obj_journal.create(cr, uid, vals_journal, context=context)
2913 # Miscellaneous Journal
2914 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_view')])
2915 data = obj_data.browse(cr, uid, data_id[0], context=context)
2916 view_id = data.res_id
2918 analytical_miscellaneous_ids = analytic_journal_obj.search(cr, uid, [('type', '=', 'situation')], context=context)
2919 analytical_journal_miscellaneous = analytical_miscellaneous_ids and analytical_miscellaneous_ids[0] or False
2922 'name': _('Miscellaneous Journal'),
2926 'analytic_journal_id': analytical_journal_miscellaneous,
2927 'company_id': company_id
2930 obj_journal.create(cr, uid, vals_journal, context=context)
2932 # Opening Entries Journal
2933 if obj_multi.chart_template_id.property_account_income_opening and obj_multi.chart_template_id.property_account_expense_opening:
2935 'name': _('Opening Entries Journal'),
2936 'type': 'situation',
2939 'company_id': company_id,
2940 'centralisation': True,
2941 'default_credit_account_id': acc_template_ref[obj_multi.chart_template_id.property_account_income_opening.id],
2942 'default_debit_account_id': acc_template_ref[obj_multi.chart_template_id.property_account_expense_opening.id]
2944 obj_journal.create(cr, uid, vals_journal, context=context)
2947 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')])
2948 data = obj_data.browse(cr, uid, data_id[0], context=context)
2949 view_id_cash = data.res_id
2951 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view_multi')])
2952 data = obj_data.browse(cr, uid, data_id[0], context=context)
2953 view_id_cur = data.res_id
2954 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2958 for line in obj_multi.bank_accounts_id:
2959 #create the account_account for this bank journal
2961 dig = obj_multi.code_digits
2962 if not ref_acc_bank.code:
2963 raise osv.except_osv(_('Configuration Error !'), _('The bank account defined on the selected chart of account hasn\'t a code.'))
2965 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)), '0')) + str(current_num)
2966 ids = obj_acc.search(cr, uid, [('code', '=', new_code), ('company_id', '=', company_id)])
2973 'currency_id': line.currency_id and line.currency_id.id or False,
2975 'type': 'liquidity',
2976 'user_type': account_template.user_type and account_template.user_type.id or False,
2978 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2979 'company_id': company_id,
2981 acc_cash_id = obj_acc.create(cr,uid,vals)
2983 #create the bank journal
2985 'name': vals['name'],
2986 'code': _('BNK') + str(current_num),
2987 'type': line.account_type == 'cash' and 'cash' or 'bank',
2988 'company_id': company_id,
2989 'analytic_journal_id': False,
2990 'currency_id': False,
2992 if line.currency_id:
2993 vals_journal['view_id'] = view_id_cur
2994 vals_journal['currency'] = line.currency_id.id
2996 vals_journal['view_id'] = view_id_cash
2997 vals_journal['default_credit_account_id'] = acc_cash_id
2998 vals_journal['default_debit_account_id'] = acc_cash_id
2999 obj_journal.create(cr, uid, vals_journal)
3003 #create the properties
3004 property_obj = self.pool.get('ir.property')
3005 fields_obj = self.pool.get('ir.model.fields')
3008 ('property_account_receivable','res.partner','account.account'),
3009 ('property_account_payable','res.partner','account.account'),
3010 ('property_account_expense_categ','product.category','account.account'),
3011 ('property_account_income_categ','product.category','account.account'),
3012 ('property_account_expense','product.template','account.account'),
3013 ('property_account_income','product.template','account.account'),
3014 ('property_reserve_and_surplus_account','res.company','account.account')
3016 for record in todo_list:
3018 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
3019 account = getattr(obj_multi.chart_template_id, record[0])
3020 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
3023 'company_id': company_id,
3024 'fields_id': field[0],
3025 'value': account and 'account.account,' + str(acc_template_ref[account.id]) or False,
3029 #the property exist: modify it
3030 property_obj.write(cr, uid, r, vals)
3032 #create the property
3033 property_obj.create(cr, uid, vals)
3035 fp_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)])
3038 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
3039 obj_ac_fp = self.pool.get('account.fiscal.position.account')
3041 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids, context=context):
3044 'company_id': company_id,
3045 'name': position.name,
3047 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
3049 for tax in position.tax_ids:
3051 'tax_src_id': tax_template_ref[tax.tax_src_id.id],
3052 'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
3053 'position_id': new_fp,
3055 obj_tax_fp.create(cr, uid, vals_tax)
3057 for acc in position.account_ids:
3059 'account_src_id': acc_template_ref[acc.account_src_id.id],
3060 'account_dest_id': acc_template_ref[acc.account_dest_id.id],
3061 'position_id': new_fp,
3063 obj_ac_fp.create(cr, uid, vals_acc)
3065 if obj_multi.sale_tax:
3066 ir_values_obj.set(cr, uid, key='default', key2=False, name="taxes_id", company=obj_multi.company_id.id,
3067 models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.sale_tax.id]])
3068 if obj_multi.purchase_tax:
3069 ir_values_obj.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=obj_multi.company_id.id,
3070 models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.purchase_tax.id]])
3072 wizard_multi_charts_accounts()
3074 class account_bank_accounts_wizard(osv.osv_memory):
3075 _name='account.bank.accounts.wizard'
3078 'acc_name': fields.char('Account Name.', size=64, required=True),
3079 'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
3080 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
3081 'account_type': fields.selection([('cash','Cash'), ('check','Check'), ('bank','Bank')], 'Account Type', size=32),
3084 account_bank_accounts_wizard()
3086 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: