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, timedelta
24 from dateutil.relativedelta import relativedelta
25 from operator import itemgetter
28 from osv import fields, osv
29 import decimal_precision as dp
30 from tools.translate import _
32 def check_cycle(self, cr, uid, ids):
33 """ climbs the ``self._table.parent_id`` chains for 100 levels or
34 until it can't find any more parent(s)
36 Returns true if it runs out of parents (no cycle), false if
37 it can recurse 100 times without ending all chains
41 cr.execute('SELECT DISTINCT parent_id '\
42 'FROM '+self._table+' '\
44 'AND parent_id IS NOT NULL',(tuple(ids),))
45 ids = map(itemgetter(0), cr.fetchall())
51 class account_payment_term(osv.osv):
52 _name = "account.payment.term"
53 _description = "Payment Term"
55 'name': fields.char('Payment Term', size=64, translate=True, required=True),
56 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the payment term without removing it."),
57 'note': fields.text('Description', translate=True),
58 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
65 def compute(self, cr, uid, id, value, date_ref=False, context=None):
67 date_ref = datetime.now().strftime('%Y-%m-%d')
68 pt = self.browse(cr, uid, id, context=context)
71 obj_precision = self.pool.get('decimal.precision')
72 for line in pt.line_ids:
73 prec = obj_precision.precision_get(cr, uid, 'Account')
74 if line.value == 'fixed':
75 amt = round(line.value_amount, prec)
76 elif line.value == 'procent':
77 amt = round(value * line.value_amount, prec)
78 elif line.value == 'balance':
79 amt = round(amount, prec)
81 next_date = (datetime.strptime(date_ref, '%Y-%m-%d') + relativedelta(days=line.days))
83 nyear = next_date.strftime("%Y")
84 nmonth = str(int(next_date.strftime("%m"))% 12+1)
87 ndate = "%s-%s-%s" % (nyear, nmonth, nday)
88 nseconds = time.mktime(time.strptime(ndate, '%Y-%m-%d'))
89 next_month = datetime.fromtimestamp(nseconds)
91 delta = timedelta(seconds=1)
92 next_date = next_month - delta
93 next_date = next_date + relativedelta(days=line.days2)
95 next_date += relativedelta(day=line.days2, months=1)
96 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
100 account_payment_term()
102 class account_payment_term_line(osv.osv):
103 _name = "account.payment.term.line"
104 _description = "Payment Term Line"
106 'name': fields.char('Line Name', size=32, required=True),
107 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the payment term lines from the lowest sequences to the higher ones"),
108 'value': fields.selection([('procent', 'Percent'),
109 ('balance', 'Balance'),
110 ('fixed', 'Fixed Amount')], 'Valuation',
111 required=True, help="""Select here the kind of valuation related to this payment term line. Note that you should have your last line with the type 'Balance' to ensure that the whole amount will be threated."""),
113 'value_amount': fields.float('Value Amount', help="For Value percent enter % ratio between 0-1."),
114 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
115 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
116 'days2': fields.integer('Day of the Month', required=True, help="Day of the month, set -1 for the last day of the current month. If it's positive, it gives the day of the next month. Set 0 for net days (otherwise it's based on the beginning of the month)."),
117 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
126 def _check_percent(self, cr, uid, ids, context=None):
127 obj = self.browse(cr, uid, ids[0])
128 if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
133 (_check_percent, _('Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% '), ['value_amount']),
136 account_payment_term_line()
138 class account_account_type(osv.osv):
139 _name = "account.account.type"
140 _description = "Account Type"
142 'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
143 'code': fields.char('Code', size=32, required=True),
144 'close_method': fields.selection([('none', 'None'), ('balance', 'Balance'), ('detail', 'Detail'), ('unreconciled', 'Unreconciled')], 'Deferral Method', required=True, help="""Set here the method that will be used to generate the end of year journal entries for all the accounts of this type.
146 'None' means that nothing will be done.
147 'Balance' will generally be used for cash accounts.
148 'Detail' will copy each existing journal item of the previous year, even the reconciled ones.
149 'Unreconciled' will copy only the journal items that were unreconciled on the first day of the new fiscal year."""),
150 'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign on Reports', required=True, help='Allows you to change the sign of the balance amount displayed in the reports, so that you can see positive figures instead of negative ones in expenses accounts.'),
151 'report_type':fields.selection([
153 ('income','Profit & Loss (Income Accounts)'),
154 ('expense','Profit & Loss (Expense Accounts)'),
155 ('asset','Balance Sheet (Assets Accounts)'),
156 ('liability','Balance Sheet (Liability Accounts)')
157 ],'P&L / BS Category', select=True, readonly=False, help="According value related accounts will be display on respective reports (Balance Sheet Profit & Loss Account)", required=True),
158 'note': fields.text('Description'),
161 'close_method': 'none',
163 'report_type': 'none',
167 account_account_type()
169 def _code_get(self, cr, uid, context=None):
170 acc_type_obj = self.pool.get('account.account.type')
171 ids = acc_type_obj.search(cr, uid, [])
172 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context=context)
173 return [(r['code'], r['name']) for r in res]
175 #----------------------------------------------------------
177 #----------------------------------------------------------
179 class account_tax(osv.osv):
180 _name = 'account.tax'
183 class account_account(osv.osv):
184 _order = "parent_left"
185 _parent_order = "code"
186 _name = "account.account"
187 _description = "Account"
189 logger = netsvc.Logger()
191 def search(self, cr, uid, args, offset=0, limit=None, order=None,
192 context=None, count=False):
197 while pos < len(args):
199 if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
200 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
201 if args[pos][0] == 'journal_id':
205 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
206 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
207 args[pos] = ('type','not in',('consolidation','view'))
209 ids3 = map(lambda x: x.id, jour.type_control_ids)
210 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
211 ids1 += map(lambda x: x.id, jour.account_control_ids)
212 args[pos] = ('id', 'in', ids1)
215 if context and context.has_key('consolidate_childs'): #add consolidated childs of accounts
216 ids = super(account_account, self).search(cr, uid, args, offset, limit,
217 order, context=context, count=count)
218 for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
219 ids.append(consolidate_child.id)
222 return super(account_account, self).search(cr, uid, args, offset, limit,
223 order, context=context, count=count)
225 def _get_children_and_consol(self, cr, uid, ids, context=None):
228 #this function search for all the children and all consolidated children (recursively) of the given account ids
229 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
231 for rec in self.browse(cr, uid, ids2, context=context):
232 for child in rec.child_consol_ids:
233 ids3.append(child.id)
235 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
238 def __compute(self, cr, uid, ids, field_names, arg=None, context=None,
239 query='', query_params=()):
240 """ compute the balance, debit and/or credit for the provided
244 `field_names`: the fields to compute (a list of any of
245 'balance', 'debit' and 'credit')
246 `arg`: unused fields.function stuff
247 `query`: additional query filter (as a string)
248 `query_params`: parameters for the provided query string
249 (__compute will handle their escaping) as a
253 'balance': "COALESCE(SUM(l.debit),0) " \
254 "- COALESCE(SUM(l.credit), 0) as balance",
255 'debit': "COALESCE(SUM(l.debit), 0) as debit",
256 'credit': "COALESCE(SUM(l.credit), 0) as credit"
258 #get all the necessary accounts
259 children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
260 #compute for each account the balance/debit/credit from the move lines
262 if children_and_consolidated:
263 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
267 wheres.append(query.strip())
268 if aml_query.strip():
269 wheres.append(aml_query.strip())
270 filters = " AND ".join(wheres)
271 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
272 'Filters: %s'%filters)
273 # IN might not work ideally in case there are too many
274 # children_and_consolidated, in that case join on a
276 # SELECT l.account_id as id FROM account_move_line l
277 # INNER JOIN (VALUES (id1), (id2), (id3), ...) AS tmp (id)
278 # ON l.account_id = tmp.id
279 # or make _get_children_and_consol return a query and join on that
280 request = ("SELECT l.account_id as id, " +\
281 ', '.join(map(mapping.__getitem__, field_names)) +
282 " FROM account_move_line l" \
283 " WHERE l.account_id IN %s " \
285 " GROUP BY l.account_id")
286 params = (tuple(children_and_consolidated),) + query_params
287 cr.execute(request, params)
288 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
289 'Status: %s'%cr.statusmessage)
291 for res in cr.dictfetchall():
292 accounts[res['id']] = res
294 # consolidate accounts with direct children
295 children_and_consolidated.reverse()
296 brs = list(self.browse(cr, uid, children_and_consolidated, context=context))
301 for child in current.child_id:
302 if child.id not in sums:
305 brs.insert(0, brs.pop(brs.index(child)))
310 for fn in field_names:
311 sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
313 sums[current.id][fn] += sum(sums[child.id][fn] for child in current.child_id)
315 null_result = dict((fn, 0.0) for fn in field_names)
317 res[id] = sums.get(id, null_result)
320 def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
322 for rec in self.browse(cr, uid, ids, context):
323 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
326 def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
328 for record in self.browse(cr, uid, ids, context):
329 if record.child_parent_ids:
330 result[record.id] = [x.id for x in record.child_parent_ids]
332 result[record.id] = []
334 if record.child_consol_ids:
335 for acc in record.child_consol_ids:
336 if acc.id not in result[record.id]:
337 result[record.id].append(acc.id)
341 def _get_level(self, cr, uid, ids, field_name, arg, context={}):
343 accounts = self.browse(cr, uid, ids)
344 for account in accounts:
346 if account.parent_id:
347 obj = self.browse(cr, uid, account.parent_id.id)
348 level = obj.level + 1
349 res[account.id] = level
353 'name': fields.char('Name', size=128, required=True, select=True),
354 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
355 'code': fields.char('Code', size=64, required=True),
356 'type': fields.selection([
358 ('other', 'Regular'),
359 ('receivable', 'Receivable'),
360 ('payable', 'Payable'),
361 ('liquidity','Liquidity'),
362 ('consolidation', 'Consolidation'),
363 ('closed', 'Closed'),
364 ], 'Internal Type', required=True, help="This type is used to differentiate types with "\
365 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
366 "can have children accounts for multi-company consolidations, payable/receivable are for "\
367 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
368 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
369 help="These types are defined according to your country. The type contains more information "\
370 "about the account and its specificities."),
371 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
372 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
373 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
374 'child_id': fields.function(_get_child_ids, method=True, type='many2many', relation="account.account", string="Child Accounts"),
375 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Balance', multi='balance'),
376 'credit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Credit', multi='balance'),
377 'debit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Debit', multi='balance'),
378 'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."),
379 'shortcut': fields.char('Shortcut', size=12),
380 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
381 'account_id', 'tax_id', 'Default Taxes'),
382 'note': fields.text('Note'),
383 'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
384 'company_id': fields.many2one('res.company', 'Company', required=True),
385 'active': fields.boolean('Active', select=2, help="If the active field is set to true, it will allow you to hide the account without removing it."),
387 'parent_left': fields.integer('Parent Left', select=1),
388 'parent_right': fields.integer('Parent Right', select=1),
389 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
391 'This will select how the current currency rate for outgoing transactions is computed. '\
392 'In most countries the legal method is "average" but only a few software systems are able to '\
393 'manage this. So if you import from another software system you may have to use the rate at date. ' \
394 'Incoming transactions always use the rate at date.', \
396 'level': fields.function(_get_level, string='Level', method=True, store=True, type='integer'),
403 'currency_mode': 'current',
404 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
407 def _check_recursion(self, cr, uid, ids):
408 obj_self = self.browse(cr, uid, ids[0])
409 p_id = obj_self.parent_id and obj_self.parent_id.id
410 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
413 cr.execute('SELECT DISTINCT child_id '\
414 'FROM account_account_consol_rel '\
415 'WHERE parent_id IN %s', (tuple(ids),))
416 child_ids = map(itemgetter(0), cr.fetchall())
418 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
421 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
422 if p_id and (p_id in s_ids):
429 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
432 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
434 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
442 if name and str(name).startswith('partner:'):
443 part_id = int(name.split(':')[1])
444 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
445 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
447 if name and str(name).startswith('type:'):
448 type = name.split(':')[1]
449 args += [('type', '=', type)]
454 ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
456 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
458 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
459 if not ids and len(name.split()) >= 2:
460 #Separating code and name of account for searching
461 operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
462 ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
464 ids = self.search(cr, user, args, context=context, limit=limit)
465 return self.name_get(cr, user, ids, context=context)
467 def name_get(self, cr, uid, ids, context=None):
470 reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
473 name = record['name']
475 name = record['code'] + ' '+name
476 res.append((record['id'], name))
479 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
480 account = self.browse(cr, uid, id, context=context)
484 default = default.copy()
485 default['code'] = (account['code'] or '') + '(copy)'
488 if account.id in done_list:
490 done_list.append(account.id)
492 for child in account.child_id:
493 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
495 new_child_ids.append(child_ids)
496 default['child_parent_ids'] = [(6, 0, new_child_ids)]
498 default['child_parent_ids'] = False
499 return super(account_account, self).copy(cr, uid, id, default, context=context)
501 def _check_moves(self, cr, uid, ids, method, context=None):
502 line_obj = self.pool.get('account.move.line')
503 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
505 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
506 if method == 'write':
507 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
508 elif method == 'unlink':
509 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
510 #Checking whether the account is set as a property to any Partner or not
511 value = 'account.account,' + str(ids[0])
512 partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
514 raise osv.except_osv(_('Warning !'), _('You cannot remove/deactivate an account which is set as a property to any Partner.'))
517 def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
518 group1 = ['payable', 'receivable', 'other']
519 group2 = ['consolidation','view']
520 line_obj = self.pool.get('account.move.line')
521 for account in self.browse(cr, uid, ids, context=context):
522 old_type = account.type
523 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
524 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
525 #Check for 'Closed' type
526 if old_type == 'closed' and new_type !='closed':
527 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains account entries!"))
528 #Check for change From group1 to group2 and vice versa
529 if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
530 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from '%s' to '%s' type as it contains account entries!") % (old_type,new_type,))
533 def write(self, cr, uid, ids, vals, context=None):
537 if 'company_id' in vals:
538 move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
540 raise osv.except_osv(_('Warning !'), _('You cannot modify Company of account as its related record exist in Entry Lines'))
541 if 'active' in vals and not vals['active']:
542 self._check_moves(cr, uid, ids, "write", context=context)
543 if 'type' in vals.keys():
544 self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
545 return super(account_account, self).write(cr, uid, ids, vals, context=context)
547 def unlink(self, cr, uid, ids, context=None):
548 self._check_moves(cr, uid, ids, "unlink", context=context)
549 return super(account_account, self).unlink(cr, uid, ids, context=context)
553 class account_journal_view(osv.osv):
554 _name = "account.journal.view"
555 _description = "Journal View"
557 'name': fields.char('Journal View', size=64, required=True),
558 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
562 account_journal_view()
565 class account_journal_column(osv.osv):
567 def _col_get(self, cr, user, context=None):
569 cols = self.pool.get('account.move.line')._columns
571 if col in ('period_id', 'journal_id'):
573 result.append( (col, cols[col].string) )
577 _name = "account.journal.column"
578 _description = "Journal Column"
580 'name': fields.char('Column Name', size=64, required=True),
581 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
582 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
583 'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column."),
584 'required': fields.boolean('Required'),
585 'readonly': fields.boolean('Readonly'),
589 account_journal_column()
591 class account_journal(osv.osv):
592 _name = "account.journal"
593 _description = "Journal"
595 'name': fields.char('Journal Name', size=64, required=True, translate=True),
596 'code': fields.char('Code', size=16, required=True, help="The code will be used to generate the numbers of the journal entries of this journal."),
597 'type': fields.selection([('sale', 'Sale'),('sale_refund','Sale Refund'), ('purchase', 'Purchase'), ('purchase_refund','Purchase Refund'), ('cash', 'Cash'), ('bank', 'Bank and Cheques'), ('general', 'General'), ('situation', 'Situation')], 'Type', size=32, required=True,
598 help="Select 'Sale' for Sale journal to be used at the time of making invoice."\
599 " Select 'Purchase' for Purchase Journal to be used at the time of approving purchase order."\
600 " Select 'Cash' to be used at the time of making payment."\
601 " Select 'General' to be used at the time of stock input/output."\
602 " Select 'Situation' to be used at the time of making vouchers."),
603 'refund_journal': fields.boolean('Refund Journal', help='Fill this if the journal is to be used for refunds of invoices.'),
604 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
605 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
606 'view_id': fields.many2one('account.journal.view', 'Display Mode', required=True, help="Gives the view used when writing or browsing entries in this journal. The view tells OpenERP which fields should be visible, required or readonly and in which order. You can create your own view for a faster encoding in each journal."),
607 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
608 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
609 'centralisation': fields.boolean('Centralised counterpart', help="Check this box to determine that each entry of this journal won't create a new counterpart but will share the same counterpart. This is used in fiscal year closing."),
610 'update_posted': fields.boolean('Allow Cancelling Entries', help="Check this box if you want to allow the cancellation the entries related to this journal or of the invoice related to this journal"),
611 'group_invoice_lines': fields.boolean('Group Invoice Lines', help="If this box is checked, the system will try to group the accounting lines when generating them from invoices."),
612 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="This field contains the informatin related to the numbering of the journal entries of this journal.", required=True),
613 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
614 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
615 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
616 'entry_posted': fields.boolean('Skip \'Draft\' State for Manual Entries', help='Check this box if you don\'t want new journal entries to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation. \nNote that journal entries that are automatically created by the system are always skipping that state.'),
617 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
618 'allow_date':fields.boolean('Check Date not in the Period', help= 'If set to True then do not accept the entry if the entry date is not into the period dates'),
622 'user_id': lambda self,cr,uid,context: uid,
623 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
626 def write(self, cr, uid, ids, vals, context=None):
627 if 'company_id' in vals:
628 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
630 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this journal as its related record exist in Entry Lines'))
631 return super(account_journal, self).write(cr, uid, ids, vals, context=context)
633 def create_sequence(self, cr, uid, vals, context=None):
635 Create new entry sequence for every new Joural
636 @param cr: cursor to database
637 @param user: id of current user
638 @param ids: list of record ids to be process
639 @param context: context arguments, like lang, time zone
640 @return: return a result
642 seq_pool = self.pool.get('ir.sequence')
643 seq_typ_pool = self.pool.get('ir.sequence.type')
646 code = vals['code'].lower()
652 seq_typ_pool.create(cr, uid, types)
658 'prefix': code + "/%(year)s/",
660 'number_increment': 1
662 return seq_pool.create(cr, uid, seq)
664 def create(self, cr, uid, vals, context=None):
665 if not 'sequence_id' in vals or not vals['sequence_id']:
666 vals.update({'sequence_id': self.create_sequence(cr, uid, vals, context)})
667 return super(account_journal, self).create(cr, uid, vals, context)
669 def name_get(self, cr, user, ids, context=None):
671 Returns a list of tupples containing id, name.
672 result format: {[(id, name), (id, name), ...]}
674 @param cr: A database cursor
675 @param user: ID of the user currently logged in
676 @param ids: list of ids for which name should be read
677 @param context: context arguments, like lang, time zone
679 @return: Returns a list of tupples containing id, name
681 result = self.browse(cr, user, ids, context)
686 name = "%s (%s)" % (rs.name, rs.currency.name)
687 res += [(rs.id, name)]
690 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
696 if context.get('journal_type', False):
697 args += [('type','=',context.get('journal_type'))]
699 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
701 ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
703 return self.name_get(cr, user, ids, context=context)
705 def onchange_type(self, cr, uid, ids, type, currency):
706 obj_data = self.pool.get('ir.model.data')
707 user_pool = self.pool.get('res.users')
710 'sale':'account_sp_journal_view',
711 'sale_refund':'account_sp_refund_journal_view',
712 'purchase':'account_sp_journal_view',
713 'purchase_refund':'account_sp_refund_journal_view',
714 'cash':'account_journal_bank_view',
715 'bank':'account_journal_bank_view',
716 'general':'account_journal_view',
717 'situation':'account_journal_view'
722 view_id = type_map.get(type, 'general')
724 user = user_pool.browse(cr, uid, uid)
725 if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
726 view_id = 'account_journal_bank_view_multi'
728 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
729 data = obj_data.browse(cr, uid, data_id[0])
732 'centralisation':type == 'situation',
733 'view_id':data.res_id,
742 class account_fiscalyear(osv.osv):
743 _name = "account.fiscalyear"
744 _description = "Fiscal Year"
746 'name': fields.char('Fiscal Year', size=64, required=True),
747 'code': fields.char('Code', size=6, required=True),
748 'company_id': fields.many2one('res.company', 'Company', required=True),
749 'date_start': fields.date('Start Date', required=True),
750 'date_stop': fields.date('End Date', required=True),
751 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
752 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True),
756 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
758 _order = "date_start"
760 def _check_fiscal_year(self, cr, uid, ids, context=None):
761 current_fiscal_yr = self.browse(cr, uid, ids, context=context)[0]
762 obj_fiscal_ids = self.search(cr, uid, [('company_id', '=', current_fiscal_yr.company_id.id)], context=context)
763 obj_fiscal_ids.remove(ids[0])
764 data_fiscal_yr = self.browse(cr, uid, obj_fiscal_ids, context=context)
766 for old_fy in data_fiscal_yr:
767 if old_fy.company_id.id == current_fiscal_yr['company_id'].id:
768 # Condition to check if the current fiscal year falls in between any previously defined fiscal year
769 if old_fy.date_start <= current_fiscal_yr['date_start'] <= old_fy.date_stop or \
770 old_fy.date_start <= current_fiscal_yr['date_stop'] <= old_fy.date_stop:
774 def _check_duration(self,cr,uid,ids):
775 obj_fy = self.browse(cr,uid,ids[0])
776 if obj_fy.date_stop < obj_fy.date_start:
781 (_check_duration, 'Error! The duration of the Fiscal Year is invalid. ', ['date_stop']),
782 (_check_fiscal_year, 'Error! You cannot define overlapping fiscal years',['date_start', 'date_stop'])
785 def create_period3(self,cr, uid, ids, context={}):
786 return self.create_period(cr, uid, ids, context, 3)
788 def create_period(self,cr, uid, ids, context={}, interval=1):
789 for fy in self.browse(cr, uid, ids, context):
790 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
791 while ds.strftime('%Y-%m-%d')<fy.date_stop:
792 de = ds + relativedelta(months=interval, days=-1)
794 if de.strftime('%Y-%m-%d')>fy.date_stop:
795 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
797 self.pool.get('account.period').create(cr, uid, {
798 'name': ds.strftime('%m/%Y'),
799 'code': ds.strftime('%m/%Y'),
800 'date_start': ds.strftime('%Y-%m-%d'),
801 'date_stop': de.strftime('%Y-%m-%d'),
802 'fiscalyear_id': fy.id,
804 ds = ds + relativedelta(months=interval)
807 def find(self, cr, uid, dt=None, exception=True, context={}):
809 dt = time.strftime('%Y-%m-%d')
810 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
813 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
818 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
825 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
827 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
828 return self.name_get(cr, user, ids, context=context)
832 class account_period(osv.osv):
833 _name = "account.period"
834 _description = "Account period"
836 'name': fields.char('Period Name', size=64, required=True),
837 'code': fields.char('Code', size=12),
838 'special': fields.boolean('Opening/Closing Period', size=12,
839 help="These periods can overlap."),
840 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
841 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
842 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
843 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True,
844 help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
845 'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
850 _order = "date_start"
852 def _check_duration(self,cr,uid,ids,context={}):
853 obj_period=self.browse(cr,uid,ids[0])
854 if obj_period.date_stop < obj_period.date_start:
858 def _check_year_limit(self,cr,uid,ids,context={}):
859 for obj_period in self.browse(cr,uid,ids):
860 if obj_period.special:
863 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
864 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
865 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
866 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
869 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
870 for period in self.browse(cr, uid, pids):
871 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
876 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
877 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
880 def next(self, cr, uid, period, step, context={}):
881 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
886 def find(self, cr, uid, dt=None, context={}):
888 dt = time.strftime('%Y-%m-%d')
889 #CHECKME: shouldn't we check the state of the period?
890 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
892 raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create a fiscal year.')%dt)
895 def action_draft(self, cr, uid, ids, *args):
898 cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
899 cr.execute('update account_period set state=%s where id=%s', (mode, id))
902 def name_search(self, cr, user, name, args=None, operator='ilike', context={}, limit=80):
909 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
911 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
912 return self.name_get(cr, user, ids, context=context)
914 def write(self, cr, uid, ids, vals, context={}):
915 if 'company_id' in vals:
916 move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
918 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines'))
919 return super(account_period, self).write(cr, uid, ids, vals, context=context)
921 def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
922 period_from = self.browse(cr, uid, period_from_id)
923 period_date_start = period_from.date_start
924 company1_id = period_from.company_id.id
925 period_to = self.browse(cr, uid, period_to_id)
926 period_date_stop = period_to.date_stop
927 company2_id = period_to.company_id.id
928 if company1_id != company2_id:
929 raise osv.except_osv(_('Error'), _('You should have chosen periods that belongs to the same company'))
930 if period_date_start > period_date_stop:
931 raise osv.except_osv(_('Error'), _('Start period should be smaller then End period'))
932 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id)])
936 class account_journal_period(osv.osv):
937 _name = "account.journal.period"
938 _description = "Journal Period"
940 def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
941 result = {}.fromkeys(ids, 'STOCK_NEW')
942 for r in self.read(cr, uid, ids, ['state']):
944 'draft': 'STOCK_NEW',
945 'printed': 'STOCK_PRINT_PREVIEW',
946 'done': 'STOCK_DIALOG_AUTHENTICATION',
947 }.get(r['state'], 'STOCK_NEW')
951 'name': fields.char('Journal-Period Name', size=64, required=True),
952 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
953 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
954 'icon': fields.function(_icon_get, method=True, string='Icon', type='char', size=32),
955 'active': fields.boolean('Active', required=True, help="If the active field is set to true, it will allow you to hide the journal period without removing it."),
956 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
957 help='When journal period is created. The state is \'Draft\'. If a report is printed it comes to \'Printed\' state. When all transactions are done, it comes in \'Done\' state.'),
958 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
959 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company')
962 def _check(self, cr, uid, ids, context={}):
963 for obj in self.browse(cr, uid, ids, context):
964 cr.execute('select * from account_move_line where journal_id=%s and period_id=%s limit 1', (obj.journal_id.id, obj.period_id.id))
967 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
970 def write(self, cr, uid, ids, vals, context={}):
971 self._check(cr, uid, ids, context)
972 return super(account_journal_period, self).write(cr, uid, ids, vals, context)
974 def create(self, cr, uid, vals, context={}):
975 period_id=vals.get('period_id',False)
977 period = self.pool.get('account.period').browse(cr, uid,period_id)
978 vals['state']=period.state
979 return super(account_journal_period, self).create(cr, uid, vals, context)
981 def unlink(self, cr, uid, ids, context={}):
982 self._check(cr, uid, ids, context)
983 return super(account_journal_period, self).unlink(cr, uid, ids, context)
991 account_journal_period()
993 class account_fiscalyear(osv.osv):
994 _inherit = "account.fiscalyear"
995 _description = "Fiscal Year"
997 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1000 def copy(self, cr, uid, id, default={}, context=None):
1003 'end_journal_period_id': False
1005 return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1007 account_fiscalyear()
1008 #----------------------------------------------------------
1010 #----------------------------------------------------------
1011 class account_move(osv.osv):
1012 _name = "account.move"
1013 _description = "Account Entry"
1016 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1018 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1019 result format: {[(id, name), (id, name), ...]}
1021 @param cr: A database cursor
1022 @param user: ID of the user currently logged in
1023 @param name: name to search
1024 @param args: other arguments
1025 @param operator: default operator is 'ilike', it can be changed
1026 @param context: context arguments, like lang, time zone
1027 @param limit: Returns first 'n' ids of complete result, default is 80.
1029 @return: Returns a list of tuples containing id and name
1038 ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1040 if not ids and name and type(name) == int:
1041 ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1044 ids += self.search(cr, user, args, limit=limit, context=context)
1046 return self.name_get(cr, user, ids, context=context)
1048 def name_get(self, cursor, user, ids, context=None):
1049 if isinstance(ids, (int, long)):
1054 data_move = self.pool.get('account.move').browse(cursor,user,ids)
1055 for move in data_move:
1056 if move.state=='draft':
1057 name = '*' + str(move.id)
1060 res.append((move.id, name))
1063 def _get_period(self, cr, uid, context):
1064 periods = self.pool.get('account.period').find(cr, uid)
1069 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1070 if not ids: return {}
1071 cr.execute( 'SELECT move_id, SUM(debit) '\
1072 'FROM account_move_line '\
1073 'WHERE move_id IN %s '\
1074 'GROUP BY move_id', (tuple(ids),))
1075 result = dict(cr.fetchall())
1077 result.setdefault(id, 0.0)
1080 def _search_amount(self, cr, uid, obj, name, args, context):
1084 if isinstance(cond[2],(list,tuple)):
1085 if cond[1] in ['in','not in']:
1086 amount = tuple(cond[2])
1090 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1093 cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1094 res_ids = set(id[0] for id in cr.fetchall())
1095 ids = ids and (ids & res_ids) or res_ids
1097 return [('id','in',tuple(ids))]
1099 return [('id', '=', '0')]
1102 'name': fields.char('Number', size=64, required=True),
1103 'ref': fields.char('Reference', size=64),
1104 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1105 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1106 'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'State', required=True, readonly=True,
1107 help='All manually created new journal entry are usually in the state \'Unposted\', but you can set the option to skip that state on the related journal. In that case, they will be behave as journal entries automatically created by the system on document validation (invoices, bank statements...) and will be created in \'Posted\' state.'),
1108 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1109 'to_check': fields.boolean('To Review', help='Check this box if you are unsure of that journal entry and if you want to note it as \'to be reviewed\' by an accounting expert.'),
1110 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1111 'amount': fields.function(_amount_compute, method=True, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1112 'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}),
1113 'narration':fields.text('Narration'),
1114 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1119 'period_id': _get_period,
1120 'date': time.strftime('%Y-%m-%d'),
1121 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1124 def _check_centralisation(self, cursor, user, ids):
1125 for move in self.browse(cursor, user, ids):
1126 if move.journal_id.centralisation:
1127 move_ids = self.search(cursor, user, [
1128 ('period_id', '=', move.period_id.id),
1129 ('journal_id', '=', move.journal_id.id),
1131 if len(move_ids) > 1:
1135 def _check_period_journal(self, cursor, user, ids):
1136 for move in self.browse(cursor, user, ids):
1137 for line in move.line_id:
1138 if line.period_id.id != move.period_id.id:
1140 if line.journal_id.id != move.journal_id.id:
1145 (_check_centralisation,
1146 'You cannot create more than one move per period on centralized journal',
1148 (_check_period_journal,
1149 'You cannot create entries on different periods/journals in the same move',
1153 def post(self, cr, uid, ids, context=None):
1156 invoice = context.get('invoice', False)
1157 valid_moves = self.validate(cr, uid, ids, context)
1160 raise osv.except_osv(_('Integrity Error !'), _('You cannot validate a non-balanced entry !\nMake sure you have configured Payment Term properly !\nIt should contain atleast one Payment Term Line with type "Balance" !'))
1161 obj_sequence = self.pool.get('ir.sequence')
1162 for move in self.browse(cr, uid, valid_moves):
1165 journal = move.journal_id
1167 if invoice and invoice.internal_number:
1168 new_name = invoice.internal_number
1170 if journal.sequence_id:
1171 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1172 new_name = obj_sequence.get_id(cr, uid, journal.sequence_id.id, context=c)
1174 raise osv.except_osv(_('Error'), _('No sequence defined on the journal !'))
1177 self.write(cr, uid, [move.id], {'name':new_name})
1179 cr.execute('UPDATE account_move '\
1182 ('posted', tuple(valid_moves),))
1186 def button_validate(self, cursor, user, ids, context=None):
1187 for move in self.browse(cursor, user, ids):
1189 for line in move.line_id:
1190 account = line.account_id
1193 account = account.parent_id
1196 elif top<>account2.id:
1197 raise osv.except_osv(_('Error !'), _('You cannot validate a Journal Entry unless all journal items are in same chart of accounts !'))
1198 return self.post(cursor, user, ids, context=context)
1200 def button_cancel(self, cr, uid, ids, context={}):
1201 for line in self.browse(cr, uid, ids, context):
1202 if not line.journal_id.update_posted:
1203 raise osv.except_osv(_('Error !'), _('You can not modify a posted entry of this journal !\nYou should set the journal to allow cancelling entries if you want to do that.'))
1205 cr.execute('UPDATE account_move '\
1207 'WHERE id IN %s', ('draft', tuple(ids),))
1210 def write(self, cr, uid, ids, vals, context={}):
1212 c['novalidate'] = True
1213 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
1214 self.validate(cr, uid, ids, context)
1218 # TODO: Check if period is closed !
1220 def create(self, cr, uid, vals, context=None):
1221 context = context or {}
1222 if 'line_id' in vals and context.get('copy'):
1223 for l in vals['line_id']:
1226 'reconcile_id':False,
1227 'reconcil_partial_id':False,
1228 'analytic_lines':False,
1232 'account_tax_id':False,
1235 if 'journal_id' in vals and vals.get('journal_id', False):
1236 for l in vals['line_id']:
1238 l[2]['journal_id'] = vals['journal_id']
1239 context['journal_id'] = vals['journal_id']
1240 if 'period_id' in vals:
1241 for l in vals['line_id']:
1243 l[2]['period_id'] = vals['period_id']
1244 context['period_id'] = vals['period_id']
1246 default_period = self._get_period(cr, uid, context)
1247 for l in vals['line_id']:
1249 l[2]['period_id'] = default_period
1250 context['period_id'] = default_period
1252 if 'line_id' in vals:
1254 c['novalidate'] = True
1255 result = super(account_move, self).create(cr, uid, vals, c)
1256 self.validate(cr, uid, [result], context)
1258 result = super(account_move, self).create(cr, uid, vals, context)
1261 def copy(self, cr, uid, id, default={}, context={}):
1262 context = context or {}
1270 return super(account_move, self).copy(cr, uid, id, default, context)
1272 def unlink(self, cr, uid, ids, context=None, check=True):
1273 context = context or {}
1275 obj_move_line = self.pool.get('account.move.line')
1276 for move in self.browse(cr, uid, ids, context):
1277 if move['state'] != 'draft':
1278 raise osv.except_osv(_('UserError'),
1279 _('You can not delete posted movement: "%s"!') % \
1281 line_ids = map(lambda x: x.id, move.line_id)
1282 context['journal_id'] = move.journal_id.id
1283 context['period_id'] = move.period_id.id
1284 obj_move_line._update_check(cr, uid, line_ids, context)
1285 obj_move_line.unlink(cr, uid, line_ids, context=context)
1286 toremove.append(move.id)
1287 result = super(account_move, self).unlink(cr, uid, toremove, context)
1290 def _compute_balance(self, cr, uid, id, context={}):
1291 move = self.browse(cr, uid, [id])[0]
1293 for line in move.line_id:
1294 amount+= (line.debit - line.credit)
1297 def _centralise(self, cr, uid, move, mode, context=None):
1298 assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1303 account_id = move.journal_id.default_debit_account_id.id
1306 raise osv.except_osv(_('UserError'),
1307 _('There is no default default debit account defined \n' \
1308 'on journal "%s"') % move.journal_id.name)
1310 account_id = move.journal_id.default_credit_account_id.id
1313 raise osv.except_osv(_('UserError'),
1314 _('There is no default default credit account defined \n' \
1315 'on journal "%s"') % move.journal_id.name)
1317 # find the first line of this move with the current mode
1318 # or create it if it doesn't exist
1319 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1324 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1325 line_id = self.pool.get('account.move.line').create(cr, uid, {
1326 'name': _(mode.capitalize()+' Centralisation'),
1327 'centralisation': mode,
1328 'account_id': account_id,
1330 'journal_id': move.journal_id.id,
1331 'period_id': move.period_id.id,
1332 'date': move.period_id.date_stop,
1337 # find the first line of this move with the other mode
1338 # so that we can exclude it from our calculation
1339 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1346 cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1347 result = cr.fetchone()[0] or 0.0
1348 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1352 # Validate a balanced move. If it is a centralised journal, create a move.
1354 def validate(self, cr, uid, ids, context={}):
1355 if context and ('__last_update' in context):
1356 del context['__last_update']
1358 valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1359 obj_analytic_line = self.pool.get('account.analytic.line')
1360 obj_move_line = self.pool.get('account.move.line')
1361 for move in self.browse(cr, uid, ids, context):
1362 # Unlink old analytic lines on move_lines
1363 for obj_line in move.line_id:
1364 for obj in obj_line.analytic_lines:
1365 obj_analytic_line.unlink(cr,uid,obj.id)
1367 journal = move.journal_id
1372 for line in move.line_id:
1373 amount += line.debit - line.credit
1374 line_ids.append(line.id)
1375 if line.state=='draft':
1376 line_draft_ids.append(line.id)
1379 company_id = line.account_id.company_id.id
1380 if not company_id == line.account_id.company_id.id:
1381 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1383 if line.account_id.currency_id and line.currency_id:
1384 if line.account_id.currency_id.id != line.currency_id.id and (line.account_id.currency_id.id != line.account_id.company_id.currency_id.id):
1385 raise osv.except_osv(_('Error'), _("""Couldn't create move with currency different from the secondary currency of the account "%s - %s". Clear the secondary currency field of the account definition if you want to accept all currencies.""" % (line.account_id.code, line.account_id.name)))
1387 if abs(amount) < 10 ** -4:
1388 # If the move is balanced
1389 # Add to the list of valid moves
1390 # (analytic lines will be created later for valid moves)
1391 valid_moves.append(move)
1393 # Check whether the move lines are confirmed
1395 if not line_draft_ids:
1397 # Update the move lines (set them as valid)
1399 obj_move_line.write(cr, uid, line_draft_ids, {
1400 'journal_id': move.journal_id.id,
1401 'period_id': move.period_id.id,
1403 }, context, check=False)
1408 if journal.type in ('purchase','sale'):
1409 for line in move.line_id:
1411 key = (line.account_id.id, line.tax_code_id.id)
1413 code = account2[key][0]
1414 amount = account2[key][1] * (line.debit + line.credit)
1415 elif line.account_id.id in account:
1416 code = account[line.account_id.id][0]
1417 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1418 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1419 obj_move_line.write(cr, uid, [line.id], {
1420 'tax_code_id': code,
1421 'tax_amount': amount
1422 }, context, check=False)
1423 elif journal.centralisation:
1424 # If the move is not balanced, it must be centralised...
1426 # Add to the list of valid moves
1427 # (analytic lines will be created later for valid moves)
1428 valid_moves.append(move)
1431 # Update the move lines (set them as valid)
1433 self._centralise(cr, uid, move, 'debit', context=context)
1434 self._centralise(cr, uid, move, 'credit', context=context)
1435 obj_move_line.write(cr, uid, line_draft_ids, {
1437 }, context, check=False)
1439 # We can't validate it (it's unbalanced)
1440 # Setting the lines as draft
1441 obj_move_line.write(cr, uid, line_ids, {
1442 'journal_id': move.journal_id.id,
1443 'period_id': move.period_id.id,
1445 }, context, check=False)
1446 # Create analytic lines for the valid moves
1447 for record in valid_moves:
1448 obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1450 valid_moves = [move.id for move in valid_moves]
1451 return len(valid_moves) > 0 and valid_moves or False
1455 class account_move_reconcile(osv.osv):
1456 _name = "account.move.reconcile"
1457 _description = "Account Reconciliation"
1459 'name': fields.char('Name', size=64, required=True),
1460 'type': fields.char('Type', size=16, required=True),
1461 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1462 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1463 'create_date': fields.date('Creation date', readonly=True),
1466 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1468 def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1470 for rec in self.browse(cr, uid, ids, context):
1471 for line in rec.line_partial_ids:
1472 total += (line.debit or 0.0) - (line.credit or 0.0)
1474 self.pool.get('account.move.line').write(cr, uid,
1475 map(lambda x: x.id, rec.line_partial_ids),
1476 {'reconcile_id': rec.id }
1480 def name_get(self, cr, uid, ids, context=None):
1484 for r in self.browse(cr, uid, ids, context):
1485 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1487 name = '%s (%.2f)' % (r.name, total)
1488 result.append((r.id,name))
1490 result.append((r.id,r.name))
1494 account_move_reconcile()
1496 #----------------------------------------------------------
1498 #----------------------------------------------------------
1501 child_depend: la taxe depend des taxes filles
1503 class account_tax_code(osv.osv):
1505 A code for the tax object.
1507 This code is used for some tax declarations.
1509 def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1510 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1511 if context.get('based_on', 'invoices') == 'payments':
1512 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1513 FROM account_move_line AS line, \
1514 account_move AS move \
1515 LEFT JOIN account_invoice invoice ON \
1516 (invoice.move_id = move.id) \
1517 WHERE line.tax_code_id IN %s '+where+' \
1518 AND move.id = line.move_id \
1519 AND ((invoice.state = \'paid\') \
1520 OR (invoice.id IS NULL)) \
1521 GROUP BY line.tax_code_id',
1522 (parent_ids,) + where_params)
1524 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1525 FROM account_move_line AS line, \
1526 account_move AS move \
1527 WHERE line.tax_code_id IN %s '+where+' \
1528 AND move.id = line.move_id \
1529 GROUP BY line.tax_code_id',
1530 (parent_ids,) + where_params)
1531 res=dict(cr.fetchall())
1532 obj_precision = self.pool.get('decimal.precision')
1533 for record in self.browse(cr, uid, ids, context):
1534 def _rec_get(record):
1535 amount = res.get(record.id, 0.0)
1536 for rec in record.child_ids:
1537 amount += _rec_get(rec) * rec.sign
1539 res[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1542 def _sum_year(self, cr, uid, ids, name, args, context=None):
1545 move_state = ('posted', )
1546 if context.get('state', 'all') == 'all':
1547 move_state = ('draft', 'posted', )
1548 if context.get('fiscalyear_id', False):
1549 fiscalyear_id = context['fiscalyear_id']
1551 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1555 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1557 where = ' AND line.period_id IN %s AND move.state IN %s '
1558 where_params = (tuple(pids), move_state)
1559 return self._sum(cr, uid, ids, name, args, context,
1560 where=where, where_params=where_params)
1562 def _sum_period(self, cr, uid, ids, name, args, context):
1565 move_state = ('posted', )
1566 if context.get('state', False) == 'all':
1567 move_state = ('draft', 'posted', )
1568 if context.get('period_id', False):
1569 period_id = context['period_id']
1571 period_id = self.pool.get('account.period').find(cr, uid)
1573 return dict.fromkeys(ids, 0.0)
1574 period_id = period_id[0]
1575 return self._sum(cr, uid, ids, name, args, context,
1576 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1578 _name = 'account.tax.code'
1579 _description = 'Tax Code'
1582 'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1583 'code': fields.char('Case Code', size=64),
1584 'info': fields.text('Description'),
1585 'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1586 'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1587 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1588 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1589 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1590 'company_id': fields.many2one('res.company', 'Company', required=True),
1591 'sign': fields.float('Coefficent for parent', required=True, help='You can specify here the coefficient that will be used when consolidating the amount of this case into its parent. For example, set 1/-1 if you want to add/substract it.'),
1592 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"),
1596 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1601 ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1602 return self.name_get(cr, user, ids, context)
1605 def name_get(self, cr, uid, ids, context=None):
1606 if isinstance(ids, (int, long)):
1610 if isinstance(ids, (int, long)):
1612 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1613 return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1616 def _default_company(self, cr, uid, context={}):
1617 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1619 return user.company_id.id
1620 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1622 'company_id': _default_company,
1624 'notprintable': False,
1627 def copy(self, cr, uid, id, default=None, context=None):
1630 default = default.copy()
1631 default.update({'line_ids': []})
1632 return super(account_tax_code, self).copy(cr, uid, id, default, context)
1634 _check_recursion = check_cycle
1636 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1638 _order = 'code,name'
1641 class account_tax(osv.osv):
1645 Type: percent, fixed, none, code
1646 PERCENT: tax = price * amount
1647 FIXED: tax = price + amount
1649 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1650 return result in the context
1651 Ex: result=round(price_unit*0.21,4)
1653 _name = 'account.tax'
1654 _description = 'Tax'
1656 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1657 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the tax lines from the lowest sequences to the higher ones. The order is important if you have a tax with several tax children. In this case, the evaluation order is important."),
1658 'amount': fields.float('Amount', required=True, digits_compute=dp.get_precision('Account'), help="For taxes of type percentage, enter % ratio between 0-1."),
1659 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the tax without removing it."),
1660 'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1661 help="The computation method for the tax amount."),
1662 'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1663 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1664 'domain':fields.char('Domain', size=32, help="This field is only used if you develop your own module allowing developers to create specific taxes in a custom domain."),
1665 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1666 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1667 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1668 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1669 'child_depend':fields.boolean('Tax on Children', help="Set if the tax computation is based on the computation of child taxes rather than on the total amount."),
1670 'python_compute':fields.text('Python Code'),
1671 'python_compute_inv':fields.text('Python Code (reverse)'),
1672 'python_applicable':fields.text('Python Code'),
1675 # Fields used for the VAT declaration
1677 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1678 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1679 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1680 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1682 # Same fields for refund invoices
1684 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1685 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1686 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1687 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1688 'include_base_amount': fields.boolean('Included in base amount', help="Indicates if the amount of tax must be included in the base amount for the computation of the next taxes"),
1689 'company_id': fields.many2one('res.company', 'Company', required=True),
1690 'description': fields.char('Tax Code',size=32),
1691 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1692 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1696 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1698 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1699 result format: {[(id, name), (id, name), ...]}
1701 @param cr: A database cursor
1702 @param user: ID of the user currently logged in
1703 @param name: name to search
1704 @param args: other arguments
1705 @param operator: default operator is 'ilike', it can be changed
1706 @param context: context arguments, like lang, time zone
1707 @param limit: Returns first 'n' ids of complete result, default is 80.
1709 @return: Returns a list of tupples containing id and name
1716 ids = self.search(cr, user, args, limit=limit, context=context)
1717 return self.name_get(cr, user, ids, context=context)
1719 def write(self, cr, uid, ids, vals, context=None):
1720 if vals.get('type', False) and vals['type'] in ('none', 'code'):
1721 vals.update({'amount': 0.0})
1722 return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1724 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1725 journal_pool = self.pool.get('account.journal')
1727 if context and context.has_key('type'):
1728 if context.get('type') in ('out_invoice','out_refund'):
1729 args += [('type_tax_use','in',['sale','all'])]
1730 elif context.get('type') in ('in_invoice','in_refund'):
1731 args += [('type_tax_use','in',['purchase','all'])]
1733 if context and context.has_key('journal_id'):
1734 journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1735 if journal.type in ('sale', 'purchase'):
1736 args += [('type_tax_use','in',[journal.type,'all'])]
1738 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1740 def name_get(self, cr, uid, ids, context=None):
1744 for record in self.read(cr, uid, ids, ['description','name'], context=context):
1745 name = record['description'] and record['description'] or record['name']
1746 res.append((record['id'],name ))
1749 def _default_company(self, cr, uid, context=None):
1750 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1752 return user.company_id.id
1753 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1756 'python_compute': '''# price_unit\n# address: res.partner.address object or False\n# product: product.product object or None\n# partner: res.partner object or None\n\nresult = price_unit * 0.10''',
1757 'python_compute_inv': '''# price_unit\n# address: res.partner.address object or False\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
1758 'applicable_type': 'true',
1763 'type_tax_use': 'all',
1769 'include_base_amount': False,
1770 'company_id': _default_company,
1774 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1776 obj_partener_address = self.pool.get('res.partner.address')
1778 if tax.applicable_type=='code':
1779 localdict = {'price_unit':price_unit, 'address':obj_partener_address.browse(cr, uid, address_id), 'product':product, 'partner':partner}
1780 exec tax.python_applicable in localdict
1781 if localdict.get('result', False):
1787 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1788 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1790 cur_price_unit=price_unit
1791 obj_partener_address = self.pool.get('res.partner.address')
1793 # we compute the amount for the current tax object and append it to the result
1795 data = {'id':tax.id,
1796 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1797 'account_collected_id':tax.account_collected_id.id,
1798 'account_paid_id':tax.account_paid_id.id,
1799 'base_code_id': tax.base_code_id.id,
1800 'ref_base_code_id': tax.ref_base_code_id.id,
1801 'sequence': tax.sequence,
1802 'base_sign': tax.base_sign,
1803 'tax_sign': tax.tax_sign,
1804 'ref_base_sign': tax.ref_base_sign,
1805 'ref_tax_sign': tax.ref_tax_sign,
1806 'price_unit': cur_price_unit,
1807 'tax_code_id': tax.tax_code_id.id,
1808 'ref_tax_code_id': tax.ref_tax_code_id.id,
1811 if tax.type=='percent':
1812 amount = cur_price_unit * tax.amount
1813 data['amount'] = amount
1815 elif tax.type=='fixed':
1816 data['amount'] = tax.amount
1817 data['tax_amount']=quantity
1818 # data['amount'] = quantity
1819 elif tax.type=='code':
1820 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1821 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1822 exec tax.python_compute in localdict
1823 amount = localdict['result']
1824 data['amount'] = amount
1825 elif tax.type=='balance':
1826 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1827 data['balance'] = cur_price_unit
1829 amount2 = data.get('amount', 0.0)
1831 if tax.child_depend:
1834 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1835 res.extend(child_tax)
1836 if tax.child_depend:
1838 for name in ('base','ref_base'):
1839 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1840 r[name+'_code_id'] = latest[name+'_code_id']
1841 r[name+'_sign'] = latest[name+'_sign']
1842 r['price_unit'] = latest['price_unit']
1843 latest[name+'_code_id'] = False
1844 for name in ('tax','ref_tax'):
1845 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1846 r[name+'_code_id'] = latest[name+'_code_id']
1847 r[name+'_sign'] = latest[name+'_sign']
1848 r['amount'] = data['amount']
1849 latest[name+'_code_id'] = False
1850 if tax.include_base_amount:
1851 cur_price_unit+=amount2
1854 def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1857 'total': 0.0, # Total without taxes
1858 'total_included: 0.0, # Total with taxes
1859 'taxes': [] # List of taxes, see compute for the format
1862 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1863 totalin = totalex = round(price_unit * quantity, precision)
1867 if tax.price_include:
1871 tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
1873 totalex -= r.get('amount', 0.0)
1876 totlex_qty=totalex/quantity
1879 tex = self._compute(cr, uid, tex, totlex_qty, quantity, address_id=address_id, product=product, partner=partner)
1881 totalin += r.get('amount', 0.0)
1884 'total_included': totalin,
1888 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1889 logger = netsvc.Logger()
1890 logger.notifyChannel("warning", netsvc.LOG_WARNING,
1891 "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
1892 return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
1894 def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1896 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1900 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1901 one tax for each tax id in IDS and their childs
1903 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1905 precision_pool = self.pool.get('decimal.precision')
1907 if r.get('balance',False):
1908 r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
1910 r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
1911 total += r['amount']
1914 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1915 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1916 obj_partener_address = self.pool.get('res.partner.address')
1919 cur_price_unit = price_unit
1921 tax_parent_tot = 0.0
1923 if (tax.type=='percent') and not tax.include_base_amount:
1924 tax_parent_tot += tax.amount
1927 if (tax.type=='fixed') and not tax.include_base_amount:
1928 cur_price_unit -= tax.amount
1931 if tax.type=='percent':
1932 if tax.include_base_amount:
1933 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1935 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1937 elif tax.type=='fixed':
1940 elif tax.type=='code':
1941 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1942 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1943 exec tax.python_compute_inv in localdict
1944 amount = localdict['result']
1945 elif tax.type=='balance':
1946 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1948 if tax.include_base_amount:
1949 cur_price_unit -= amount
1958 'account_collected_id': tax.account_collected_id.id,
1959 'account_paid_id': tax.account_paid_id.id,
1960 'base_code_id': tax.base_code_id.id,
1961 'ref_base_code_id': tax.ref_base_code_id.id,
1962 'sequence': tax.sequence,
1963 'base_sign': tax.base_sign,
1964 'tax_sign': tax.tax_sign,
1965 'ref_base_sign': tax.ref_base_sign,
1966 'ref_tax_sign': tax.ref_tax_sign,
1967 'price_unit': cur_price_unit,
1968 'tax_code_id': tax.tax_code_id.id,
1969 'ref_tax_code_id': tax.ref_tax_code_id.id,
1972 if tax.child_depend:
1976 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1977 res.extend(parent_tax)
1982 total += r['amount']
1984 r['price_unit'] -= total
1988 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1990 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1991 Price Unit is a VAT included price
1995 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1996 one tax for each tax id in IDS and their childs
1998 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
2000 obj_precision = self.pool.get('decimal.precision')
2002 prec = obj_precision.precision_get(cr, uid, 'Account')
2003 if r.get('balance',False):
2004 r['amount'] = round(r['balance'] * quantity, prec) - total
2006 r['amount'] = round(r['amount'] * quantity, prec)
2007 total += r['amount']
2011 # ---------------------------------------------------------
2012 # Account Entries Models
2013 # ---------------------------------------------------------
2015 class account_model(osv.osv):
2016 _name = "account.model"
2017 _description = "Account Model"
2019 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2020 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2021 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2022 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2023 'legend': fields.text('Legend', readonly=True, size=100),
2027 'legend': lambda self, cr, uid, context:_('You can specify year, month and date in the name of the model using the following labels:\n\n%(year)s: To Specify Year \n%(month)s: To Specify Month \n%(date)s: Current Date\n\ne.g. My model on %(date)s'),
2028 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.model', context=c),
2030 def generate(self, cr, uid, ids, datas={}, context=None):
2033 account_move_obj = self.pool.get('account.move')
2034 account_move_line_obj = self.pool.get('account.move.line')
2035 pt_obj = self.pool.get('account.payment.term')
2040 if datas.get('date', False):
2041 context.update({'date': datas['date']})
2043 period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
2045 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
2046 period_id = period_id[0]
2048 for model in self.browse(cr, uid, ids, context):
2049 entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')}
2050 move_id = account_move_obj.create(cr, uid, {
2051 'ref': entry['name'],
2052 'period_id': period_id,
2053 'journal_id': model.journal_id.id,
2054 'date': context.get('date',time.strftime('%Y-%m-%d'))
2056 move_ids.append(move_id)
2057 for line in model.lines_id:
2058 analytic_account_id = False
2059 if line.analytic_account_id:
2060 if not model.journal_id.analytic_journal_id:
2061 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2062 analytic_account_id = line.analytic_account_id.id
2065 'journal_id': model.journal_id.id,
2066 'period_id': period_id,
2067 'analytic_account_id': analytic_account_id
2070 date_maturity = time.strftime('%Y-%m-%d')
2071 if line.date_maturity == 'partner':
2072 if not line.partner_id:
2073 raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term! \
2074 \nPlease define partner on it!"%(line.name, model.name)))
2075 if line.partner_id.property_payment_term:
2076 payment_term_id = line.partner_id.property_payment_term.id
2077 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2079 pterm_list = [l[0] for l in pterm_list]
2081 date_maturity = pterm_list[-1]
2085 'quantity': line.quantity,
2086 'debit': line.debit,
2087 'credit': line.credit,
2088 'account_id': line.account_id.id,
2090 'partner_id': line.partner_id.id,
2091 'date': context.get('date',time.strftime('%Y-%m-%d')),
2092 'date_maturity': date_maturity
2095 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
2096 account_move_line_obj.create(cr, uid, val, context=c)
2102 class account_model_line(osv.osv):
2103 _name = "account.model.line"
2104 _description = "Account Model Entries"
2106 'name': fields.char('Name', size=64, required=True),
2107 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
2108 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries"),
2109 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2110 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2111 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2112 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2113 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2114 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2115 'currency_id': fields.many2one('res.currency', 'Currency'),
2116 'partner_id': fields.many2one('res.partner', 'Partner'),
2117 'date_maturity': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Maturity date', help="The maturity date of the generated entries for this model. You can choose between the creation date or the creation date of the entries plus the partner payment terms."),
2120 _sql_constraints = [
2121 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
2122 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
2124 account_model_line()
2126 # ---------------------------------------------------------
2127 # Account Subscription
2128 # ---------------------------------------------------------
2131 class account_subscription(osv.osv):
2132 _name = "account.subscription"
2133 _description = "Account Subscription"
2135 'name': fields.char('Name', size=64, required=True),
2136 'ref': fields.char('Reference', size=16),
2137 'model_id': fields.many2one('account.model', 'Model', required=True),
2139 'date_start': fields.date('Start Date', required=True),
2140 'period_total': fields.integer('Number of Periods', required=True),
2141 'period_nbr': fields.integer('Period', required=True),
2142 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2143 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
2145 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2148 'date_start': time.strftime('%Y-%m-%d'),
2149 'period_type': 'month',
2154 def state_draft(self, cr, uid, ids, context={}):
2155 self.write(cr, uid, ids, {'state':'draft'})
2158 def check(self, cr, uid, ids, context={}):
2160 for sub in self.browse(cr, uid, ids, context):
2162 for line in sub.lines_id:
2163 if not line.move_id.id:
2167 todone.append(sub.id)
2169 self.write(cr, uid, todone, {'state':'done'})
2172 def remove_line(self, cr, uid, ids, context={}):
2174 for sub in self.browse(cr, uid, ids, context):
2175 for line in sub.lines_id:
2176 if not line.move_id.id:
2177 toremove.append(line.id)
2179 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2180 self.write(cr, uid, ids, {'state':'draft'})
2183 def compute(self, cr, uid, ids, context={}):
2184 for sub in self.browse(cr, uid, ids, context):
2186 for i in range(sub.period_total):
2187 self.pool.get('account.subscription.line').create(cr, uid, {
2189 'subscription_id': sub.id,
2191 if sub.period_type=='day':
2192 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2193 if sub.period_type=='month':
2194 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2195 if sub.period_type=='year':
2196 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2197 self.write(cr, uid, ids, {'state':'running'})
2199 account_subscription()
2201 class account_subscription_line(osv.osv):
2202 _name = "account.subscription.line"
2203 _description = "Account Subscription Line"
2205 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2206 'date': fields.date('Date', required=True),
2207 'move_id': fields.many2one('account.move', 'Entry'),
2210 def move_create(self, cr, uid, ids, context=None):
2213 obj_model = self.pool.get('account.model')
2214 for line in self.browse(cr, uid, ids, context=context):
2218 move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2219 tocheck[line.subscription_id.id] = True
2220 self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2221 all_moves.extend(move_ids)
2223 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2227 account_subscription_line()
2229 # ---------------------------------------------------------------
2230 # Account Templates: Account, Tax, Tax Code and chart. + Wizard
2231 # ---------------------------------------------------------------
2233 class account_tax_template(osv.osv):
2234 _name = 'account.tax.template'
2235 account_tax_template()
2237 class account_account_template(osv.osv):
2239 _name = "account.account.template"
2240 _description ='Templates for Accounts'
2243 'name': fields.char('Name', size=128, required=True, select=True),
2244 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2245 'code': fields.char('Code', size=64),
2246 'type': fields.selection([
2247 ('receivable','Receivable'),
2248 ('payable','Payable'),
2250 ('consolidation','Consolidation'),
2251 ('liquidity','Liquidity'),
2252 ('other','Regular'),
2253 ('closed','Closed'),
2254 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2255 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2256 "can have children accounts for multi-company consolidations, payable/receivable are for "\
2257 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2258 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2259 help="These types are defined according to your country. The type contains more information "\
2260 "about the account and its specificities."),
2261 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2262 'shortcut': fields.char('Shortcut', size=12),
2263 'note': fields.text('Note'),
2264 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2265 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2266 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2267 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2276 _check_recursion = check_cycle
2278 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
2282 def name_get(self, cr, uid, ids, context={}):
2285 reads = self.read(cr, uid, ids, ['name','code'], context)
2287 for record in reads:
2288 name = record['name']
2290 name = record['code']+' '+name
2291 res.append((record['id'],name ))
2294 account_account_template()
2296 class account_add_tmpl_wizard(osv.osv_memory):
2297 """Add one more account from the template.
2299 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2300 _name = 'account.addtmpl.wizard'
2302 def _get_def_cparent(self, cr, uid, context):
2303 acc_obj=self.pool.get('account.account')
2304 tmpl_obj=self.pool.get('account.account.template')
2305 tids=tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2306 if not tids or not tids[0]['parent_id']:
2308 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2310 if not ptids or not ptids[0]['code']:
2311 raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
2312 res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2314 return res and res[0] or False
2317 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2320 'cparent_id': _get_def_cparent,
2323 def action_create(self,cr,uid,ids,context=None):
2324 acc_obj = self.pool.get('account.account')
2325 tmpl_obj = self.pool.get('account.account.template')
2326 data = self.read(cr, uid, ids)
2327 company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2328 account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2330 'name': account_template.name,
2331 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2332 'code': account_template.code,
2333 'type': account_template.type,
2334 'user_type': account_template.user_type and account_template.user_type.id or False,
2335 'reconcile': account_template.reconcile,
2336 'shortcut': account_template.shortcut,
2337 'note': account_template.note,
2338 'parent_id': data[0]['cparent_id'],
2339 'company_id': company_id,
2341 acc_obj.create(cr, uid, vals)
2342 return {'type':'state', 'state': 'end' }
2344 def action_cancel(self, cr, uid, ids, context=None):
2345 return { 'type': 'state', 'state': 'end' }
2347 account_add_tmpl_wizard()
2349 class account_tax_code_template(osv.osv):
2351 _name = 'account.tax.code.template'
2352 _description = 'Tax Code Template'
2356 'name': fields.char('Tax Case Name', size=64, required=True),
2357 'code': fields.char('Case Code', size=64),
2358 'info': fields.text('Description'),
2359 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2360 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2361 'sign': fields.float('Sign For Parent', required=True),
2362 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want any VAT related to this Tax Code to appear on invoices"),
2367 'notprintable': False,
2370 def name_get(self, cr, uid, ids, context=None):
2373 if isinstance(ids, (int, long)):
2375 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2376 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2379 _check_recursion = check_cycle
2381 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2383 _order = 'code,name'
2384 account_tax_code_template()
2387 class account_chart_template(osv.osv):
2388 _name="account.chart.template"
2389 _description= "Templates for Account Chart"
2392 'name': fields.char('Name', size=64, required=True),
2393 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
2394 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
2395 'tax_template_ids': fields.one2many('account.tax.template', 'chart_template_id', 'Tax Template List', help='List of all the taxes that have to be installed by the wizard'),
2396 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
2397 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2398 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2399 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2400 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2401 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2402 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2403 'property_reserve_and_surplus_account': fields.many2one('account.account.template', 'Reserve and Profit/Loss Account', domain=[('type', '=', 'payable')], help='This Account is used for transferring Profit/Loss(If It is Profit: Amount will be added, Loss: Amount will be deducted.), Which is calculated from Profilt & Loss Report'),
2406 account_chart_template()
2408 class account_tax_template(osv.osv):
2410 _name = 'account.tax.template'
2411 _description = 'Templates for Taxes'
2414 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2415 'name': fields.char('Tax Name', size=64, required=True),
2416 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the taxes lines from lower sequences to higher ones. The order is important if you have a tax that has several tax children. In this case, the evaluation order is important."),
2417 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2418 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
2419 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True, help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
2420 'domain':fields.char('Domain', size=32, help="This field is only used if you develop your own module allowing developers to create specific taxes in a custom domain."),
2421 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2422 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2423 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2424 'child_depend':fields.boolean('Tax on Children', help="Set if the tax computation is based on the computation of child taxes rather than on the total amount."),
2425 'python_compute':fields.text('Python Code'),
2426 'python_compute_inv':fields.text('Python Code (reverse)'),
2427 'python_applicable':fields.text('Python Code'),
2430 # Fields used for the VAT declaration
2432 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2433 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2434 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2435 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2437 # Same fields for refund invoices
2439 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2440 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2441 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2442 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2443 'include_base_amount': fields.boolean('Include in Base Amount', help="Set if the amount of tax must be included in the base amount before computing the next taxes."),
2444 'description': fields.char('Internal Name', size=32),
2445 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,)
2448 def name_get(self, cr, uid, ids, context={}):
2452 for record in self.read(cr, uid, ids, ['description','name'], context):
2453 name = record['description'] and record['description'] or record['name']
2454 res.append((record['id'],name ))
2457 def _default_company(self, cr, uid, context={}):
2458 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2460 return user.company_id.id
2461 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2464 'python_compute': lambda *a: '''# price_unit\n# address: res.partner.address object or False\n# product: product.product object or None\n# partner: res.partner object or None\n\nresult = price_unit * 0.10''',
2465 'python_compute_inv': lambda *a: '''# price_unit\n# address: res.partner.address object or False\n# product: product.product object or False\n\nresult = price_unit * 0.10''',
2466 'applicable_type': 'true',
2474 'include_base_amount': False,
2475 'type_tax_use': 'all',
2480 account_tax_template()
2482 # Fiscal Position Templates
2484 class account_fiscal_position_template(osv.osv):
2485 _name = 'account.fiscal.position.template'
2486 _description = 'Template for Fiscal Position'
2489 'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
2490 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2491 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2492 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2495 account_fiscal_position_template()
2497 class account_fiscal_position_tax_template(osv.osv):
2498 _name = 'account.fiscal.position.tax.template'
2499 _description = 'Template Tax Fiscal Position'
2500 _rec_name = 'position_id'
2503 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2504 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2505 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2508 account_fiscal_position_tax_template()
2510 class account_fiscal_position_account_template(osv.osv):
2511 _name = 'account.fiscal.position.account.template'
2512 _description = 'Template Account Fiscal Mapping'
2513 _rec_name = 'position_id'
2515 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2516 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2517 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2520 account_fiscal_position_account_template()
2522 # Multi charts of Accounts wizard
2524 class wizard_multi_charts_accounts(osv.osv_memory):
2526 Create a new account chart for a company.
2529 * an account chart template
2530 * a number of digits for formatting code of non-view accounts
2531 * a list of bank accounts owned by the company
2533 * generates all accounts from the template and assigns them to the right company
2534 * generates all taxes and tax codes, changing account assignations
2535 * generates all accounting properties and assigns them correctly
2537 _name='wizard.multi.charts.accounts'
2538 _inherit = 'res.config'
2541 'company_id':fields.many2one('res.company', 'Company', required=True),
2542 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2543 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts', required=True),
2544 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2545 'seq_journal':fields.boolean('Separated Journal Sequences', help="Check this box if you want to use a different sequence for each created journal. Otherwise, all will use the same sequence."),
2548 def _get_chart(self, cr, uid, context={}):
2549 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2554 def _get_default_accounts(self, cr, uid, context=None):
2555 accounts = [{'acc_name':'Current','account_type':'bank'},
2556 {'acc_name':'Deposit','account_type':'bank'},
2557 {'acc_name':'Cash','account_type':'cash'}]
2561 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, [uid], c)[0].company_id.id,
2562 'chart_template_id': _get_chart,
2563 'bank_accounts_id': _get_default_accounts,
2568 def execute(self, cr, uid, ids, context=None):
2569 obj_multi = self.browse(cr, uid, ids[0])
2570 obj_acc = self.pool.get('account.account')
2571 obj_acc_tax = self.pool.get('account.tax')
2572 obj_journal = self.pool.get('account.journal')
2573 obj_sequence = self.pool.get('ir.sequence')
2574 obj_acc_template = self.pool.get('account.account.template')
2575 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2576 obj_fiscal_position = self.pool.get('account.fiscal.position')
2577 obj_data = self.pool.get('ir.model.data')
2578 analytic_journal_obj = self.pool.get('account.analytic.journal')
2579 obj_tax_code = self.pool.get('account.tax.code')
2581 obj_acc_root = obj_multi.chart_template_id.account_root_id
2582 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2583 company_id = obj_multi.company_id.id
2586 acc_template_ref = {}
2587 tax_template_ref = {}
2588 tax_code_template_ref = {}
2591 #create all the tax code
2592 children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2593 children_tax_code_template.sort()
2594 for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2596 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2597 'code': tax_code_template.code,
2598 'info': tax_code_template.info,
2599 'parent_id': tax_code_template.parent_id and ((tax_code_template.parent_id.id in tax_code_template_ref) and tax_code_template_ref[tax_code_template.parent_id.id]) or False,
2600 'company_id': company_id,
2601 'sign': tax_code_template.sign,
2603 new_tax_code = obj_tax_code.create(cr, uid, vals)
2604 #recording the new tax code to do the mapping
2605 tax_code_template_ref[tax_code_template.id] = new_tax_code
2608 for tax in obj_multi.chart_template_id.tax_template_ids:
2612 'sequence': tax.sequence,
2613 'amount':tax.amount,
2615 'applicable_type': tax.applicable_type,
2616 'domain':tax.domain,
2617 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2618 'child_depend': tax.child_depend,
2619 'python_compute': tax.python_compute,
2620 'python_compute_inv': tax.python_compute_inv,
2621 'python_applicable': tax.python_applicable,
2622 'base_code_id': tax.base_code_id and ((tax.base_code_id.id in tax_code_template_ref) and tax_code_template_ref[tax.base_code_id.id]) or False,
2623 'tax_code_id': tax.tax_code_id and ((tax.tax_code_id.id in tax_code_template_ref) and tax_code_template_ref[tax.tax_code_id.id]) or False,
2624 'base_sign': tax.base_sign,
2625 'tax_sign': tax.tax_sign,
2626 'ref_base_code_id': tax.ref_base_code_id and ((tax.ref_base_code_id.id in tax_code_template_ref) and tax_code_template_ref[tax.ref_base_code_id.id]) or False,
2627 'ref_tax_code_id': tax.ref_tax_code_id and ((tax.ref_tax_code_id.id in tax_code_template_ref) and tax_code_template_ref[tax.ref_tax_code_id.id]) or False,
2628 'ref_base_sign': tax.ref_base_sign,
2629 'ref_tax_sign': tax.ref_tax_sign,
2630 'include_base_amount': tax.include_base_amount,
2631 'description':tax.description,
2632 'company_id': company_id,
2633 'type_tax_use': tax.type_tax_use
2635 new_tax = obj_acc_tax.create(cr, uid, vals_tax)
2636 #as the accounts have not been created yet, we have to wait before filling these fields
2637 todo_dict[new_tax] = {
2638 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2639 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2641 tax_template_ref[tax.id] = new_tax
2643 #deactivate the parent_store functionnality on account_account for rapidity purpose
2644 self.pool._init = True
2646 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2647 children_acc_template.sort()
2648 for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2650 for tax in account_template.tax_ids:
2651 tax_ids.append(tax_template_ref[tax.id])
2652 #create the account_account
2654 dig = obj_multi.code_digits
2655 code_main = account_template.code and len(account_template.code) or 0
2656 code_acc = account_template.code or ''
2657 if code_main>0 and code_main<=dig and account_template.type != 'view':
2658 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2660 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2661 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2663 'type': account_template.type,
2664 'user_type': account_template.user_type and account_template.user_type.id or False,
2665 'reconcile': account_template.reconcile,
2666 'shortcut': account_template.shortcut,
2667 'note': account_template.note,
2668 'parent_id': account_template.parent_id and ((account_template.parent_id.id in acc_template_ref) and acc_template_ref[account_template.parent_id.id]) or False,
2669 'tax_ids': [(6,0,tax_ids)],
2670 'company_id': company_id,
2672 new_account = obj_acc.create(cr, uid, vals)
2673 acc_template_ref[account_template.id] = new_account
2674 #reactivate the parent_store functionnality on account_account
2675 self.pool._init = False
2676 self.pool.get('account.account')._parent_store_compute(cr)
2678 for key,value in todo_dict.items():
2679 if value['account_collected_id'] or value['account_paid_id']:
2680 obj_acc_tax.write(cr, uid, [key], {
2681 'account_collected_id': acc_template_ref[value['account_collected_id']],
2682 'account_paid_id': acc_template_ref[value['account_paid_id']],
2685 # Creating Journals Sales and Purchase
2687 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_sp_journal_view')])
2688 data = obj_data.browse(cr, uid, data_id[0])
2689 view_id = data.res_id
2691 seq_id = obj_sequence.search(cr, uid, [('name','=','Account Journal')])[0]
2693 if obj_multi.seq_journal:
2694 seq_id_sale = obj_sequence.search(cr, uid, [('name','=','Sale Journal')])[0]
2695 seq_id_purchase = obj_sequence.search(cr, uid, [('name','=','Purchase Journal')])[0]
2697 seq_id_sale = seq_id
2698 seq_id_purchase = seq_id
2700 vals_journal['view_id'] = view_id
2703 analitical_sale_ids = analytic_journal_obj.search(cr,uid,[('type','=','sale')])
2704 analitical_journal_sale = analitical_sale_ids and analitical_sale_ids[0] or False
2706 vals_journal['name'] = _('Sales Journal')
2707 vals_journal['type'] = 'sale'
2708 vals_journal['code'] = _('SAJ')
2709 vals_journal['sequence_id'] = seq_id_sale
2710 vals_journal['company_id'] = company_id
2711 vals_journal['analytic_journal_id'] = analitical_journal_sale
2713 if obj_multi.chart_template_id.property_account_receivable:
2714 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2715 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2717 obj_journal.create(cr,uid,vals_journal)
2720 analitical_purchase_ids = analytic_journal_obj.search(cr,uid,[('type','=','purchase')])
2721 analitical_journal_purchase = analitical_purchase_ids and analitical_purchase_ids[0] or False
2723 vals_journal['name'] = _('Purchase Journal')
2724 vals_journal['type'] = 'purchase'
2725 vals_journal['code'] = _('EXJ')
2726 vals_journal['sequence_id'] = seq_id_purchase
2727 vals_journal['view_id'] = view_id
2728 vals_journal['company_id'] = company_id
2729 vals_journal['analytic_journal_id'] = analitical_journal_purchase
2731 if obj_multi.chart_template_id.property_account_payable:
2732 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2733 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2735 obj_journal.create(cr,uid,vals_journal)
2738 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')])
2739 data = obj_data.browse(cr, uid, data_id[0])
2740 view_id_cash = data.res_id
2742 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view_multi')])
2743 data = obj_data.browse(cr, uid, data_id[0])
2744 view_id_cur = data.res_id
2745 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2748 for line in obj_multi.bank_accounts_id:
2749 #create the account_account for this bank journal
2751 dig = obj_multi.code_digits
2752 if ref_acc_bank.code:
2754 new_code = str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num)
2756 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)),'0')) + str(current_num)
2759 'currency_id': line.currency_id and line.currency_id.id or False,
2762 'user_type': account_template.user_type and account_template.user_type.id or False,
2764 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2765 'company_id': company_id,
2767 acc_cash_id = obj_acc.create(cr,uid,vals)
2769 if obj_multi.seq_journal:
2771 'name': _('Bank Journal ') + vals['name'],
2772 'code': 'account.journal',
2774 seq_id = obj_sequence.create(cr,uid,vals_seq)
2776 #create the bank journal
2777 analitical_bank_ids = analytic_journal_obj.search(cr,uid,[('type','=','situation')])
2778 analitical_journal_bank = analitical_bank_ids and analitical_bank_ids[0] or False
2780 vals_journal['name']= vals['name']
2781 vals_journal['code']= _('BNK') + str(current_num)
2782 vals_journal['sequence_id'] = seq_id
2783 vals_journal['type'] = 'cash'
2784 vals_journal['company_id'] = company_id
2785 vals_journal['analytic_journal_id'] = analitical_journal_bank
2787 if line.currency_id:
2788 vals_journal['view_id'] = view_id_cur
2789 vals_journal['currency'] = line.currency_id.id
2791 vals_journal['view_id'] = view_id_cash
2792 vals_journal['default_credit_account_id'] = acc_cash_id
2793 vals_journal['default_debit_account_id'] = acc_cash_id
2794 obj_journal.create(cr, uid, vals_journal)
2797 #create the properties
2798 property_obj = self.pool.get('ir.property')
2799 fields_obj = self.pool.get('ir.model.fields')
2802 ('property_account_receivable','res.partner','account.account'),
2803 ('property_account_payable','res.partner','account.account'),
2804 ('property_account_expense_categ','product.category','account.account'),
2805 ('property_account_income_categ','product.category','account.account'),
2806 ('property_account_expense','product.template','account.account'),
2807 ('property_account_income','product.template','account.account'),
2808 ('property_reserve_and_surplus_account','res.company','account.account')
2810 for record in todo_list:
2812 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2813 account = getattr(obj_multi.chart_template_id, record[0])
2814 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2817 'company_id': company_id,
2818 'fields_id': field[0],
2819 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2823 #the property exist: modify it
2824 property_obj.write(cr, uid, r, vals)
2826 #create the property
2827 property_obj.create(cr, uid, vals)
2829 fp_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)])
2833 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2834 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2836 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2839 'company_id': company_id,
2840 'name': position.name,
2842 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2844 for tax in position.tax_ids:
2846 'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2847 'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2848 'position_id': new_fp,
2850 obj_tax_fp.create(cr, uid, vals_tax)
2852 for acc in position.account_ids:
2854 'account_src_id': acc_template_ref[acc.account_src_id.id],
2855 'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2856 'position_id': new_fp,
2858 obj_ac_fp.create(cr, uid, vals_acc)
2860 wizard_multi_charts_accounts()
2862 class account_bank_accounts_wizard(osv.osv_memory):
2863 _name='account.bank.accounts.wizard'
2866 'acc_name':fields.char('Account Name.', size=64, required=True),
2867 'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2868 'currency_id':fields.many2one('res.currency', 'Currency'),
2869 'account_type':fields.selection([('cash','Cash'),('check','Check'),('bank','Bank')], 'Type', size=32),
2872 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
2875 account_bank_accounts_wizard()
2877 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: