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 dateutil.relativedelta import relativedelta
25 from operator import itemgetter
29 from osv import fields, osv
30 import decimal_precision as dp
31 from tools.translate import _
33 def check_cycle(self, cr, uid, ids):
34 """ climbs the ``self._table.parent_id`` chains for 100 levels or
35 until it can't find any more parent(s)
37 Returns true if it runs out of parents (no cycle), false if
38 it can recurse 100 times without ending all chains
42 cr.execute('SELECT DISTINCT parent_id '\
43 'FROM '+self._table+' '\
45 'AND parent_id IS NOT NULL',(tuple(ids),))
46 ids = map(itemgetter(0), cr.fetchall())
52 class account_payment_term(osv.osv):
53 _name = "account.payment.term"
54 _description = "Payment Term"
56 'name': fields.char('Payment Term', size=64, translate=True, required=True),
57 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the payment term without removing it."),
58 'note': fields.text('Description', translate=True),
59 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
66 def compute(self, cr, uid, id, value, date_ref=False, context=None):
68 date_ref = datetime.now().strftime('%Y-%m-%d')
69 pt = self.browse(cr, uid, id, context=context)
72 obj_precision = self.pool.get('decimal.precision')
73 for line in pt.line_ids:
74 prec = obj_precision.precision_get(cr, uid, 'Account')
75 if line.value == 'fixed':
76 amt = round(line.value_amount, prec)
77 elif line.value == 'procent':
78 amt = round(value * line.value_amount, prec)
79 elif line.value == 'balance':
80 amt = round(amount, prec)
82 next_date = (datetime.strptime(date_ref, '%Y-%m-%d') + relativedelta(days=line.days))
84 next_first_date = next_date + relativedelta(day=1,months=1) #Getting 1st of next month
85 next_date = next_first_date + relativedelta(days=line.days2)
87 next_date += relativedelta(day=line.days2, months=1)
88 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
92 account_payment_term()
94 class account_payment_term_line(osv.osv):
95 _name = "account.payment.term.line"
96 _description = "Payment Term Line"
98 'name': fields.char('Line Name', size=32, required=True),
99 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the payment term lines from the lowest sequences to the higher ones"),
100 'value': fields.selection([('procent', 'Percent'),
101 ('balance', 'Balance'),
102 ('fixed', 'Fixed Amount')], 'Valuation',
103 required=True, help="""Select here the kind of valuation related to this payment term line. Note that you should have your last line with the type 'Balance' to ensure that the whole amount will be threated."""),
105 'value_amount': fields.float('Value Amount', help="For Value percent enter % ratio between 0-1."),
106 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
107 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
108 'days2': fields.integer('Day of the Month', required=True, help="Day of the month, set -1 for the last day of the current month. If it's positive, it gives the day of the next month. Set 0 for net days (otherwise it's based on the beginning of the month)."),
109 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
118 def _check_percent(self, cr, uid, ids, context=None):
119 obj = self.browse(cr, uid, ids[0])
120 if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
125 (_check_percent, _('Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% '), ['value_amount']),
128 account_payment_term_line()
130 class account_account_type(osv.osv):
131 _name = "account.account.type"
132 _description = "Account Type"
134 'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
135 'code': fields.char('Code', size=32, required=True),
136 'close_method': fields.selection([('none', 'None'), ('balance', 'Balance'), ('detail', 'Detail'), ('unreconciled', 'Unreconciled')], 'Deferral Method', required=True, help="""Set here the method that will be used to generate the end of year journal entries for all the accounts of this type.
138 'None' means that nothing will be done.
139 'Balance' will generally be used for cash accounts.
140 'Detail' will copy each existing journal item of the previous year, even the reconciled ones.
141 'Unreconciled' will copy only the journal items that were unreconciled on the first day of the new fiscal year."""),
142 'sign': fields.selection([(-1, '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.'),
143 'report_type':fields.selection([
145 ('income','Profit & Loss (Income Accounts)'),
146 ('expense','Profit & Loss (Expense Accounts)'),
147 ('asset','Balance Sheet (Assets Accounts)'),
148 ('liability','Balance Sheet (Liability Accounts)')
149 ],'P&L / BS Category', select=True, readonly=False, help="According value related accounts will be display on respective reports (Balance Sheet Profit & Loss Account)", required=True),
150 'note': fields.text('Description'),
153 'close_method': 'none',
155 'report_type': 'none',
159 account_account_type()
161 def _code_get(self, cr, uid, context=None):
162 acc_type_obj = self.pool.get('account.account.type')
163 ids = acc_type_obj.search(cr, uid, [])
164 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context=context)
165 return [(r['code'], r['name']) for r in res]
167 #----------------------------------------------------------
169 #----------------------------------------------------------
171 class account_tax(osv.osv):
172 _name = 'account.tax'
175 class account_account(osv.osv):
176 _order = "parent_left"
177 _parent_order = "code"
178 _name = "account.account"
179 _description = "Account"
181 logger = netsvc.Logger()
183 def search(self, cr, uid, args, offset=0, limit=None, order=None,
184 context=None, count=False):
189 while pos < len(args):
191 if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
192 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
193 if args[pos][0] == 'journal_id':
197 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
198 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
199 args[pos] = ('type','not in',('consolidation','view'))
201 ids3 = map(lambda x: x.id, jour.type_control_ids)
202 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
203 ids1 += map(lambda x: x.id, jour.account_control_ids)
204 args[pos] = ('id', 'in', ids1)
207 if context and context.has_key('consolidate_childs'): #add consolidated childs of accounts
208 ids = super(account_account, self).search(cr, uid, args, offset, limit,
209 order, context=context, count=count)
210 for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
211 ids.append(consolidate_child.id)
214 return super(account_account, self).search(cr, uid, args, offset, limit,
215 order, context=context, count=count)
217 def _get_children_and_consol(self, cr, uid, ids, context=None):
220 #this function search for all the children and all consolidated children (recursively) of the given account ids
221 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
223 for rec in self.browse(cr, uid, ids2, context=context):
224 for child in rec.child_consol_ids:
225 ids3.append(child.id)
227 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
230 def __compute(self, cr, uid, ids, field_names, arg=None, context=None,
231 query='', query_params=()):
232 """ compute the balance, debit and/or credit for the provided
236 `field_names`: the fields to compute (a list of any of
237 'balance', 'debit' and 'credit')
238 `arg`: unused fields.function stuff
239 `query`: additional query filter (as a string)
240 `query_params`: parameters for the provided query string
241 (__compute will handle their escaping) as a
245 'balance': "COALESCE(SUM(l.debit),0) " \
246 "- COALESCE(SUM(l.credit), 0) as balance",
247 'debit': "COALESCE(SUM(l.debit), 0) as debit",
248 'credit': "COALESCE(SUM(l.credit), 0) as credit"
250 #get all the necessary accounts
251 children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
252 #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))
293 for child in current.child_id:
294 if child.id not in sums:
297 brs.insert(0, brs.pop(brs.index(child)))
302 for fn in field_names:
303 sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
305 sums[current.id][fn] += sum(sums[child.id][fn] for child in current.child_id)
307 null_result = dict((fn, 0.0) for fn in field_names)
309 res[id] = sums.get(id, null_result)
312 def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
314 for rec in self.browse(cr, uid, ids, context):
315 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
318 def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
320 for record in self.browse(cr, uid, ids, context):
321 if record.child_parent_ids:
322 result[record.id] = [x.id for x in record.child_parent_ids]
324 result[record.id] = []
326 if record.child_consol_ids:
327 for acc in record.child_consol_ids:
328 if acc.id not in result[record.id]:
329 result[record.id].append(acc.id)
333 def _get_level(self, cr, uid, ids, field_name, arg, context={}):
335 accounts = self.browse(cr, uid, ids)
336 for account in accounts:
338 if account.parent_id:
339 obj = self.browse(cr, uid, account.parent_id.id)
340 level = obj.level + 1
341 res[account.id] = level
345 'name': fields.char('Name', size=128, required=True, select=True),
346 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
347 'code': fields.char('Code', size=64, required=True),
348 'type': fields.selection([
350 ('other', 'Regular'),
351 ('receivable', 'Receivable'),
352 ('payable', 'Payable'),
353 ('liquidity','Liquidity'),
354 ('consolidation', 'Consolidation'),
355 ('closed', 'Closed'),
356 ], 'Internal Type', required=True, help="This type is used to differentiate types with "\
357 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
358 "can have children accounts for multi-company consolidations, payable/receivable are for "\
359 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
360 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
361 help="These types are defined according to your country. The type contains more information "\
362 "about the account and its specificities."),
363 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
364 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
365 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
366 'child_id': fields.function(_get_child_ids, method=True, type='many2many', relation="account.account", string="Child Accounts"),
367 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Balance', multi='balance'),
368 'credit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Credit', multi='balance'),
369 'debit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Debit', multi='balance'),
370 'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."),
371 'shortcut': fields.char('Shortcut', size=12),
372 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
373 'account_id', 'tax_id', 'Default Taxes'),
374 'note': fields.text('Note'),
375 'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
376 'company_id': fields.many2one('res.company', 'Company', required=True),
377 '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."),
379 'parent_left': fields.integer('Parent Left', select=1),
380 'parent_right': fields.integer('Parent Right', select=1),
381 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
383 'This will select how the current currency rate for outgoing transactions is computed. '\
384 'In most countries the legal method is "average" but only a few software systems are able to '\
385 'manage this. So if you import from another software system you may have to use the rate at date. ' \
386 'Incoming transactions always use the rate at date.', \
388 'level': fields.function(_get_level, string='Level', method=True, store=True, type='integer'),
395 'currency_mode': 'current',
396 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
399 def _check_recursion(self, cr, uid, ids):
400 obj_self = self.browse(cr, uid, ids[0])
401 p_id = obj_self.parent_id and obj_self.parent_id.id
402 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
405 cr.execute('SELECT DISTINCT child_id '\
406 'FROM account_account_consol_rel '\
407 'WHERE parent_id IN %s', (tuple(ids),))
408 child_ids = map(itemgetter(0), cr.fetchall())
410 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
413 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
414 if p_id and (p_id in s_ids):
421 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
424 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
426 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
434 if name and str(name).startswith('partner:'):
435 part_id = int(name.split(':')[1])
436 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
437 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
439 if name and str(name).startswith('type:'):
440 type = name.split(':')[1]
441 args += [('type', '=', type)]
446 ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
448 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
450 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
451 if not ids and len(name.split()) >= 2:
452 #Separating code and name of account for searching
453 operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
454 ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
456 ids = self.search(cr, user, args, context=context, limit=limit)
457 return self.name_get(cr, user, ids, context=context)
459 def name_get(self, cr, uid, ids, context=None):
462 reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
465 name = record['name']
467 name = record['code'] + ' '+name
468 res.append((record['id'], name))
471 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
472 account = self.browse(cr, uid, id, context=context)
476 default = default.copy()
477 default['code'] = (account['code'] or '') + '(copy)'
480 if account.id in done_list:
482 done_list.append(account.id)
484 for child in account.child_id:
485 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
487 new_child_ids.append(child_ids)
488 default['child_parent_ids'] = [(6, 0, new_child_ids)]
490 default['child_parent_ids'] = False
491 return super(account_account, self).copy(cr, uid, id, default, context=context)
493 def _check_moves(self, cr, uid, ids, method, context=None):
494 line_obj = self.pool.get('account.move.line')
495 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
497 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
498 if method == 'write':
499 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
500 elif method == 'unlink':
501 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
502 #Checking whether the account is set as a property to any Partner or not
503 value = 'account.account,' + str(ids[0])
504 partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
506 raise osv.except_osv(_('Warning !'), _('You cannot remove/deactivate an account which is set as a property to any Partner.'))
509 def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
510 group1 = ['payable', 'receivable', 'other']
511 group2 = ['consolidation','view']
512 line_obj = self.pool.get('account.move.line')
513 for account in self.browse(cr, uid, ids, context=context):
514 old_type = account.type
515 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
516 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
517 #Check for 'Closed' type
518 if old_type == 'closed' and new_type !='closed':
519 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains account entries!"))
520 #Check for change From group1 to group2 and vice versa
521 if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
522 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,))
525 def write(self, cr, uid, ids, vals, context=None):
529 if 'company_id' in vals:
530 move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
532 raise osv.except_osv(_('Warning !'), _('You cannot modify Company of account as its related record exist in Entry Lines'))
533 if 'active' in vals and not vals['active']:
534 self._check_moves(cr, uid, ids, "write", context=context)
535 if 'type' in vals.keys():
536 self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
537 return super(account_account, self).write(cr, uid, ids, vals, context=context)
539 def unlink(self, cr, uid, ids, context=None):
540 self._check_moves(cr, uid, ids, "unlink", context=context)
541 return super(account_account, self).unlink(cr, uid, ids, context=context)
545 class account_journal_view(osv.osv):
546 _name = "account.journal.view"
547 _description = "Journal View"
549 'name': fields.char('Journal View', size=64, required=True),
550 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
554 account_journal_view()
557 class account_journal_column(osv.osv):
559 def _col_get(self, cr, user, context=None):
561 cols = self.pool.get('account.move.line')._columns
563 if col in ('period_id', 'journal_id'):
565 result.append( (col, cols[col].string) )
569 _name = "account.journal.column"
570 _description = "Journal Column"
572 'name': fields.char('Column Name', size=64, required=True),
573 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
574 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
575 'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column."),
576 'required': fields.boolean('Required'),
577 'readonly': fields.boolean('Readonly'),
581 account_journal_column()
583 class account_journal(osv.osv):
584 _name = "account.journal"
585 _description = "Journal"
587 'name': fields.char('Journal Name', size=64, required=True, translate=True),
588 'code': fields.char('Code', size=16, required=True, help="The code will be used to generate the numbers of the journal entries of this journal."),
589 '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,
590 help="Select 'Sale' for Sale journal to be used at the time of making invoice."\
591 " Select 'Purchase' for Purchase Journal to be used at the time of approving purchase order."\
592 " Select 'Cash' to be used at the time of making payment."\
593 " Select 'General' for miscellaneous operations."\
594 " Select 'Opening/Closing Situation' to be used at the time of new fiscal year creation or end of year entries generation."),
595 'refund_journal': fields.boolean('Refund Journal', help='Fill this if the journal is to be used for refunds of invoices.'),
596 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
597 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
598 '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."),
599 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
600 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
601 '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."),
602 '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"),
603 '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."),
604 '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),
605 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
606 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
607 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
608 '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.'),
609 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
610 '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'),
614 'user_id': lambda self,cr,uid,context: uid,
615 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
618 ('code_company_uniq', 'unique (code, company_id)', 'The code of the journal must be unique per company !'),
619 ('name_company_uniq', 'unique (name, company_id)', 'The name of the journal must be unique per company !'),
624 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
625 journal = self.browse(cr, uid, id, context=context)
628 default = default.copy()
629 default['code'] = (journal['code'] or '') + '(copy)'
630 default['name'] = (journal['name'] or '') + '(copy)'
631 return super(account_journal, self).copy(cr, uid, id, default, context=context)
633 def write(self, cr, uid, ids, vals, context=None):
634 if 'company_id' in vals:
635 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
637 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this journal as its related record exist in Entry Lines'))
638 return super(account_journal, self).write(cr, uid, ids, vals, context=context)
640 def create_sequence(self, cr, uid, vals, context=None):
642 Create new entry sequence for every new Joural
643 @param cr: cursor to database
644 @param user: id of current user
645 @param ids: list of record ids to be process
646 @param context: context arguments, like lang, time zone
647 @return: return a result
649 seq_pool = self.pool.get('ir.sequence')
650 seq_typ_pool = self.pool.get('ir.sequence.type')
653 code = vals['code'].lower()
659 seq_typ_pool.create(cr, uid, types)
665 'prefix': code + "/%(year)s/",
667 'number_increment': 1
669 return seq_pool.create(cr, uid, seq)
671 def create(self, cr, uid, vals, context=None):
672 if not 'sequence_id' in vals or not vals['sequence_id']:
673 vals.update({'sequence_id': self.create_sequence(cr, uid, vals, context)})
674 return super(account_journal, self).create(cr, uid, vals, context)
676 def name_get(self, cr, user, ids, context=None):
678 Returns a list of tupples containing id, name.
679 result format: {[(id, name), (id, name), ...]}
681 @param cr: A database cursor
682 @param user: ID of the user currently logged in
683 @param ids: list of ids for which name should be read
684 @param context: context arguments, like lang, time zone
686 @return: Returns a list of tupples containing id, name
688 result = self.browse(cr, user, ids, context)
693 name = "%s (%s)" % (rs.name, rs.currency.name)
694 res += [(rs.id, name)]
697 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
703 if context.get('journal_type', False):
704 args += [('type','=',context.get('journal_type'))]
706 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
708 ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
710 return self.name_get(cr, user, ids, context=context)
712 def onchange_type(self, cr, uid, ids, type, currency):
713 obj_data = self.pool.get('ir.model.data')
714 user_pool = self.pool.get('res.users')
717 'sale':'account_sp_journal_view',
718 'sale_refund':'account_sp_refund_journal_view',
719 'purchase':'account_sp_journal_view',
720 'purchase_refund':'account_sp_refund_journal_view',
721 'cash':'account_journal_bank_view',
722 'bank':'account_journal_bank_view',
723 'general':'account_journal_view',
724 'situation':'account_journal_view'
729 view_id = type_map.get(type, 'general')
731 user = user_pool.browse(cr, uid, uid)
732 if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
733 view_id = 'account_journal_bank_view_multi'
735 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
736 data = obj_data.browse(cr, uid, data_id[0])
739 'centralisation':type == 'situation',
740 'view_id':data.res_id,
749 class account_fiscalyear(osv.osv):
750 _name = "account.fiscalyear"
751 _description = "Fiscal Year"
753 'name': fields.char('Fiscal Year', size=64, required=True),
754 'code': fields.char('Code', size=6, required=True),
755 'company_id': fields.many2one('res.company', 'Company', required=True),
756 'date_start': fields.date('Start Date', required=True),
757 'date_stop': fields.date('End Date', required=True),
758 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
759 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True),
763 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
765 _order = "date_start"
767 def _check_fiscal_year(self, cr, uid, ids, context=None):
768 current_fiscal_yr = self.browse(cr, uid, ids, context=context)[0]
769 obj_fiscal_ids = self.search(cr, uid, [('company_id', '=', current_fiscal_yr.company_id.id)], context=context)
770 obj_fiscal_ids.remove(ids[0])
771 data_fiscal_yr = self.browse(cr, uid, obj_fiscal_ids, context=context)
773 for old_fy in data_fiscal_yr:
774 if old_fy.company_id.id == current_fiscal_yr['company_id'].id:
775 # Condition to check if the current fiscal year falls in between any previously defined fiscal year
776 if old_fy.date_start <= current_fiscal_yr['date_start'] <= old_fy.date_stop or \
777 old_fy.date_start <= current_fiscal_yr['date_stop'] <= old_fy.date_stop:
781 def _check_duration(self,cr,uid,ids):
782 obj_fy = self.browse(cr,uid,ids[0])
783 if obj_fy.date_stop < obj_fy.date_start:
788 (_check_duration, 'Error! The duration of the Fiscal Year is invalid. ', ['date_stop']),
789 (_check_fiscal_year, 'Error! You cannot define overlapping fiscal years',['date_start', 'date_stop'])
792 def create_period3(self,cr, uid, ids, context={}):
793 return self.create_period(cr, uid, ids, context, 3)
795 def create_period(self,cr, uid, ids, context={}, interval=1):
796 for fy in self.browse(cr, uid, ids, context):
797 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
798 while ds.strftime('%Y-%m-%d')<fy.date_stop:
799 de = ds + relativedelta(months=interval, days=-1)
801 if de.strftime('%Y-%m-%d')>fy.date_stop:
802 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
804 self.pool.get('account.period').create(cr, uid, {
805 'name': ds.strftime('%m/%Y'),
806 'code': ds.strftime('%m/%Y'),
807 'date_start': ds.strftime('%Y-%m-%d'),
808 'date_stop': de.strftime('%Y-%m-%d'),
809 'fiscalyear_id': fy.id,
811 ds = ds + relativedelta(months=interval)
814 def find(self, cr, uid, dt=None, exception=True, context={}):
816 dt = time.strftime('%Y-%m-%d')
817 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
820 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
825 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
832 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
834 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
835 return self.name_get(cr, user, ids, context=context)
839 class account_period(osv.osv):
840 _name = "account.period"
841 _description = "Account period"
843 'name': fields.char('Period Name', size=64, required=True),
844 'code': fields.char('Code', size=12),
845 'special': fields.boolean('Opening/Closing Period', size=12,
846 help="These periods can overlap."),
847 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
848 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
849 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
850 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True,
851 help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
852 'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
857 _order = "date_start"
859 def _check_duration(self,cr,uid,ids,context={}):
860 obj_period=self.browse(cr,uid,ids[0])
861 if obj_period.date_stop < obj_period.date_start:
865 def _check_year_limit(self,cr,uid,ids,context={}):
866 for obj_period in self.browse(cr,uid,ids):
867 if obj_period.special:
870 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
871 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
872 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
873 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
876 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
877 for period in self.browse(cr, uid, pids):
878 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
883 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
884 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
887 def next(self, cr, uid, period, step, context={}):
888 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
893 def find(self, cr, uid, dt=None, context={}):
895 dt = time.strftime('%Y-%m-%d')
896 #CHECKME: shouldn't we check the state of the period?
897 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
899 raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create a fiscal year.')%dt)
902 def action_draft(self, cr, uid, ids, *args):
905 cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
906 cr.execute('update account_period set state=%s where id=%s', (mode, id))
909 def name_search(self, cr, user, name, args=None, operator='ilike', context={}, limit=80):
916 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
918 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
919 return self.name_get(cr, user, ids, context=context)
921 def write(self, cr, uid, ids, vals, context={}):
922 if 'company_id' in vals:
923 move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
925 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines'))
926 return super(account_period, self).write(cr, uid, ids, vals, context=context)
928 def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
929 period_from = self.browse(cr, uid, period_from_id)
930 period_date_start = period_from.date_start
931 company1_id = period_from.company_id.id
932 period_to = self.browse(cr, uid, period_to_id)
933 period_date_stop = period_to.date_stop
934 company2_id = period_to.company_id.id
935 if company1_id != company2_id:
936 raise osv.except_osv(_('Error'), _('You should have chosen periods that belongs to the same company'))
937 if period_date_start > period_date_stop:
938 raise osv.except_osv(_('Error'), _('Start period should be smaller then End period'))
939 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id)])
943 class account_journal_period(osv.osv):
944 _name = "account.journal.period"
945 _description = "Journal Period"
947 def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
948 result = {}.fromkeys(ids, 'STOCK_NEW')
949 for r in self.read(cr, uid, ids, ['state']):
951 'draft': 'STOCK_NEW',
952 'printed': 'STOCK_PRINT_PREVIEW',
953 'done': 'STOCK_DIALOG_AUTHENTICATION',
954 }.get(r['state'], 'STOCK_NEW')
958 'name': fields.char('Journal-Period Name', size=64, required=True),
959 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
960 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
961 'icon': fields.function(_icon_get, method=True, string='Icon', type='char', size=32),
962 '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."),
963 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
964 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.'),
965 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
966 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company')
969 def _check(self, cr, uid, ids, context={}):
970 for obj in self.browse(cr, uid, ids, context):
971 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))
974 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
977 def write(self, cr, uid, ids, vals, context={}):
978 self._check(cr, uid, ids, context)
979 return super(account_journal_period, self).write(cr, uid, ids, vals, context)
981 def create(self, cr, uid, vals, context={}):
982 period_id=vals.get('period_id',False)
984 period = self.pool.get('account.period').browse(cr, uid,period_id)
985 vals['state']=period.state
986 return super(account_journal_period, self).create(cr, uid, vals, context)
988 def unlink(self, cr, uid, ids, context={}):
989 self._check(cr, uid, ids, context)
990 return super(account_journal_period, self).unlink(cr, uid, ids, context)
998 account_journal_period()
1000 class account_fiscalyear(osv.osv):
1001 _inherit = "account.fiscalyear"
1002 _description = "Fiscal Year"
1004 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1007 def copy(self, cr, uid, id, default={}, context=None):
1010 'end_journal_period_id': False
1012 return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1014 account_fiscalyear()
1015 #----------------------------------------------------------
1017 #----------------------------------------------------------
1018 class account_move(osv.osv):
1019 _name = "account.move"
1020 _description = "Account Entry"
1023 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1025 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1026 result format: {[(id, name), (id, name), ...]}
1028 @param cr: A database cursor
1029 @param user: ID of the user currently logged in
1030 @param name: name to search
1031 @param args: other arguments
1032 @param operator: default operator is 'ilike', it can be changed
1033 @param context: context arguments, like lang, time zone
1034 @param limit: Returns first 'n' ids of complete result, default is 80.
1036 @return: Returns a list of tuples containing id and name
1045 ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1047 if not ids and name and type(name) == int:
1048 ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1051 ids += self.search(cr, user, args, limit=limit, context=context)
1053 return self.name_get(cr, user, ids, context=context)
1055 def name_get(self, cursor, user, ids, context=None):
1056 if isinstance(ids, (int, long)):
1061 data_move = self.pool.get('account.move').browse(cursor,user,ids)
1062 for move in data_move:
1063 if move.state=='draft':
1064 name = '*' + str(move.id)
1067 res.append((move.id, name))
1070 def _get_period(self, cr, uid, context):
1071 periods = self.pool.get('account.period').find(cr, uid)
1076 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1077 if not ids: return {}
1078 cr.execute( 'SELECT move_id, SUM(debit) '\
1079 'FROM account_move_line '\
1080 'WHERE move_id IN %s '\
1081 'GROUP BY move_id', (tuple(ids),))
1082 result = dict(cr.fetchall())
1084 result.setdefault(id, 0.0)
1087 def _search_amount(self, cr, uid, obj, name, args, context):
1091 if isinstance(cond[2],(list,tuple)):
1092 if cond[1] in ['in','not in']:
1093 amount = tuple(cond[2])
1097 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1100 cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1101 res_ids = set(id[0] for id in cr.fetchall())
1102 ids = ids and (ids & res_ids) or res_ids
1104 return [('id','in',tuple(ids))]
1106 return [('id', '=', '0')]
1109 'name': fields.char('Number', size=64, required=True),
1110 'ref': fields.char('Reference', size=64),
1111 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1112 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1113 'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'State', required=True, readonly=True,
1114 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.'),
1115 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1116 '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.'),
1117 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1118 'amount': fields.function(_amount_compute, method=True, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1119 'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}),
1120 'narration':fields.text('Narration'),
1121 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1126 'period_id': _get_period,
1127 'date': lambda *a: time.strftime('%Y-%m-%d'),
1128 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1131 def _check_centralisation(self, cursor, user, ids):
1132 for move in self.browse(cursor, user, ids):
1133 if move.journal_id.centralisation:
1134 move_ids = self.search(cursor, user, [
1135 ('period_id', '=', move.period_id.id),
1136 ('journal_id', '=', move.journal_id.id),
1138 if len(move_ids) > 1:
1142 def _check_period_journal(self, cursor, user, ids):
1143 for move in self.browse(cursor, user, ids):
1144 for line in move.line_id:
1145 if line.period_id.id != move.period_id.id:
1147 if line.journal_id.id != move.journal_id.id:
1152 (_check_centralisation,
1153 'You cannot create more than one move per period on centralized journal',
1155 (_check_period_journal,
1156 'You cannot create entries on different periods/journals in the same move',
1160 def post(self, cr, uid, ids, context=None):
1163 invoice = context.get('invoice', False)
1164 valid_moves = self.validate(cr, uid, ids, context)
1167 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" !'))
1168 obj_sequence = self.pool.get('ir.sequence')
1169 for move in self.browse(cr, uid, valid_moves):
1172 journal = move.journal_id
1174 if invoice and invoice.internal_number:
1175 new_name = invoice.internal_number
1177 if journal.sequence_id:
1178 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1179 new_name = obj_sequence.get_id(cr, uid, journal.sequence_id.id, context=c)
1181 raise osv.except_osv(_('Error'), _('No sequence defined on the journal !'))
1184 self.write(cr, uid, [move.id], {'name':new_name})
1186 cr.execute('UPDATE account_move '\
1189 ('posted', tuple(valid_moves),))
1193 def button_validate(self, cursor, user, ids, context=None):
1194 for move in self.browse(cursor, user, ids):
1196 for line in move.line_id:
1197 account = line.account_id
1200 account = account.parent_id
1203 elif top<>account2.id:
1204 raise osv.except_osv(_('Error !'), _('You cannot validate a Journal Entry unless all journal items are in same chart of accounts !'))
1205 return self.post(cursor, user, ids, context=context)
1207 def button_cancel(self, cr, uid, ids, context={}):
1208 for line in self.browse(cr, uid, ids, context):
1209 if not line.journal_id.update_posted:
1210 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.'))
1212 cr.execute('UPDATE account_move '\
1214 'WHERE id IN %s', ('draft', tuple(ids),))
1217 def write(self, cr, uid, ids, vals, context={}):
1219 c['novalidate'] = True
1220 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
1221 self.validate(cr, uid, ids, context)
1225 # TODO: Check if period is closed !
1227 def create(self, cr, uid, vals, context=None):
1228 context = context or {}
1229 if 'line_id' in vals and context.get('copy'):
1230 for l in vals['line_id']:
1233 'reconcile_id':False,
1234 'reconcil_partial_id':False,
1235 'analytic_lines':False,
1239 'account_tax_id':False,
1242 if 'journal_id' in vals and vals.get('journal_id', False):
1243 for l in vals['line_id']:
1245 l[2]['journal_id'] = vals['journal_id']
1246 context['journal_id'] = vals['journal_id']
1247 if 'period_id' in vals:
1248 for l in vals['line_id']:
1250 l[2]['period_id'] = vals['period_id']
1251 context['period_id'] = vals['period_id']
1253 default_period = self._get_period(cr, uid, context)
1254 for l in vals['line_id']:
1256 l[2]['period_id'] = default_period
1257 context['period_id'] = default_period
1259 if 'line_id' in vals:
1261 c['novalidate'] = True
1262 result = super(account_move, self).create(cr, uid, vals, c)
1263 self.validate(cr, uid, [result], context)
1265 result = super(account_move, self).create(cr, uid, vals, context)
1268 def copy(self, cr, uid, id, default={}, context={}):
1269 context = context or {}
1277 return super(account_move, self).copy(cr, uid, id, default, context)
1279 def unlink(self, cr, uid, ids, context=None, check=True):
1280 context = context or {}
1282 obj_move_line = self.pool.get('account.move.line')
1283 for move in self.browse(cr, uid, ids, context):
1284 if move['state'] != 'draft':
1285 raise osv.except_osv(_('UserError'),
1286 _('You can not delete posted movement: "%s"!') % \
1288 line_ids = map(lambda x: x.id, move.line_id)
1289 context['journal_id'] = move.journal_id.id
1290 context['period_id'] = move.period_id.id
1291 obj_move_line._update_check(cr, uid, line_ids, context)
1292 obj_move_line.unlink(cr, uid, line_ids, context=context)
1293 toremove.append(move.id)
1294 result = super(account_move, self).unlink(cr, uid, toremove, context)
1297 def _compute_balance(self, cr, uid, id, context={}):
1298 move = self.browse(cr, uid, [id])[0]
1300 for line in move.line_id:
1301 amount+= (line.debit - line.credit)
1304 def _centralise(self, cr, uid, move, mode, context=None):
1305 assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1310 account_id = move.journal_id.default_debit_account_id.id
1313 raise osv.except_osv(_('UserError'),
1314 _('There is no default default debit account defined \n' \
1315 'on journal "%s"') % move.journal_id.name)
1317 account_id = move.journal_id.default_credit_account_id.id
1320 raise osv.except_osv(_('UserError'),
1321 _('There is no default default credit account defined \n' \
1322 'on journal "%s"') % move.journal_id.name)
1324 # find the first line of this move with the current mode
1325 # or create it if it doesn't exist
1326 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1331 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1332 line_id = self.pool.get('account.move.line').create(cr, uid, {
1333 'name': _(mode.capitalize()+' Centralisation'),
1334 'centralisation': mode,
1335 'account_id': account_id,
1337 'journal_id': move.journal_id.id,
1338 'period_id': move.period_id.id,
1339 'date': move.period_id.date_stop,
1344 # find the first line of this move with the other mode
1345 # so that we can exclude it from our calculation
1346 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1353 cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1354 result = cr.fetchone()[0] or 0.0
1355 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1359 # Validate a balanced move. If it is a centralised journal, create a move.
1361 def validate(self, cr, uid, ids, context={}):
1362 if context and ('__last_update' in context):
1363 del context['__last_update']
1365 valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1366 obj_analytic_line = self.pool.get('account.analytic.line')
1367 obj_move_line = self.pool.get('account.move.line')
1368 for move in self.browse(cr, uid, ids, context):
1369 # Unlink old analytic lines on move_lines
1370 for obj_line in move.line_id:
1371 for obj in obj_line.analytic_lines:
1372 obj_analytic_line.unlink(cr,uid,obj.id)
1374 journal = move.journal_id
1379 for line in move.line_id:
1380 amount += line.debit - line.credit
1381 line_ids.append(line.id)
1382 if line.state=='draft':
1383 line_draft_ids.append(line.id)
1386 company_id = line.account_id.company_id.id
1387 if not company_id == line.account_id.company_id.id:
1388 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1390 if line.account_id.currency_id and line.currency_id:
1391 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):
1392 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))
1394 if abs(amount) < 10 ** -4:
1395 # If the move is balanced
1396 # Add to the list of valid moves
1397 # (analytic lines will be created later for valid moves)
1398 valid_moves.append(move)
1400 # Check whether the move lines are confirmed
1402 if not line_draft_ids:
1404 # Update the move lines (set them as valid)
1406 obj_move_line.write(cr, uid, line_draft_ids, {
1407 'journal_id': move.journal_id.id,
1408 'period_id': move.period_id.id,
1410 }, context, check=False)
1415 if journal.type in ('purchase','sale'):
1416 for line in move.line_id:
1418 key = (line.account_id.id, line.tax_code_id.id)
1420 code = account2[key][0]
1421 amount = account2[key][1] * (line.debit + line.credit)
1422 elif line.account_id.id in account:
1423 code = account[line.account_id.id][0]
1424 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1425 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1426 obj_move_line.write(cr, uid, [line.id], {
1427 'tax_code_id': code,
1428 'tax_amount': amount
1429 }, context, check=False)
1430 elif journal.centralisation:
1431 # If the move is not balanced, it must be centralised...
1433 # Add to the list of valid moves
1434 # (analytic lines will be created later for valid moves)
1435 valid_moves.append(move)
1438 # Update the move lines (set them as valid)
1440 self._centralise(cr, uid, move, 'debit', context=context)
1441 self._centralise(cr, uid, move, 'credit', context=context)
1442 obj_move_line.write(cr, uid, line_draft_ids, {
1444 }, context, check=False)
1446 # We can't validate it (it's unbalanced)
1447 # Setting the lines as draft
1448 obj_move_line.write(cr, uid, line_ids, {
1449 'journal_id': move.journal_id.id,
1450 'period_id': move.period_id.id,
1452 }, context, check=False)
1453 # Create analytic lines for the valid moves
1454 for record in valid_moves:
1455 obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1457 valid_moves = [move.id for move in valid_moves]
1458 return len(valid_moves) > 0 and valid_moves or False
1462 class account_move_reconcile(osv.osv):
1463 _name = "account.move.reconcile"
1464 _description = "Account Reconciliation"
1466 'name': fields.char('Name', size=64, required=True),
1467 'type': fields.char('Type', size=16, required=True),
1468 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1469 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1470 'create_date': fields.date('Creation date', readonly=True),
1473 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1475 def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1477 for rec in self.browse(cr, uid, ids, context):
1478 for line in rec.line_partial_ids:
1479 total += (line.debit or 0.0) - (line.credit or 0.0)
1481 self.pool.get('account.move.line').write(cr, uid,
1482 map(lambda x: x.id, rec.line_partial_ids),
1483 {'reconcile_id': rec.id }
1487 def name_get(self, cr, uid, ids, context=None):
1491 for r in self.browse(cr, uid, ids, context):
1492 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1494 name = '%s (%.2f)' % (r.name, total)
1495 result.append((r.id,name))
1497 result.append((r.id,r.name))
1501 account_move_reconcile()
1503 #----------------------------------------------------------
1505 #----------------------------------------------------------
1508 child_depend: la taxe depend des taxes filles
1510 class account_tax_code(osv.osv):
1512 A code for the tax object.
1514 This code is used for some tax declarations.
1516 def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1517 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1518 if context.get('based_on', 'invoices') == 'payments':
1519 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1520 FROM account_move_line AS line, \
1521 account_move AS move \
1522 LEFT JOIN account_invoice invoice ON \
1523 (invoice.move_id = move.id) \
1524 WHERE line.tax_code_id IN %s '+where+' \
1525 AND move.id = line.move_id \
1526 AND ((invoice.state = \'paid\') \
1527 OR (invoice.id IS NULL)) \
1528 GROUP BY line.tax_code_id',
1529 (parent_ids,) + where_params)
1531 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1532 FROM account_move_line AS line, \
1533 account_move AS move \
1534 WHERE line.tax_code_id IN %s '+where+' \
1535 AND move.id = line.move_id \
1536 GROUP BY line.tax_code_id',
1537 (parent_ids,) + where_params)
1538 res=dict(cr.fetchall())
1539 obj_precision = self.pool.get('decimal.precision')
1540 for record in self.browse(cr, uid, ids, context):
1541 def _rec_get(record):
1542 amount = res.get(record.id, 0.0)
1543 for rec in record.child_ids:
1544 amount += _rec_get(rec) * rec.sign
1546 res[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1549 def _sum_year(self, cr, uid, ids, name, args, context=None):
1552 move_state = ('posted', )
1553 if context.get('state', 'all') == 'all':
1554 move_state = ('draft', 'posted', )
1555 if context.get('fiscalyear_id', False):
1556 fiscalyear_id = context['fiscalyear_id']
1558 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1562 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1564 where = ' AND line.period_id IN %s AND move.state IN %s '
1565 where_params = (tuple(pids), move_state)
1566 return self._sum(cr, uid, ids, name, args, context,
1567 where=where, where_params=where_params)
1569 def _sum_period(self, cr, uid, ids, name, args, context):
1572 move_state = ('posted', )
1573 if context.get('state', False) == 'all':
1574 move_state = ('draft', 'posted', )
1575 if context.get('period_id', False):
1576 period_id = context['period_id']
1578 period_id = self.pool.get('account.period').find(cr, uid)
1580 return dict.fromkeys(ids, 0.0)
1581 period_id = period_id[0]
1582 return self._sum(cr, uid, ids, name, args, context,
1583 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1585 _name = 'account.tax.code'
1586 _description = 'Tax Code'
1589 'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1590 'code': fields.char('Case Code', size=64),
1591 'info': fields.text('Description'),
1592 'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1593 'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1594 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1595 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1596 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1597 'company_id': fields.many2one('res.company', 'Company', required=True),
1598 '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.'),
1599 '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"),
1602 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1607 ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1608 return self.name_get(cr, user, ids, context)
1611 def name_get(self, cr, uid, ids, context=None):
1612 if isinstance(ids, (int, long)):
1616 if isinstance(ids, (int, long)):
1618 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1619 return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1622 def _default_company(self, cr, uid, context={}):
1623 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1625 return user.company_id.id
1626 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1628 'company_id': _default_company,
1630 'notprintable': False,
1633 def copy(self, cr, uid, id, default=None, context=None):
1636 default = default.copy()
1637 default.update({'line_ids': []})
1638 return super(account_tax_code, self).copy(cr, uid, id, default, context)
1640 _check_recursion = check_cycle
1642 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1647 class account_tax(osv.osv):
1651 Type: percent, fixed, none, code
1652 PERCENT: tax = price * amount
1653 FIXED: tax = price + amount
1655 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1656 return result in the context
1657 Ex: result=round(price_unit*0.21,4)
1660 def get_precision_tax():
1661 def change_digit_tax(cr):
1662 res = pooler.get_pool(cr.dbname).get('decimal.precision').precision_get(cr, 1, 'Account')
1664 return change_digit_tax
1666 _name = 'account.tax'
1667 _description = 'Tax'
1669 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1670 '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."),
1671 'amount': fields.float('Amount', required=True, digits_compute=get_precision_tax(), help="For taxes of type percentage, enter % ratio between 0-1."),
1672 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the tax without removing it."),
1673 'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1674 help="The computation method for the tax amount."),
1675 'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1676 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1677 '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."),
1678 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1679 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1680 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1681 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1682 '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."),
1683 'python_compute':fields.text('Python Code'),
1684 'python_compute_inv':fields.text('Python Code (reverse)'),
1685 'python_applicable':fields.text('Python Code'),
1688 # Fields used for the VAT declaration
1690 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1691 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1692 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1693 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1695 # Same fields for refund invoices
1697 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1698 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1699 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1700 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1701 '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"),
1702 'company_id': fields.many2one('res.company', 'Company', required=True),
1703 'description': fields.char('Tax Code',size=32),
1704 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1705 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1709 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1711 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1712 result format: {[(id, name), (id, name), ...]}
1714 @param cr: A database cursor
1715 @param user: ID of the user currently logged in
1716 @param name: name to search
1717 @param args: other arguments
1718 @param operator: default operator is 'ilike', it can be changed
1719 @param context: context arguments, like lang, time zone
1720 @param limit: Returns first 'n' ids of complete result, default is 80.
1722 @return: Returns a list of tupples containing id and name
1729 ids = self.search(cr, user, args, limit=limit, context=context)
1730 return self.name_get(cr, user, ids, context=context)
1732 def write(self, cr, uid, ids, vals, context=None):
1733 if vals.get('type', False) and vals['type'] in ('none', 'code'):
1734 vals.update({'amount': 0.0})
1735 return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1737 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1738 journal_pool = self.pool.get('account.journal')
1740 if context and context.has_key('type'):
1741 if context.get('type') in ('out_invoice','out_refund'):
1742 args += [('type_tax_use','in',['sale','all'])]
1743 elif context.get('type') in ('in_invoice','in_refund'):
1744 args += [('type_tax_use','in',['purchase','all'])]
1746 if context and context.has_key('journal_id'):
1747 journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1748 if journal.type in ('sale', 'purchase'):
1749 args += [('type_tax_use','in',[journal.type,'all'])]
1751 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1753 def name_get(self, cr, uid, ids, context=None):
1757 for record in self.read(cr, uid, ids, ['description','name'], context=context):
1758 name = record['description'] and record['description'] or record['name']
1759 res.append((record['id'],name ))
1762 def _default_company(self, cr, uid, context=None):
1763 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1765 return user.company_id.id
1766 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1769 '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''',
1770 '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''',
1771 'applicable_type': 'true',
1776 'type_tax_use': 'all',
1782 'include_base_amount': False,
1783 'company_id': _default_company,
1787 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1789 obj_partener_address = self.pool.get('res.partner.address')
1791 if tax.applicable_type=='code':
1792 localdict = {'price_unit':price_unit, 'address':obj_partener_address.browse(cr, uid, address_id), 'product':product, 'partner':partner}
1793 exec tax.python_applicable in localdict
1794 if localdict.get('result', False):
1800 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1801 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1803 cur_price_unit=price_unit
1804 obj_partener_address = self.pool.get('res.partner.address')
1806 # we compute the amount for the current tax object and append it to the result
1808 data = {'id':tax.id,
1809 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1810 'account_collected_id':tax.account_collected_id.id,
1811 'account_paid_id':tax.account_paid_id.id,
1812 'base_code_id': tax.base_code_id.id,
1813 'ref_base_code_id': tax.ref_base_code_id.id,
1814 'sequence': tax.sequence,
1815 'base_sign': tax.base_sign,
1816 'tax_sign': tax.tax_sign,
1817 'ref_base_sign': tax.ref_base_sign,
1818 'ref_tax_sign': tax.ref_tax_sign,
1819 'price_unit': cur_price_unit,
1820 'tax_code_id': tax.tax_code_id.id,
1821 'ref_tax_code_id': tax.ref_tax_code_id.id,
1824 if tax.type=='percent':
1825 amount = cur_price_unit * tax.amount
1826 data['amount'] = amount
1828 elif tax.type=='fixed':
1829 data['amount'] = tax.amount
1830 data['tax_amount']=quantity
1831 # data['amount'] = quantity
1832 elif tax.type=='code':
1833 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1834 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1835 exec tax.python_compute in localdict
1836 amount = localdict['result']
1837 data['amount'] = amount
1838 elif tax.type=='balance':
1839 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1840 data['balance'] = cur_price_unit
1842 amount2 = data.get('amount', 0.0)
1844 if tax.child_depend:
1847 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1848 res.extend(child_tax)
1849 if tax.child_depend:
1851 for name in ('base','ref_base'):
1852 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1853 r[name+'_code_id'] = latest[name+'_code_id']
1854 r[name+'_sign'] = latest[name+'_sign']
1855 r['price_unit'] = latest['price_unit']
1856 latest[name+'_code_id'] = False
1857 for name in ('tax','ref_tax'):
1858 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1859 r[name+'_code_id'] = latest[name+'_code_id']
1860 r[name+'_sign'] = latest[name+'_sign']
1861 r['amount'] = data['amount']
1862 latest[name+'_code_id'] = False
1863 if tax.include_base_amount:
1864 cur_price_unit+=amount2
1867 def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1870 'total': 0.0, # Total without taxes
1871 'total_included: 0.0, # Total with taxes
1872 'taxes': [] # List of taxes, see compute for the format
1875 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1876 totalin = totalex = round(price_unit * quantity, precision)
1880 if tax.price_include:
1884 tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
1886 totalex -= r.get('amount', 0.0)
1889 totlex_qty=totalex/quantity
1892 tex = self._compute(cr, uid, tex, totlex_qty, quantity, address_id=address_id, product=product, partner=partner)
1894 totalin += r.get('amount', 0.0)
1897 'total_included': totalin,
1901 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1902 logger = netsvc.Logger()
1903 logger.notifyChannel("warning", netsvc.LOG_WARNING,
1904 "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
1905 return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
1907 def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1909 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1913 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1914 one tax for each tax id in IDS and their childs
1916 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1918 precision_pool = self.pool.get('decimal.precision')
1920 if r.get('balance',False):
1921 r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
1923 r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
1924 total += r['amount']
1927 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1928 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1929 obj_partener_address = self.pool.get('res.partner.address')
1932 cur_price_unit = price_unit
1934 tax_parent_tot = 0.0
1936 if (tax.type=='percent') and not tax.include_base_amount:
1937 tax_parent_tot += tax.amount
1940 if (tax.type=='fixed') and not tax.include_base_amount:
1941 cur_price_unit -= tax.amount
1944 if tax.type=='percent':
1945 if tax.include_base_amount:
1946 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1948 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1950 elif tax.type=='fixed':
1953 elif tax.type=='code':
1954 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1955 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1956 exec tax.python_compute_inv in localdict
1957 amount = localdict['result']
1958 elif tax.type=='balance':
1959 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1961 if tax.include_base_amount:
1962 cur_price_unit -= amount
1971 'account_collected_id': tax.account_collected_id.id,
1972 'account_paid_id': tax.account_paid_id.id,
1973 'base_code_id': tax.base_code_id.id,
1974 'ref_base_code_id': tax.ref_base_code_id.id,
1975 'sequence': tax.sequence,
1976 'base_sign': tax.base_sign,
1977 'tax_sign': tax.tax_sign,
1978 'ref_base_sign': tax.ref_base_sign,
1979 'ref_tax_sign': tax.ref_tax_sign,
1980 'price_unit': cur_price_unit,
1981 'tax_code_id': tax.tax_code_id.id,
1982 'ref_tax_code_id': tax.ref_tax_code_id.id,
1985 if tax.child_depend:
1989 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1990 res.extend(parent_tax)
1995 total += r['amount']
1997 r['price_unit'] -= total
2001 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
2003 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
2004 Price Unit is a VAT included price
2008 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
2009 one tax for each tax id in IDS and their childs
2011 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
2013 obj_precision = self.pool.get('decimal.precision')
2015 prec = obj_precision.precision_get(cr, uid, 'Account')
2016 if r.get('balance',False):
2017 r['amount'] = round(r['balance'] * quantity, prec) - total
2019 r['amount'] = round(r['amount'] * quantity, prec)
2020 total += r['amount']
2024 # ---------------------------------------------------------
2025 # Account Entries Models
2026 # ---------------------------------------------------------
2028 class account_model(osv.osv):
2029 _name = "account.model"
2030 _description = "Account Model"
2032 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2033 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2034 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2035 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2036 'legend': fields.text('Legend', readonly=True, size=100),
2040 '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'),
2042 def generate(self, cr, uid, ids, datas={}, context=None):
2045 account_move_obj = self.pool.get('account.move')
2046 account_move_line_obj = self.pool.get('account.move.line')
2047 pt_obj = self.pool.get('account.payment.term')
2052 if datas.get('date', False):
2053 context.update({'date': datas['date']})
2055 period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
2057 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
2058 period_id = period_id[0]
2060 for model in self.browse(cr, uid, ids, context):
2061 entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')}
2062 move_id = account_move_obj.create(cr, uid, {
2063 'ref': entry['name'],
2064 'period_id': period_id,
2065 'journal_id': model.journal_id.id,
2066 'date': context.get('date',time.strftime('%Y-%m-%d'))
2068 move_ids.append(move_id)
2069 for line in model.lines_id:
2070 analytic_account_id = False
2071 if line.analytic_account_id:
2072 if not model.journal_id.analytic_journal_id:
2073 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2074 analytic_account_id = line.analytic_account_id.id
2077 'journal_id': model.journal_id.id,
2078 'period_id': period_id,
2079 'analytic_account_id': analytic_account_id
2082 date_maturity = time.strftime('%Y-%m-%d')
2083 if line.date_maturity == 'partner':
2084 if not line.partner_id:
2085 raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term!" \
2086 "\nPlease define partner on it!")%(line.name, model.name))
2087 if line.partner_id.property_payment_term:
2088 payment_term_id = line.partner_id.property_payment_term.id
2089 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2091 pterm_list = [l[0] for l in pterm_list]
2093 date_maturity = pterm_list[-1]
2097 'quantity': line.quantity,
2098 'debit': line.debit,
2099 'credit': line.credit,
2100 'account_id': line.account_id.id,
2102 'partner_id': line.partner_id.id,
2103 'date': context.get('date',time.strftime('%Y-%m-%d')),
2104 'date_maturity': date_maturity
2107 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
2108 account_move_line_obj.create(cr, uid, val, context=c)
2114 class account_model_line(osv.osv):
2115 _name = "account.model.line"
2116 _description = "Account Model Entries"
2118 'name': fields.char('Name', size=64, required=True),
2119 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
2120 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries"),
2121 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2122 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2123 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2124 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2125 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2126 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2127 'currency_id': fields.many2one('res.currency', 'Currency'),
2128 'partner_id': fields.many2one('res.partner', 'Partner'),
2129 '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."),
2132 _sql_constraints = [
2133 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
2134 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
2136 account_model_line()
2138 # ---------------------------------------------------------
2139 # Account Subscription
2140 # ---------------------------------------------------------
2143 class account_subscription(osv.osv):
2144 _name = "account.subscription"
2145 _description = "Account Subscription"
2147 'name': fields.char('Name', size=64, required=True),
2148 'ref': fields.char('Reference', size=16),
2149 'model_id': fields.many2one('account.model', 'Model', required=True),
2151 'date_start': fields.date('Start Date', required=True),
2152 'period_total': fields.integer('Number of Periods', required=True),
2153 'period_nbr': fields.integer('Period', required=True),
2154 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2155 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
2157 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2160 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
2161 'period_type': 'month',
2166 def state_draft(self, cr, uid, ids, context={}):
2167 self.write(cr, uid, ids, {'state':'draft'})
2170 def check(self, cr, uid, ids, context={}):
2172 for sub in self.browse(cr, uid, ids, context):
2174 for line in sub.lines_id:
2175 if not line.move_id.id:
2179 todone.append(sub.id)
2181 self.write(cr, uid, todone, {'state':'done'})
2184 def remove_line(self, cr, uid, ids, context={}):
2186 for sub in self.browse(cr, uid, ids, context):
2187 for line in sub.lines_id:
2188 if not line.move_id.id:
2189 toremove.append(line.id)
2191 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2192 self.write(cr, uid, ids, {'state':'draft'})
2195 def compute(self, cr, uid, ids, context={}):
2196 for sub in self.browse(cr, uid, ids, context):
2198 for i in range(sub.period_total):
2199 self.pool.get('account.subscription.line').create(cr, uid, {
2201 'subscription_id': sub.id,
2203 if sub.period_type=='day':
2204 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2205 if sub.period_type=='month':
2206 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2207 if sub.period_type=='year':
2208 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2209 self.write(cr, uid, ids, {'state':'running'})
2211 account_subscription()
2213 class account_subscription_line(osv.osv):
2214 _name = "account.subscription.line"
2215 _description = "Account Subscription Line"
2217 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2218 'date': fields.date('Date', required=True),
2219 'move_id': fields.many2one('account.move', 'Entry'),
2222 def move_create(self, cr, uid, ids, context=None):
2225 obj_model = self.pool.get('account.model')
2226 for line in self.browse(cr, uid, ids, context=context):
2230 move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2231 tocheck[line.subscription_id.id] = True
2232 self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2233 all_moves.extend(move_ids)
2235 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2239 account_subscription_line()
2241 # ---------------------------------------------------------------
2242 # Account Templates: Account, Tax, Tax Code and chart. + Wizard
2243 # ---------------------------------------------------------------
2245 class account_tax_template(osv.osv):
2246 _name = 'account.tax.template'
2247 account_tax_template()
2249 class account_account_template(osv.osv):
2251 _name = "account.account.template"
2252 _description ='Templates for Accounts'
2255 'name': fields.char('Name', size=128, required=True, select=True),
2256 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2257 'code': fields.char('Code', size=64),
2258 'type': fields.selection([
2259 ('receivable','Receivable'),
2260 ('payable','Payable'),
2262 ('consolidation','Consolidation'),
2263 ('liquidity','Liquidity'),
2264 ('other','Regular'),
2265 ('closed','Closed'),
2266 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2267 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2268 "can have children accounts for multi-company consolidations, payable/receivable are for "\
2269 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2270 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2271 help="These types are defined according to your country. The type contains more information "\
2272 "about the account and its specificities."),
2273 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2274 'shortcut': fields.char('Shortcut', size=12),
2275 'note': fields.text('Note'),
2276 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2277 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2278 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2279 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2288 _check_recursion = check_cycle
2290 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
2294 def name_get(self, cr, uid, ids, context={}):
2297 reads = self.read(cr, uid, ids, ['name','code'], context)
2299 for record in reads:
2300 name = record['name']
2302 name = record['code']+' '+name
2303 res.append((record['id'],name ))
2306 account_account_template()
2308 class account_add_tmpl_wizard(osv.osv_memory):
2309 """Add one more account from the template.
2311 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2312 _name = 'account.addtmpl.wizard'
2314 def _get_def_cparent(self, cr, uid, context):
2315 acc_obj=self.pool.get('account.account')
2316 tmpl_obj=self.pool.get('account.account.template')
2317 tids=tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2318 if not tids or not tids[0]['parent_id']:
2320 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2322 if not ptids or not ptids[0]['code']:
2323 raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
2324 res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2326 return res and res[0] or False
2329 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2332 'cparent_id': _get_def_cparent,
2335 def action_create(self,cr,uid,ids,context=None):
2336 acc_obj = self.pool.get('account.account')
2337 tmpl_obj = self.pool.get('account.account.template')
2338 data = self.read(cr, uid, ids)
2339 company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2340 account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2342 'name': account_template.name,
2343 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2344 'code': account_template.code,
2345 'type': account_template.type,
2346 'user_type': account_template.user_type and account_template.user_type.id or False,
2347 'reconcile': account_template.reconcile,
2348 'shortcut': account_template.shortcut,
2349 'note': account_template.note,
2350 'parent_id': data[0]['cparent_id'],
2351 'company_id': company_id,
2353 acc_obj.create(cr, uid, vals)
2354 return {'type':'state', 'state': 'end' }
2356 def action_cancel(self, cr, uid, ids, context=None):
2357 return { 'type': 'state', 'state': 'end' }
2359 account_add_tmpl_wizard()
2361 class account_tax_code_template(osv.osv):
2363 _name = 'account.tax.code.template'
2364 _description = 'Tax Code Template'
2368 'name': fields.char('Tax Case Name', size=64, required=True),
2369 'code': fields.char('Case Code', size=64),
2370 'info': fields.text('Description'),
2371 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2372 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2373 'sign': fields.float('Sign For Parent', required=True),
2374 '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"),
2379 'notprintable': False,
2382 def name_get(self, cr, uid, ids, context=None):
2385 if isinstance(ids, (int, long)):
2387 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2388 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2391 _check_recursion = check_cycle
2393 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2395 _order = 'code,name'
2396 account_tax_code_template()
2399 class account_chart_template(osv.osv):
2400 _name="account.chart.template"
2401 _description= "Templates for Account Chart"
2404 'name': fields.char('Name', size=64, required=True),
2405 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
2406 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
2407 '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'),
2408 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
2409 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2410 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2411 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2412 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2413 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2414 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2415 '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'),
2418 account_chart_template()
2420 class account_tax_template(osv.osv):
2422 _name = 'account.tax.template'
2423 _description = 'Templates for Taxes'
2426 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2427 'name': fields.char('Tax Name', size=64, required=True),
2428 '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."),
2429 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2430 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
2431 '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."),
2432 '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."),
2433 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2434 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2435 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2436 '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."),
2437 'python_compute':fields.text('Python Code'),
2438 'python_compute_inv':fields.text('Python Code (reverse)'),
2439 'python_applicable':fields.text('Python Code'),
2442 # Fields used for the VAT declaration
2444 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2445 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2446 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2447 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2449 # Same fields for refund invoices
2451 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2452 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2453 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2454 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2455 '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."),
2456 'description': fields.char('Internal Name', size=32),
2457 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,)
2460 def name_get(self, cr, uid, ids, context={}):
2464 for record in self.read(cr, uid, ids, ['description','name'], context):
2465 name = record['description'] and record['description'] or record['name']
2466 res.append((record['id'],name ))
2469 def _default_company(self, cr, uid, context={}):
2470 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2472 return user.company_id.id
2473 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2476 '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''',
2477 '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''',
2478 'applicable_type': 'true',
2486 'include_base_amount': False,
2487 'type_tax_use': 'all',
2492 account_tax_template()
2494 # Fiscal Position Templates
2496 class account_fiscal_position_template(osv.osv):
2497 _name = 'account.fiscal.position.template'
2498 _description = 'Template for Fiscal Position'
2501 'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
2502 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2503 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2504 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2507 account_fiscal_position_template()
2509 class account_fiscal_position_tax_template(osv.osv):
2510 _name = 'account.fiscal.position.tax.template'
2511 _description = 'Template Tax Fiscal Position'
2512 _rec_name = 'position_id'
2515 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2516 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2517 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2520 account_fiscal_position_tax_template()
2522 class account_fiscal_position_account_template(osv.osv):
2523 _name = 'account.fiscal.position.account.template'
2524 _description = 'Template Account Fiscal Mapping'
2525 _rec_name = 'position_id'
2527 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2528 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2529 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2532 account_fiscal_position_account_template()
2534 # Multi charts of Accounts wizard
2536 class wizard_multi_charts_accounts(osv.osv_memory):
2538 Create a new account chart for a company.
2541 * an account chart template
2542 * a number of digits for formatting code of non-view accounts
2543 * a list of bank accounts owned by the company
2545 * generates all accounts from the template and assigns them to the right company
2546 * generates all taxes and tax codes, changing account assignations
2547 * generates all accounting properties and assigns them correctly
2549 _name='wizard.multi.charts.accounts'
2550 _inherit = 'res.config'
2553 'company_id':fields.many2one('res.company', 'Company', required=True),
2554 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2555 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts', required=True),
2556 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2557 '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."),
2558 "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
2559 "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
2561 def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
2564 res['value']["sale_tax"] = False
2565 res['value']["purchase_tax"] = False
2566 if chart_template_id:
2567 sale_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2568 , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence")
2569 purchase_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2570 , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence")
2571 res['value']["sale_tax"] = sale_tax_ids and sale_tax_ids[0] or False
2572 res['value']["purchase_tax"] = purchase_tax_ids and purchase_tax_ids[0] or False
2575 def _get_chart(self, cr, uid, context={}):
2576 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2581 def _get_default_accounts(self, cr, uid, context=None):
2582 accounts = [{'acc_name':'Current','account_type':'bank'},
2583 {'acc_name':'Deposit','account_type':'bank'},
2584 {'acc_name':'Cash','account_type':'cash'}]
2588 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, [uid], c)[0].company_id.id,
2589 'chart_template_id': _get_chart,
2590 'bank_accounts_id': _get_default_accounts,
2595 def execute(self, cr, uid, ids, context=None):
2596 obj_multi = self.browse(cr, uid, ids[0])
2597 obj_acc = self.pool.get('account.account')
2598 obj_acc_tax = self.pool.get('account.tax')
2599 obj_journal = self.pool.get('account.journal')
2600 obj_sequence = self.pool.get('ir.sequence')
2601 obj_acc_template = self.pool.get('account.account.template')
2602 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2603 obj_fiscal_position = self.pool.get('account.fiscal.position')
2604 obj_data = self.pool.get('ir.model.data')
2605 analytic_journal_obj = self.pool.get('account.analytic.journal')
2606 obj_tax_code = self.pool.get('account.tax.code')
2608 obj_acc_root = obj_multi.chart_template_id.account_root_id
2609 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2610 company_id = obj_multi.company_id.id
2613 acc_template_ref = {}
2614 tax_template_ref = {}
2615 tax_code_template_ref = {}
2618 #create all the tax code
2619 children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2620 children_tax_code_template.sort()
2621 for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2623 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2624 'code': tax_code_template.code,
2625 'info': tax_code_template.info,
2626 '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,
2627 'company_id': company_id,
2628 'sign': tax_code_template.sign,
2630 new_tax_code = obj_tax_code.create(cr, uid, vals)
2631 #recording the new tax code to do the mapping
2632 tax_code_template_ref[tax_code_template.id] = new_tax_code
2635 tax_template_to_tax = {}
2636 for tax in obj_multi.chart_template_id.tax_template_ids:
2640 'sequence': tax.sequence,
2641 'amount':tax.amount,
2643 'applicable_type': tax.applicable_type,
2644 'domain':tax.domain,
2645 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2646 'child_depend': tax.child_depend,
2647 'python_compute': tax.python_compute,
2648 'python_compute_inv': tax.python_compute_inv,
2649 'python_applicable': tax.python_applicable,
2650 '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,
2651 '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,
2652 'base_sign': tax.base_sign,
2653 'tax_sign': tax.tax_sign,
2654 '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,
2655 '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,
2656 'ref_base_sign': tax.ref_base_sign,
2657 'ref_tax_sign': tax.ref_tax_sign,
2658 'include_base_amount': tax.include_base_amount,
2659 'description':tax.description,
2660 'company_id': company_id,
2661 'type_tax_use': tax.type_tax_use
2663 new_tax = obj_acc_tax.create(cr, uid, vals_tax)
2664 tax_template_to_tax[tax.id] = new_tax
2665 #as the accounts have not been created yet, we have to wait before filling these fields
2666 todo_dict[new_tax] = {
2667 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2668 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2670 tax_template_ref[tax.id] = new_tax
2672 #deactivate the parent_store functionnality on account_account for rapidity purpose
2673 self.pool._init = True
2675 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2676 children_acc_template.sort()
2677 for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2679 for tax in account_template.tax_ids:
2680 tax_ids.append(tax_template_ref[tax.id])
2681 #create the account_account
2683 dig = obj_multi.code_digits
2684 code_main = account_template.code and len(account_template.code) or 0
2685 code_acc = account_template.code or ''
2686 if code_main>0 and code_main<=dig and account_template.type != 'view':
2687 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2689 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2690 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2692 'type': account_template.type,
2693 'user_type': account_template.user_type and account_template.user_type.id or False,
2694 'reconcile': account_template.reconcile,
2695 'shortcut': account_template.shortcut,
2696 'note': account_template.note,
2697 '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,
2698 'tax_ids': [(6,0,tax_ids)],
2699 'company_id': company_id,
2701 new_account = obj_acc.create(cr, uid, vals)
2702 acc_template_ref[account_template.id] = new_account
2703 #reactivate the parent_store functionnality on account_account
2704 self.pool._init = False
2705 self.pool.get('account.account')._parent_store_compute(cr)
2707 for key,value in todo_dict.items():
2708 if value['account_collected_id'] or value['account_paid_id']:
2709 obj_acc_tax.write(cr, uid, [key], {
2710 'account_collected_id': acc_template_ref[value['account_collected_id']],
2711 'account_paid_id': acc_template_ref[value['account_paid_id']],
2714 # Creating Journals Sales and Purchase
2716 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_sp_journal_view')])
2717 data = obj_data.browse(cr, uid, data_id[0])
2718 view_id = data.res_id
2720 seq_id = obj_sequence.search(cr, uid, [('name','=','Account Journal')])[0]
2722 if obj_multi.seq_journal:
2723 seq_id_sale = obj_sequence.search(cr, uid, [('name','=','Sale Journal')])[0]
2724 seq_id_purchase = obj_sequence.search(cr, uid, [('name','=','Purchase Journal')])[0]
2725 seq_id_sale_refund = obj_sequence.search(cr, uid, [('name','=','Sales Refund Journal')])
2726 if seq_id_sale_refund:
2727 seq_id_sale_refund = seq_id_sale_refund[0]
2728 seq_id_purchase_refund = obj_sequence.search(cr, uid, [('name','=','Purchase Refund Journal')])
2729 if seq_id_purchase_refund:
2730 seq_id_purchase_refund = seq_id_purchase_refund[0]
2732 seq_id_sale = seq_id
2733 seq_id_purchase = seq_id
2734 seq_id_sale_refund = seq_id
2735 seq_id_purchase_refund = seq_id
2737 vals_journal['view_id'] = view_id
2740 analitical_sale_ids = analytic_journal_obj.search(cr,uid,[('type','=','sale')])
2741 analitical_journal_sale = analitical_sale_ids and analitical_sale_ids[0] or False
2743 vals_journal['name'] = _('Sales Journal')
2744 vals_journal['type'] = 'sale'
2745 vals_journal['code'] = _('SAJ')
2746 vals_journal['sequence_id'] = seq_id_sale
2747 vals_journal['company_id'] = company_id
2748 vals_journal['analytic_journal_id'] = analitical_journal_sale
2750 if obj_multi.chart_template_id.property_account_receivable:
2751 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2752 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2754 obj_journal.create(cr,uid,vals_journal)
2757 analitical_purchase_ids = analytic_journal_obj.search(cr,uid,[('type','=','purchase')])
2758 analitical_journal_purchase = analitical_purchase_ids and analitical_purchase_ids[0] or False
2760 vals_journal['name'] = _('Purchase Journal')
2761 vals_journal['type'] = 'purchase'
2762 vals_journal['code'] = _('EXJ')
2763 vals_journal['sequence_id'] = seq_id_purchase
2764 vals_journal['view_id'] = view_id
2765 vals_journal['company_id'] = company_id
2766 vals_journal['analytic_journal_id'] = analitical_journal_purchase
2768 if obj_multi.chart_template_id.property_account_payable:
2769 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2770 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2772 obj_journal.create(cr,uid,vals_journal)
2774 # Creating Journals Sales Refund and Purchase Refund
2776 data_id = obj_data.search(cr, uid, [('model', '=', 'account.journal.view'), ('name', '=', 'account_sp_refund_journal_view')], context=context)
2777 data = obj_data.browse(cr, uid, data_id[0], context=context)
2778 view_id = data.res_id
2780 #Sales Refund Journal
2783 'name': _('Sales Refund Journal'),
2784 'type': 'sale_refund',
2785 'refund_journal': True,
2787 'sequence_id': seq_id_sale_refund,
2788 'analytic_journal_id': analitical_journal_sale,
2789 'company_id': company_id
2792 if obj_multi.chart_template_id.property_account_receivable:
2793 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2794 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2797 # if obj_multi.property_account_receivable:
2798 # vals_journal.update({
2799 # 'default_credit_account_id': acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id],
2800 # 'default_debit_account_id': acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2802 obj_journal.create(cr, uid, vals_journal, context=context)
2804 # Purchase Refund Journal
2807 'name': _('Purchase Refund Journal'),
2808 'type': 'purchase_refund',
2809 'refund_journal': True,
2811 'sequence_id': seq_id_purchase_refund,
2812 'analytic_journal_id': analitical_journal_purchase,
2813 'company_id': company_id
2816 if obj_multi.chart_template_id.property_account_payable:
2817 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2818 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2821 # if obj_multi.property_account_payable:
2822 # vals_journal.update({
2823 # 'default_credit_account_id': acc_template_ref[obj_multi.property_account_expense_categ.id],
2824 # 'default_debit_account_id': acc_template_ref[obj_multi.property_account_expense_categ.id]
2826 obj_journal.create(cr, uid, vals_journal, context=context)
2829 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')])
2830 data = obj_data.browse(cr, uid, data_id[0])
2831 view_id_cash = data.res_id
2833 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view_multi')])
2834 data = obj_data.browse(cr, uid, data_id[0])
2835 view_id_cur = data.res_id
2836 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2839 for line in obj_multi.bank_accounts_id:
2840 #create the account_account for this bank journal
2842 dig = obj_multi.code_digits
2843 if ref_acc_bank.code:
2845 new_code = str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num)
2847 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)),'0')) + str(current_num)
2850 'currency_id': line.currency_id and line.currency_id.id or False,
2853 'user_type': account_template.user_type and account_template.user_type.id or False,
2855 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2856 'company_id': company_id,
2858 acc_cash_id = obj_acc.create(cr,uid,vals)
2860 if obj_multi.seq_journal:
2862 'name': _('Bank Journal ') + vals['name'],
2863 'code': 'account.journal',
2865 seq_id = obj_sequence.create(cr,uid,vals_seq)
2867 #create the bank journal
2868 analitical_bank_ids = analytic_journal_obj.search(cr,uid,[('type','=','situation')])
2869 analitical_journal_bank = analitical_bank_ids and analitical_bank_ids[0] or False
2871 vals_journal['name']= vals['name']
2872 vals_journal['code']= _('BNK') + str(current_num)
2873 vals_journal['sequence_id'] = seq_id
2874 vals_journal['type'] = 'cash'
2875 vals_journal['company_id'] = company_id
2876 vals_journal['analytic_journal_id'] = analitical_journal_bank
2878 if line.currency_id:
2879 vals_journal['view_id'] = view_id_cur
2880 vals_journal['currency'] = line.currency_id.id
2882 vals_journal['view_id'] = view_id_cash
2883 vals_journal['default_credit_account_id'] = acc_cash_id
2884 vals_journal['default_debit_account_id'] = acc_cash_id
2885 obj_journal.create(cr, uid, vals_journal)
2888 #create the properties
2889 property_obj = self.pool.get('ir.property')
2890 fields_obj = self.pool.get('ir.model.fields')
2893 ('property_account_receivable','res.partner','account.account'),
2894 ('property_account_payable','res.partner','account.account'),
2895 ('property_account_expense_categ','product.category','account.account'),
2896 ('property_account_income_categ','product.category','account.account'),
2897 ('property_account_expense','product.template','account.account'),
2898 ('property_account_income','product.template','account.account'),
2899 ('property_reserve_and_surplus_account','res.company','account.account')
2901 for record in todo_list:
2903 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2904 account = getattr(obj_multi.chart_template_id, record[0])
2905 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2908 'company_id': company_id,
2909 'fields_id': field[0],
2910 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2914 #the property exist: modify it
2915 property_obj.write(cr, uid, r, vals)
2917 #create the property
2918 property_obj.create(cr, uid, vals)
2920 fp_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)])
2924 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2925 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2927 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2930 'company_id': company_id,
2931 'name': position.name,
2933 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2935 for tax in position.tax_ids:
2937 'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2938 'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2939 'position_id': new_fp,
2941 obj_tax_fp.create(cr, uid, vals_tax)
2943 for acc in position.account_ids:
2945 'account_src_id': acc_template_ref[acc.account_src_id.id],
2946 'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2947 'position_id': new_fp,
2949 obj_ac_fp.create(cr, uid, vals_acc)
2951 ir_values = self.pool.get('ir.values')
2952 if obj_multi.sale_tax:
2953 ir_values.set(cr, uid, key='default', key2=False, name="taxes_id", company=obj_multi.company_id.id,
2954 models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.sale_tax.id]])
2955 if obj_multi.purchase_tax:
2956 ir_values.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=obj_multi.company_id.id,
2957 models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.purchase_tax.id]])
2959 wizard_multi_charts_accounts()
2961 class account_bank_accounts_wizard(osv.osv_memory):
2962 _name = 'account.bank.accounts.wizard'
2965 'acc_name': fields.char('Account Name.', size=64, required=True),
2966 'bank_account_id': fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2967 'currency_id': fields.many2one('res.currency', 'Currency'),
2968 'account_type': fields.selection([('cash','Cash'),('check','Check'),('bank','Bank')], 'Type', size=32),
2971 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
2974 account_bank_accounts_wizard()
2976 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: