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 ids = self.search(cr, user, [('code', operator, name.split()[0]), ('name', operator, name.split()[1])]+ args, limit=limit)
462 ids = self.search(cr, user, args, context=context, limit=limit)
463 return self.name_get(cr, user, ids, context=context)
465 def name_get(self, cr, uid, ids, context=None):
468 reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
471 name = record['name']
473 name = record['code'] + ' '+name
474 res.append((record['id'], name))
477 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
478 account = self.browse(cr, uid, id, context=context)
482 default = default.copy()
483 default['code'] = (account['code'] or '') + '(copy)'
486 if account.id in done_list:
488 done_list.append(account.id)
490 for child in account.child_id:
491 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
493 new_child_ids.append(child_ids)
494 default['child_parent_ids'] = [(6, 0, new_child_ids)]
496 default['child_parent_ids'] = False
497 return super(account_account, self).copy(cr, uid, id, default, context=context)
499 def _check_moves(self, cr, uid, ids, method, context=None):
500 line_obj = self.pool.get('account.move.line')
501 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
503 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
504 if method == 'write':
505 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
506 elif method == 'unlink':
507 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
508 #Checking whether the account is set as a property to any Partner or not
509 value = 'account.account,' + str(ids[0])
510 partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
512 raise osv.except_osv(_('Warning !'), _('You cannot remove/deactivate an account which is set as a property to any Partner.'))
515 def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
516 group1 = ['payable', 'receivable', 'other']
517 group2 = ['consolidation','view']
518 line_obj = self.pool.get('account.move.line')
519 for account in self.browse(cr, uid, ids, context=context):
520 old_type = account.type
521 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
522 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
523 #Check for 'Closed' type
524 if old_type == 'closed' and new_type !='closed':
525 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains account entries!"))
526 #Check for change From group1 to group2 and vice versa
527 if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
528 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from '%s' to '%s' type as it contains account entries!") % (old_type,new_type,))
531 def write(self, cr, uid, ids, vals, context=None):
535 if 'company_id' in vals:
536 move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
538 raise osv.except_osv(_('Warning !'), _('You cannot modify Company of account as its related record exist in Entry Lines'))
539 if 'active' in vals and not vals['active']:
540 self._check_moves(cr, uid, ids, "write", context=context)
541 if 'type' in vals.keys():
542 self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
543 return super(account_account, self).write(cr, uid, ids, vals, context=context)
545 def unlink(self, cr, uid, ids, context=None):
546 self._check_moves(cr, uid, ids, "unlink", context=context)
547 return super(account_account, self).unlink(cr, uid, ids, context=context)
551 class account_journal_view(osv.osv):
552 _name = "account.journal.view"
553 _description = "Journal View"
555 'name': fields.char('Journal View', size=64, required=True),
556 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
560 account_journal_view()
563 class account_journal_column(osv.osv):
565 def _col_get(self, cr, user, context=None):
567 cols = self.pool.get('account.move.line')._columns
569 if col in ('period_id', 'journal_id'):
571 result.append( (col, cols[col].string) )
575 _name = "account.journal.column"
576 _description = "Journal Column"
578 'name': fields.char('Column Name', size=64, required=True),
579 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
580 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
581 'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column."),
582 'required': fields.boolean('Required'),
583 'readonly': fields.boolean('Readonly'),
587 account_journal_column()
589 class account_journal(osv.osv):
590 _name = "account.journal"
591 _description = "Journal"
593 'name': fields.char('Journal Name', size=64, required=True, translate=True),
594 '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."),
595 '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,
596 help="Select 'Sale' for Sale journal to be used at the time of making invoice."\
597 " Select 'Purchase' for Purchase Journal to be used at the time of approving purchase order."\
598 " Select 'Cash' to be used at the time of making payment."\
599 " Select 'General' to be used at the time of stock input/output."\
600 " Select 'Situation' to be used at the time of making vouchers."),
601 'refund_journal': fields.boolean('Refund Journal', help='Fill this if the journal is to be used for refunds of invoices.'),
602 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
603 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
604 '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."),
605 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
606 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
607 '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."),
608 '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"),
609 '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."),
610 '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),
611 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
612 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
613 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
614 '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.'),
615 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
616 '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'),
620 'user_id': lambda self,cr,uid,context: uid,
621 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
624 def write(self, cr, uid, ids, vals, context=None):
625 if 'company_id' in vals:
626 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
628 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this journal as its related record exist in Entry Lines'))
629 return super(account_journal, self).write(cr, uid, ids, vals, context=context)
631 def create_sequence(self, cr, uid, vals, context=None):
633 Create new entry sequence for every new Joural
634 @param cr: cursor to database
635 @param user: id of current user
636 @param ids: list of record ids to be process
637 @param context: context arguments, like lang, time zone
638 @return: return a result
640 seq_pool = self.pool.get('ir.sequence')
641 seq_typ_pool = self.pool.get('ir.sequence.type')
644 code = vals['code'].lower()
650 seq_typ_pool.create(cr, uid, types)
656 'prefix': code + "/%(year)s/",
658 'number_increment': 1
660 return seq_pool.create(cr, uid, seq)
662 def create(self, cr, uid, vals, context=None):
663 if not 'sequence_id' in vals or not vals['sequence_id']:
664 vals.update({'sequence_id': self.create_sequence(cr, uid, vals, context)})
665 return super(account_journal, self).create(cr, uid, vals, context)
667 def name_get(self, cr, user, ids, context=None):
669 Returns a list of tupples containing id, name.
670 result format: {[(id, name), (id, name), ...]}
672 @param cr: A database cursor
673 @param user: ID of the user currently logged in
674 @param ids: list of ids for which name should be read
675 @param context: context arguments, like lang, time zone
677 @return: Returns a list of tupples containing id, name
679 result = self.browse(cr, user, ids, context)
684 name = "%s (%s)" % (rs.name, rs.currency.name)
685 res += [(rs.id, name)]
688 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
694 if context.get('journal_type', False):
695 args += [('type','=',context.get('journal_type'))]
697 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
699 ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
701 return self.name_get(cr, user, ids, context=context)
703 def onchange_type(self, cr, uid, ids, type, currency):
704 obj_data = self.pool.get('ir.model.data')
705 user_pool = self.pool.get('res.users')
708 'sale':'account_sp_journal_view',
709 'sale_refund':'account_sp_refund_journal_view',
710 'purchase':'account_sp_journal_view',
711 'purchase_refund':'account_sp_refund_journal_view',
712 'cash':'account_journal_bank_view',
713 'bank':'account_journal_bank_view',
714 'general':'account_journal_view',
715 'situation':'account_journal_view'
720 view_id = type_map.get(type, 'general')
722 user = user_pool.browse(cr, uid, uid)
723 if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
724 view_id = 'account_journal_bank_view_multi'
726 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
727 data = obj_data.browse(cr, uid, data_id[0])
730 'centralisation':type == 'situation',
731 'view_id':data.res_id,
740 class account_fiscalyear(osv.osv):
741 _name = "account.fiscalyear"
742 _description = "Fiscal Year"
744 'name': fields.char('Fiscal Year', size=64, required=True),
745 'code': fields.char('Code', size=6, required=True),
746 'company_id': fields.many2one('res.company', 'Company', required=True),
747 'date_start': fields.date('Start Date', required=True),
748 'date_stop': fields.date('End Date', required=True),
749 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
750 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True),
754 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
756 _order = "date_start"
758 def _check_fiscal_year(self, cr, uid, ids, context=None):
759 current_fiscal_yr = self.browse(cr, uid, ids, context=context)[0]
760 obj_fiscal_ids = self.search(cr, uid, [('company_id', '=', current_fiscal_yr.company_id.id)], context=context)
761 obj_fiscal_ids.remove(ids[0])
762 data_fiscal_yr = self.browse(cr, uid, obj_fiscal_ids, context=context)
764 for old_fy in data_fiscal_yr:
765 if old_fy.company_id.id == current_fiscal_yr['company_id'].id:
766 # Condition to check if the current fiscal year falls in between any previously defined fiscal year
767 if old_fy.date_start <= current_fiscal_yr['date_start'] <= old_fy.date_stop or \
768 old_fy.date_start <= current_fiscal_yr['date_stop'] <= old_fy.date_stop:
772 def _check_duration(self,cr,uid,ids):
773 obj_fy = self.browse(cr,uid,ids[0])
774 if obj_fy.date_stop < obj_fy.date_start:
779 (_check_duration, 'Error! The duration of the Fiscal Year is invalid. ', ['date_stop']),
780 (_check_fiscal_year, 'Error! You cannot define overlapping fiscal years',['date_start', 'date_stop'])
783 def create_period3(self,cr, uid, ids, context={}):
784 return self.create_period(cr, uid, ids, context, 3)
786 def create_period(self,cr, uid, ids, context={}, interval=1):
787 for fy in self.browse(cr, uid, ids, context):
788 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
789 while ds.strftime('%Y-%m-%d')<fy.date_stop:
790 de = ds + relativedelta(months=interval, days=-1)
792 if de.strftime('%Y-%m-%d')>fy.date_stop:
793 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
795 self.pool.get('account.period').create(cr, uid, {
796 'name': ds.strftime('%m/%Y'),
797 'code': ds.strftime('%m/%Y'),
798 'date_start': ds.strftime('%Y-%m-%d'),
799 'date_stop': de.strftime('%Y-%m-%d'),
800 'fiscalyear_id': fy.id,
802 ds = ds + relativedelta(months=interval)
805 def find(self, cr, uid, dt=None, exception=True, context={}):
807 dt = time.strftime('%Y-%m-%d')
808 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
811 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
816 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
823 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
825 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
826 return self.name_get(cr, user, ids, context=context)
830 class account_period(osv.osv):
831 _name = "account.period"
832 _description = "Account period"
834 'name': fields.char('Period Name', size=64, required=True),
835 'code': fields.char('Code', size=12),
836 'special': fields.boolean('Opening/Closing Period', size=12,
837 help="These periods can overlap."),
838 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
839 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
840 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
841 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True,
842 help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
843 'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
848 _order = "date_start"
850 def _check_duration(self,cr,uid,ids,context={}):
851 obj_period=self.browse(cr,uid,ids[0])
852 if obj_period.date_stop < obj_period.date_start:
856 def _check_year_limit(self,cr,uid,ids,context={}):
857 for obj_period in self.browse(cr,uid,ids):
858 if obj_period.special:
861 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
862 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
863 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
864 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
867 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
868 for period in self.browse(cr, uid, pids):
869 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
874 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
875 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
878 def next(self, cr, uid, period, step, context={}):
879 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
884 def find(self, cr, uid, dt=None, context={}):
886 dt = time.strftime('%Y-%m-%d')
887 #CHECKME: shouldn't we check the state of the period?
888 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
890 raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create a fiscal year.')%dt)
893 def action_draft(self, cr, uid, ids, *args):
896 cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
897 cr.execute('update account_period set state=%s where id=%s', (mode, id))
900 def name_search(self, cr, user, name, args=None, operator='ilike', context={}, limit=80):
907 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
909 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
910 return self.name_get(cr, user, ids, context=context)
912 def write(self, cr, uid, ids, vals, context={}):
913 if 'company_id' in vals:
914 move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
916 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines'))
917 return super(account_period, self).write(cr, uid, ids, vals, context=context)
919 def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
920 period_from = self.browse(cr, uid, period_from_id)
921 period_date_start = period_from.date_start
922 company1_id = period_from.company_id.id
923 period_to = self.browse(cr, uid, period_to_id)
924 period_date_stop = period_to.date_stop
925 company2_id = period_to.company_id.id
926 if company1_id != company2_id:
927 raise osv.except_osv(_('Error'), _('You should have chosen periods that belongs to the same company'))
928 if period_date_start > period_date_stop:
929 raise osv.except_osv(_('Error'), _('Start period should be smaller then End period'))
930 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id)])
934 class account_journal_period(osv.osv):
935 _name = "account.journal.period"
936 _description = "Journal Period"
938 def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
939 result = {}.fromkeys(ids, 'STOCK_NEW')
940 for r in self.read(cr, uid, ids, ['state']):
942 'draft': 'STOCK_NEW',
943 'printed': 'STOCK_PRINT_PREVIEW',
944 'done': 'STOCK_DIALOG_AUTHENTICATION',
945 }.get(r['state'], 'STOCK_NEW')
949 'name': fields.char('Journal-Period Name', size=64, required=True),
950 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
951 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
952 'icon': fields.function(_icon_get, method=True, string='Icon', type='char', size=32),
953 '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."),
954 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
955 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.'),
956 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
957 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company')
960 def _check(self, cr, uid, ids, context={}):
961 for obj in self.browse(cr, uid, ids, context):
962 cr.execute('select * from account_move_line where journal_id=%s and period_id=%s limit 1', (obj.journal_id.id, obj.period_id.id))
965 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
968 def write(self, cr, uid, ids, vals, context={}):
969 self._check(cr, uid, ids, context)
970 return super(account_journal_period, self).write(cr, uid, ids, vals, context)
972 def create(self, cr, uid, vals, context={}):
973 period_id=vals.get('period_id',False)
975 period = self.pool.get('account.period').browse(cr, uid,period_id)
976 vals['state']=period.state
977 return super(account_journal_period, self).create(cr, uid, vals, context)
979 def unlink(self, cr, uid, ids, context={}):
980 self._check(cr, uid, ids, context)
981 return super(account_journal_period, self).unlink(cr, uid, ids, context)
989 account_journal_period()
991 class account_fiscalyear(osv.osv):
992 _inherit = "account.fiscalyear"
993 _description = "Fiscal Year"
995 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
998 def copy(self, cr, uid, id, default={}, context=None):
1001 'end_journal_period_id': False
1003 return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
1005 account_fiscalyear()
1006 #----------------------------------------------------------
1008 #----------------------------------------------------------
1009 class account_move(osv.osv):
1010 _name = "account.move"
1011 _description = "Account Entry"
1014 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1016 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1017 result format: {[(id, name), (id, name), ...]}
1019 @param cr: A database cursor
1020 @param user: ID of the user currently logged in
1021 @param name: name to search
1022 @param args: other arguments
1023 @param operator: default operator is 'ilike', it can be changed
1024 @param context: context arguments, like lang, time zone
1025 @param limit: Returns first 'n' ids of complete result, default is 80.
1027 @return: Returns a list of tuples containing id and name
1036 ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1038 if not ids and name and type(name) == int:
1039 ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1042 ids += self.search(cr, user, args, limit=limit, context=context)
1044 return self.name_get(cr, user, ids, context=context)
1046 def name_get(self, cursor, user, ids, context=None):
1047 if isinstance(ids, (int, long)):
1052 data_move = self.pool.get('account.move').browse(cursor,user,ids)
1053 for move in data_move:
1054 if move.state=='draft':
1055 name = '*' + str(move.id)
1058 res.append((move.id, name))
1061 def _get_period(self, cr, uid, context):
1062 periods = self.pool.get('account.period').find(cr, uid)
1067 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1068 if not ids: return {}
1069 cr.execute( 'SELECT move_id, SUM(debit) '\
1070 'FROM account_move_line '\
1071 'WHERE move_id IN %s '\
1072 'GROUP BY move_id', (tuple(ids),))
1073 result = dict(cr.fetchall())
1075 result.setdefault(id, 0.0)
1078 def _search_amount(self, cr, uid, obj, name, args, context):
1082 if isinstance(cond[2],(list,tuple)):
1083 if cond[1] in ['in','not in']:
1084 amount = tuple(cond[2])
1088 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1091 cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1092 res_ids = set(id[0] for id in cr.fetchall())
1093 ids = ids and (ids & res_ids) or res_ids
1095 return [('id','in',tuple(ids))]
1097 return [('id', '=', '0')]
1100 'name': fields.char('Number', size=64, required=True),
1101 'ref': fields.char('Reference', size=64),
1102 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1103 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1104 'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'State', required=True, readonly=True,
1105 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.'),
1106 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1107 '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.'),
1108 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1109 'amount': fields.function(_amount_compute, method=True, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1110 'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}),
1111 'narration':fields.text('Narration'),
1112 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1117 'period_id': _get_period,
1118 'date': time.strftime('%Y-%m-%d'),
1119 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1122 def _check_centralisation(self, cursor, user, ids):
1123 for move in self.browse(cursor, user, ids):
1124 if move.journal_id.centralisation:
1125 move_ids = self.search(cursor, user, [
1126 ('period_id', '=', move.period_id.id),
1127 ('journal_id', '=', move.journal_id.id),
1129 if len(move_ids) > 1:
1133 def _check_period_journal(self, cursor, user, ids):
1134 for move in self.browse(cursor, user, ids):
1135 for line in move.line_id:
1136 if line.period_id.id != move.period_id.id:
1138 if line.journal_id.id != move.journal_id.id:
1143 (_check_centralisation,
1144 'You cannot create more than one move per period on centralized journal',
1146 (_check_period_journal,
1147 'You cannot create entries on different periods/journals in the same move',
1151 def post(self, cr, uid, ids, context=None):
1154 invoice = context.get('invoice', False)
1155 valid_moves = self.validate(cr, uid, ids, context)
1158 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" !'))
1159 obj_sequence = self.pool.get('ir.sequence')
1160 for move in self.browse(cr, uid, valid_moves):
1163 journal = move.journal_id
1165 if invoice and invoice.internal_number:
1166 new_name = invoice.internal_number
1168 if journal.sequence_id:
1169 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1170 new_name = obj_sequence.get_id(cr, uid, journal.sequence_id.id, context=c)
1172 raise osv.except_osv(_('Error'), _('No sequence defined on the journal !'))
1175 self.write(cr, uid, [move.id], {'name':new_name})
1177 cr.execute('UPDATE account_move '\
1180 ('posted', tuple(valid_moves),))
1184 def button_validate(self, cursor, user, ids, context=None):
1185 for move in self.browse(cursor, user, ids):
1187 for line in move.line_id:
1188 account = line.account_id
1191 account = account.parent_id
1194 elif top<>account2.id:
1195 raise osv.except_osv(_('Error !'), _('You cannot validate a Journal Entry unless all journal items are in same chart of accounts !'))
1196 return self.post(cursor, user, ids, context=context)
1198 def button_cancel(self, cr, uid, ids, context={}):
1199 for line in self.browse(cr, uid, ids, context):
1200 if not line.journal_id.update_posted:
1201 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.'))
1203 cr.execute('UPDATE account_move '\
1205 'WHERE id IN %s', ('draft', tuple(ids),))
1208 def write(self, cr, uid, ids, vals, context={}):
1210 c['novalidate'] = True
1211 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
1212 self.validate(cr, uid, ids, context)
1216 # TODO: Check if period is closed !
1218 def create(self, cr, uid, vals, context=None):
1219 context = context or {}
1220 if 'line_id' in vals and context.get('copy'):
1221 for l in vals['line_id']:
1224 'reconcile_id':False,
1225 'reconcil_partial_id':False,
1226 'analytic_lines':False,
1230 'account_tax_id':False,
1233 if 'journal_id' in vals and vals.get('journal_id', False):
1234 for l in vals['line_id']:
1236 l[2]['journal_id'] = vals['journal_id']
1237 context['journal_id'] = vals['journal_id']
1238 if 'period_id' in vals:
1239 for l in vals['line_id']:
1241 l[2]['period_id'] = vals['period_id']
1242 context['period_id'] = vals['period_id']
1244 default_period = self._get_period(cr, uid, context)
1245 for l in vals['line_id']:
1247 l[2]['period_id'] = default_period
1248 context['period_id'] = default_period
1250 if 'line_id' in vals:
1252 c['novalidate'] = True
1253 result = super(account_move, self).create(cr, uid, vals, c)
1254 self.validate(cr, uid, [result], context)
1256 result = super(account_move, self).create(cr, uid, vals, context)
1259 def copy(self, cr, uid, id, default={}, context={}):
1260 context = context or {}
1268 return super(account_move, self).copy(cr, uid, id, default, context)
1270 def unlink(self, cr, uid, ids, context=None, check=True):
1271 context = context or {}
1273 obj_move_line = self.pool.get('account.move.line')
1274 for move in self.browse(cr, uid, ids, context):
1275 if move['state'] != 'draft':
1276 raise osv.except_osv(_('UserError'),
1277 _('You can not delete posted movement: "%s"!') % \
1279 line_ids = map(lambda x: x.id, move.line_id)
1280 context['journal_id'] = move.journal_id.id
1281 context['period_id'] = move.period_id.id
1282 obj_move_line._update_check(cr, uid, line_ids, context)
1283 obj_move_line.unlink(cr, uid, line_ids, context=context)
1284 toremove.append(move.id)
1285 result = super(account_move, self).unlink(cr, uid, toremove, context)
1288 def _compute_balance(self, cr, uid, id, context={}):
1289 move = self.browse(cr, uid, [id])[0]
1291 for line in move.line_id:
1292 amount+= (line.debit - line.credit)
1295 def _centralise(self, cr, uid, move, mode, context=None):
1296 assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1301 account_id = move.journal_id.default_debit_account_id.id
1304 raise osv.except_osv(_('UserError'),
1305 _('There is no default default debit account defined \n' \
1306 'on journal "%s"') % move.journal_id.name)
1308 account_id = move.journal_id.default_credit_account_id.id
1311 raise osv.except_osv(_('UserError'),
1312 _('There is no default default credit account defined \n' \
1313 'on journal "%s"') % move.journal_id.name)
1315 # find the first line of this move with the current mode
1316 # or create it if it doesn't exist
1317 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1322 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1323 line_id = self.pool.get('account.move.line').create(cr, uid, {
1324 'name': _(mode.capitalize()+' Centralisation'),
1325 'centralisation': mode,
1326 'account_id': account_id,
1328 'journal_id': move.journal_id.id,
1329 'period_id': move.period_id.id,
1330 'date': move.period_id.date_stop,
1335 # find the first line of this move with the other mode
1336 # so that we can exclude it from our calculation
1337 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1344 cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1345 result = cr.fetchone()[0] or 0.0
1346 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1350 # Validate a balanced move. If it is a centralised journal, create a move.
1352 def validate(self, cr, uid, ids, context={}):
1353 if context and ('__last_update' in context):
1354 del context['__last_update']
1356 valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1357 obj_analytic_line = self.pool.get('account.analytic.line')
1358 obj_move_line = self.pool.get('account.move.line')
1359 for move in self.browse(cr, uid, ids, context):
1360 # Unlink old analytic lines on move_lines
1361 for obj_line in move.line_id:
1362 for obj in obj_line.analytic_lines:
1363 obj_analytic_line.unlink(cr,uid,obj.id)
1365 journal = move.journal_id
1370 for line in move.line_id:
1371 amount += line.debit - line.credit
1372 line_ids.append(line.id)
1373 if line.state=='draft':
1374 line_draft_ids.append(line.id)
1377 company_id = line.account_id.company_id.id
1378 if not company_id == line.account_id.company_id.id:
1379 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1381 if line.account_id.currency_id and line.currency_id:
1382 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):
1383 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)))
1385 if abs(amount) < 10 ** -4:
1386 # If the move is balanced
1387 # Add to the list of valid moves
1388 # (analytic lines will be created later for valid moves)
1389 valid_moves.append(move)
1391 # Check whether the move lines are confirmed
1393 if not line_draft_ids:
1395 # Update the move lines (set them as valid)
1397 obj_move_line.write(cr, uid, line_draft_ids, {
1398 'journal_id': move.journal_id.id,
1399 'period_id': move.period_id.id,
1401 }, context, check=False)
1406 if journal.type in ('purchase','sale'):
1407 for line in move.line_id:
1409 key = (line.account_id.id, line.tax_code_id.id)
1411 code = account2[key][0]
1412 amount = account2[key][1] * (line.debit + line.credit)
1413 elif line.account_id.id in account:
1414 code = account[line.account_id.id][0]
1415 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1416 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1417 obj_move_line.write(cr, uid, [line.id], {
1418 'tax_code_id': code,
1419 'tax_amount': amount
1420 }, context, check=False)
1421 elif journal.centralisation:
1422 # If the move is not balanced, it must be centralised...
1424 # Add to the list of valid moves
1425 # (analytic lines will be created later for valid moves)
1426 valid_moves.append(move)
1429 # Update the move lines (set them as valid)
1431 self._centralise(cr, uid, move, 'debit', context=context)
1432 self._centralise(cr, uid, move, 'credit', context=context)
1433 obj_move_line.write(cr, uid, line_draft_ids, {
1435 }, context, check=False)
1437 # We can't validate it (it's unbalanced)
1438 # Setting the lines as draft
1439 obj_move_line.write(cr, uid, line_ids, {
1440 'journal_id': move.journal_id.id,
1441 'period_id': move.period_id.id,
1443 }, context, check=False)
1444 # Create analytic lines for the valid moves
1445 for record in valid_moves:
1446 obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1448 valid_moves = [move.id for move in valid_moves]
1449 return len(valid_moves) > 0 and valid_moves or False
1453 class account_move_reconcile(osv.osv):
1454 _name = "account.move.reconcile"
1455 _description = "Account Reconciliation"
1457 'name': fields.char('Name', size=64, required=True),
1458 'type': fields.char('Type', size=16, required=True),
1459 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1460 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1461 'create_date': fields.date('Creation date', readonly=True),
1464 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1466 def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1468 for rec in self.browse(cr, uid, ids, context):
1469 for line in rec.line_partial_ids:
1470 total += (line.debit or 0.0) - (line.credit or 0.0)
1472 self.pool.get('account.move.line').write(cr, uid,
1473 map(lambda x: x.id, rec.line_partial_ids),
1474 {'reconcile_id': rec.id }
1478 def name_get(self, cr, uid, ids, context=None):
1482 for r in self.browse(cr, uid, ids, context):
1483 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1485 name = '%s (%.2f)' % (r.name, total)
1486 result.append((r.id,name))
1488 result.append((r.id,r.name))
1492 account_move_reconcile()
1494 #----------------------------------------------------------
1496 #----------------------------------------------------------
1499 child_depend: la taxe depend des taxes filles
1501 class account_tax_code(osv.osv):
1503 A code for the tax object.
1505 This code is used for some tax declarations.
1507 def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1508 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1509 if context.get('based_on', 'invoices') == 'payments':
1510 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1511 FROM account_move_line AS line, \
1512 account_move AS move \
1513 LEFT JOIN account_invoice invoice ON \
1514 (invoice.move_id = move.id) \
1515 WHERE line.tax_code_id IN %s '+where+' \
1516 AND move.id = line.move_id \
1517 AND ((invoice.state = \'paid\') \
1518 OR (invoice.id IS NULL)) \
1519 GROUP BY line.tax_code_id',
1520 (parent_ids,) + where_params)
1522 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1523 FROM account_move_line AS line, \
1524 account_move AS move \
1525 WHERE line.tax_code_id IN %s '+where+' \
1526 AND move.id = line.move_id \
1527 GROUP BY line.tax_code_id',
1528 (parent_ids,) + where_params)
1529 res=dict(cr.fetchall())
1530 obj_precision = self.pool.get('decimal.precision')
1531 for record in self.browse(cr, uid, ids, context):
1532 def _rec_get(record):
1533 amount = res.get(record.id, 0.0)
1534 for rec in record.child_ids:
1535 amount += _rec_get(rec) * rec.sign
1537 res[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1540 def _sum_year(self, cr, uid, ids, name, args, context=None):
1543 move_state = ('posted', )
1544 if context.get('state', 'all') == 'all':
1545 move_state = ('draft', 'posted', )
1546 if context.get('fiscalyear_id', False):
1547 fiscalyear_id = context['fiscalyear_id']
1549 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1553 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1555 where = ' AND line.period_id IN %s AND move.state IN %s '
1556 where_params = (tuple(pids), move_state)
1557 return self._sum(cr, uid, ids, name, args, context,
1558 where=where, where_params=where_params)
1560 def _sum_period(self, cr, uid, ids, name, args, context):
1563 move_state = ('posted', )
1564 if context.get('state', False) == 'all':
1565 move_state = ('draft', 'posted', )
1566 if context.get('period_id', False):
1567 period_id = context['period_id']
1569 period_id = self.pool.get('account.period').find(cr, uid)
1571 return dict.fromkeys(ids, 0.0)
1572 period_id = period_id[0]
1573 return self._sum(cr, uid, ids, name, args, context,
1574 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1576 _name = 'account.tax.code'
1577 _description = 'Tax Code'
1580 'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1581 'code': fields.char('Case Code', size=64),
1582 'info': fields.text('Description'),
1583 'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1584 'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1585 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1586 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1587 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1588 'company_id': fields.many2one('res.company', 'Company', required=True),
1589 '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.'),
1590 '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"),
1594 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1599 ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1600 return self.name_get(cr, user, ids, context)
1603 def name_get(self, cr, uid, ids, context=None):
1604 if isinstance(ids, (int, long)):
1608 if isinstance(ids, (int, long)):
1610 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1611 return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1614 def _default_company(self, cr, uid, context={}):
1615 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1617 return user.company_id.id
1618 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1620 'company_id': _default_company,
1622 'notprintable': False,
1625 def copy(self, cr, uid, id, default=None, context=None):
1628 default = default.copy()
1629 default.update({'line_ids': []})
1630 return super(account_tax_code, self).copy(cr, uid, id, default, context)
1632 _check_recursion = check_cycle
1634 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1636 _order = 'code,name'
1639 class account_tax(osv.osv):
1643 Type: percent, fixed, none, code
1644 PERCENT: tax = price * amount
1645 FIXED: tax = price + amount
1647 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1648 return result in the context
1649 Ex: result=round(price_unit*0.21,4)
1651 _name = 'account.tax'
1652 _description = 'Tax'
1654 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1655 '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."),
1656 'amount': fields.float('Amount', required=True, digits_compute=dp.get_precision('Account'), help="For taxes of type percentage, enter % ratio between 0-1."),
1657 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the tax without removing it."),
1658 'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1659 help="The computation method for the tax amount."),
1660 'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1661 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1662 '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."),
1663 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1664 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1665 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1666 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1667 '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."),
1668 'python_compute':fields.text('Python Code'),
1669 'python_compute_inv':fields.text('Python Code (reverse)'),
1670 'python_applicable':fields.text('Python Code'),
1673 # Fields used for the VAT declaration
1675 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1676 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1677 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1678 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1680 # Same fields for refund invoices
1682 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1683 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1684 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1685 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1686 '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"),
1687 'company_id': fields.many2one('res.company', 'Company', required=True),
1688 'description': fields.char('Tax Code',size=32),
1689 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1690 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1694 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1696 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1697 result format: {[(id, name), (id, name), ...]}
1699 @param cr: A database cursor
1700 @param user: ID of the user currently logged in
1701 @param name: name to search
1702 @param args: other arguments
1703 @param operator: default operator is 'ilike', it can be changed
1704 @param context: context arguments, like lang, time zone
1705 @param limit: Returns first 'n' ids of complete result, default is 80.
1707 @return: Returns a list of tupples containing id and name
1714 ids = self.search(cr, user, args, limit=limit, context=context)
1715 return self.name_get(cr, user, ids, context=context)
1717 def write(self, cr, uid, ids, vals, context=None):
1718 if vals.get('type', False) and vals['type'] in ('none', 'code'):
1719 vals.update({'amount': 0.0})
1720 return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1722 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1723 journal_pool = self.pool.get('account.journal')
1725 if context and context.has_key('type'):
1726 if context.get('type') in ('out_invoice','out_refund'):
1727 args += [('type_tax_use','in',['sale','all'])]
1728 elif context.get('type') in ('in_invoice','in_refund'):
1729 args += [('type_tax_use','in',['purchase','all'])]
1731 if context and context.has_key('journal_id'):
1732 journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1733 if journal.type in ('sale', 'purchase'):
1734 args += [('type_tax_use','in',[journal.type,'all'])]
1736 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1738 def name_get(self, cr, uid, ids, context=None):
1742 for record in self.read(cr, uid, ids, ['description','name'], context=context):
1743 name = record['description'] and record['description'] or record['name']
1744 res.append((record['id'],name ))
1747 def _default_company(self, cr, uid, context=None):
1748 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1750 return user.company_id.id
1751 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1754 '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''',
1755 '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''',
1756 'applicable_type': 'true',
1761 'type_tax_use': 'all',
1767 'include_base_amount': False,
1768 'company_id': _default_company,
1772 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1774 obj_partener_address = self.pool.get('res.partner.address')
1776 if tax.applicable_type=='code':
1777 localdict = {'price_unit':price_unit, 'address':obj_partener_address.browse(cr, uid, address_id), 'product':product, 'partner':partner}
1778 exec tax.python_applicable in localdict
1779 if localdict.get('result', False):
1785 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1786 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1788 cur_price_unit=price_unit
1789 obj_partener_address = self.pool.get('res.partner.address')
1791 # we compute the amount for the current tax object and append it to the result
1793 data = {'id':tax.id,
1794 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1795 'account_collected_id':tax.account_collected_id.id,
1796 'account_paid_id':tax.account_paid_id.id,
1797 'base_code_id': tax.base_code_id.id,
1798 'ref_base_code_id': tax.ref_base_code_id.id,
1799 'sequence': tax.sequence,
1800 'base_sign': tax.base_sign,
1801 'tax_sign': tax.tax_sign,
1802 'ref_base_sign': tax.ref_base_sign,
1803 'ref_tax_sign': tax.ref_tax_sign,
1804 'price_unit': cur_price_unit,
1805 'tax_code_id': tax.tax_code_id.id,
1806 'ref_tax_code_id': tax.ref_tax_code_id.id,
1809 if tax.type=='percent':
1810 amount = cur_price_unit * tax.amount
1811 data['amount'] = amount
1813 elif tax.type=='fixed':
1814 data['amount'] = tax.amount
1815 data['tax_amount']=quantity
1816 # data['amount'] = quantity
1817 elif tax.type=='code':
1818 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1819 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1820 exec tax.python_compute in localdict
1821 amount = localdict['result']
1822 data['amount'] = amount
1823 elif tax.type=='balance':
1824 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1825 data['balance'] = cur_price_unit
1827 amount2 = data.get('amount', 0.0)
1829 if tax.child_depend:
1832 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1833 res.extend(child_tax)
1834 if tax.child_depend:
1836 for name in ('base','ref_base'):
1837 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1838 r[name+'_code_id'] = latest[name+'_code_id']
1839 r[name+'_sign'] = latest[name+'_sign']
1840 r['price_unit'] = latest['price_unit']
1841 latest[name+'_code_id'] = False
1842 for name in ('tax','ref_tax'):
1843 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1844 r[name+'_code_id'] = latest[name+'_code_id']
1845 r[name+'_sign'] = latest[name+'_sign']
1846 r['amount'] = data['amount']
1847 latest[name+'_code_id'] = False
1848 if tax.include_base_amount:
1849 cur_price_unit+=amount2
1852 def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1855 'total': 0.0, # Total without taxes
1856 'total_included: 0.0, # Total with taxes
1857 'taxes': [] # List of taxes, see compute for the format
1860 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1861 totalin = totalex = round(price_unit * quantity, precision)
1865 if tax.price_include:
1869 tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
1871 totalex -= r.get('amount', 0.0)
1874 totlex_qty=totalex/quantity
1877 tex = self._compute(cr, uid, tex, totlex_qty, quantity, address_id=address_id, product=product, partner=partner)
1879 totalin += r.get('amount', 0.0)
1882 'total_included': totalin,
1886 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1887 logger = netsvc.Logger()
1888 logger.notifyChannel("warning", netsvc.LOG_WARNING,
1889 "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
1890 return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
1892 def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1894 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1898 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1899 one tax for each tax id in IDS and their childs
1901 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1903 precision_pool = self.pool.get('decimal.precision')
1905 if r.get('balance',False):
1906 r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
1908 r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
1909 total += r['amount']
1912 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1913 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1914 obj_partener_address = self.pool.get('res.partner.address')
1917 cur_price_unit = price_unit
1919 tax_parent_tot = 0.0
1921 if (tax.type=='percent') and not tax.include_base_amount:
1922 tax_parent_tot += tax.amount
1925 if (tax.type=='fixed') and not tax.include_base_amount:
1926 cur_price_unit -= tax.amount
1929 if tax.type=='percent':
1930 if tax.include_base_amount:
1931 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1933 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1935 elif tax.type=='fixed':
1938 elif tax.type=='code':
1939 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1940 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1941 exec tax.python_compute_inv in localdict
1942 amount = localdict['result']
1943 elif tax.type=='balance':
1944 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1946 if tax.include_base_amount:
1947 cur_price_unit -= amount
1956 'account_collected_id': tax.account_collected_id.id,
1957 'account_paid_id': tax.account_paid_id.id,
1958 'base_code_id': tax.base_code_id.id,
1959 'ref_base_code_id': tax.ref_base_code_id.id,
1960 'sequence': tax.sequence,
1961 'base_sign': tax.base_sign,
1962 'tax_sign': tax.tax_sign,
1963 'ref_base_sign': tax.ref_base_sign,
1964 'ref_tax_sign': tax.ref_tax_sign,
1965 'price_unit': cur_price_unit,
1966 'tax_code_id': tax.tax_code_id.id,
1967 'ref_tax_code_id': tax.ref_tax_code_id.id,
1970 if tax.child_depend:
1974 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1975 res.extend(parent_tax)
1980 total += r['amount']
1982 r['price_unit'] -= total
1986 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1988 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1989 Price Unit is a VAT included price
1993 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1994 one tax for each tax id in IDS and their childs
1996 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1998 obj_precision = self.pool.get('decimal.precision')
2000 prec = obj_precision.precision_get(cr, uid, 'Account')
2001 if r.get('balance',False):
2002 r['amount'] = round(r['balance'] * quantity, prec) - total
2004 r['amount'] = round(r['amount'] * quantity, prec)
2005 total += r['amount']
2009 # ---------------------------------------------------------
2010 # Account Entries Models
2011 # ---------------------------------------------------------
2013 class account_model(osv.osv):
2014 _name = "account.model"
2015 _description = "Account Model"
2017 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2018 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2019 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2020 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2021 'legend': fields.text('Legend', readonly=True, size=100),
2025 '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'),
2027 def generate(self, cr, uid, ids, datas={}, context=None):
2030 account_move_obj = self.pool.get('account.move')
2031 account_move_line_obj = self.pool.get('account.move.line')
2032 pt_obj = self.pool.get('account.payment.term')
2037 if datas.get('date', False):
2038 context.update({'date': datas['date']})
2040 period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
2042 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
2043 period_id = period_id[0]
2045 for model in self.browse(cr, uid, ids, context):
2046 entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')}
2047 move_id = account_move_obj.create(cr, uid, {
2048 'ref': entry['name'],
2049 'period_id': period_id,
2050 'journal_id': model.journal_id.id,
2051 'date': context.get('date',time.strftime('%Y-%m-%d'))
2053 move_ids.append(move_id)
2054 for line in model.lines_id:
2055 analytic_account_id = False
2056 if line.analytic_account_id:
2057 if not model.journal_id.analytic_journal_id:
2058 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2059 analytic_account_id = line.analytic_account_id.id
2062 'journal_id': model.journal_id.id,
2063 'period_id': period_id,
2064 'analytic_account_id': analytic_account_id
2067 date_maturity = time.strftime('%Y-%m-%d')
2068 if line.date_maturity == 'partner':
2069 if not line.partner_id:
2070 raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term! \
2071 \nPlease define partner on it!"%(line.name, model.name)))
2072 if line.partner_id.property_payment_term:
2073 payment_term_id = line.partner_id.property_payment_term.id
2074 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2076 pterm_list = [l[0] for l in pterm_list]
2078 date_maturity = pterm_list[-1]
2082 'quantity': line.quantity,
2083 'debit': line.debit,
2084 'credit': line.credit,
2085 'account_id': line.account_id.id,
2087 'partner_id': line.partner_id.id,
2088 'date': context.get('date',time.strftime('%Y-%m-%d')),
2089 'date_maturity': date_maturity
2092 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
2093 account_move_line_obj.create(cr, uid, val, context=c)
2099 class account_model_line(osv.osv):
2100 _name = "account.model.line"
2101 _description = "Account Model Entries"
2103 'name': fields.char('Name', size=64, required=True),
2104 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
2105 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries"),
2106 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2107 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2108 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2109 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2110 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2111 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2112 'currency_id': fields.many2one('res.currency', 'Currency'),
2113 'partner_id': fields.many2one('res.partner', 'Partner'),
2114 '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."),
2117 _sql_constraints = [
2118 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
2119 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
2121 account_model_line()
2123 # ---------------------------------------------------------
2124 # Account Subscription
2125 # ---------------------------------------------------------
2128 class account_subscription(osv.osv):
2129 _name = "account.subscription"
2130 _description = "Account Subscription"
2132 'name': fields.char('Name', size=64, required=True),
2133 'ref': fields.char('Reference', size=16),
2134 'model_id': fields.many2one('account.model', 'Model', required=True),
2136 'date_start': fields.date('Start Date', required=True),
2137 'period_total': fields.integer('Number of Periods', required=True),
2138 'period_nbr': fields.integer('Period', required=True),
2139 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2140 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
2142 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2145 'date_start': time.strftime('%Y-%m-%d'),
2146 'period_type': 'month',
2151 def state_draft(self, cr, uid, ids, context={}):
2152 self.write(cr, uid, ids, {'state':'draft'})
2155 def check(self, cr, uid, ids, context={}):
2157 for sub in self.browse(cr, uid, ids, context):
2159 for line in sub.lines_id:
2160 if not line.move_id.id:
2164 todone.append(sub.id)
2166 self.write(cr, uid, todone, {'state':'done'})
2169 def remove_line(self, cr, uid, ids, context={}):
2171 for sub in self.browse(cr, uid, ids, context):
2172 for line in sub.lines_id:
2173 if not line.move_id.id:
2174 toremove.append(line.id)
2176 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2177 self.write(cr, uid, ids, {'state':'draft'})
2180 def compute(self, cr, uid, ids, context={}):
2181 for sub in self.browse(cr, uid, ids, context):
2183 for i in range(sub.period_total):
2184 self.pool.get('account.subscription.line').create(cr, uid, {
2186 'subscription_id': sub.id,
2188 if sub.period_type=='day':
2189 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2190 if sub.period_type=='month':
2191 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2192 if sub.period_type=='year':
2193 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2194 self.write(cr, uid, ids, {'state':'running'})
2196 account_subscription()
2198 class account_subscription_line(osv.osv):
2199 _name = "account.subscription.line"
2200 _description = "Account Subscription Line"
2202 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2203 'date': fields.date('Date', required=True),
2204 'move_id': fields.many2one('account.move', 'Entry'),
2207 def move_create(self, cr, uid, ids, context=None):
2210 obj_model = self.pool.get('account.model')
2211 for line in self.browse(cr, uid, ids, context=context):
2215 move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2216 tocheck[line.subscription_id.id] = True
2217 self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2218 all_moves.extend(move_ids)
2220 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2224 account_subscription_line()
2226 # ---------------------------------------------------------------
2227 # Account Templates: Account, Tax, Tax Code and chart. + Wizard
2228 # ---------------------------------------------------------------
2230 class account_tax_template(osv.osv):
2231 _name = 'account.tax.template'
2232 account_tax_template()
2234 class account_account_template(osv.osv):
2236 _name = "account.account.template"
2237 _description ='Templates for Accounts'
2240 'name': fields.char('Name', size=128, required=True, select=True),
2241 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2242 'code': fields.char('Code', size=64),
2243 'type': fields.selection([
2244 ('receivable','Receivable'),
2245 ('payable','Payable'),
2247 ('consolidation','Consolidation'),
2248 ('liquidity','Liquidity'),
2249 ('other','Regular'),
2250 ('closed','Closed'),
2251 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2252 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2253 "can have children accounts for multi-company consolidations, payable/receivable are for "\
2254 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2255 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2256 help="These types are defined according to your country. The type contains more information "\
2257 "about the account and its specificities."),
2258 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2259 'shortcut': fields.char('Shortcut', size=12),
2260 'note': fields.text('Note'),
2261 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2262 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2263 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2264 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2273 _check_recursion = check_cycle
2275 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
2279 def name_get(self, cr, uid, ids, context={}):
2282 reads = self.read(cr, uid, ids, ['name','code'], context)
2284 for record in reads:
2285 name = record['name']
2287 name = record['code']+' '+name
2288 res.append((record['id'],name ))
2291 account_account_template()
2293 class account_add_tmpl_wizard(osv.osv_memory):
2294 """Add one more account from the template.
2296 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2297 _name = 'account.addtmpl.wizard'
2299 def _get_def_cparent(self, cr, uid, context):
2300 acc_obj=self.pool.get('account.account')
2301 tmpl_obj=self.pool.get('account.account.template')
2302 tids=tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2303 if not tids or not tids[0]['parent_id']:
2305 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2307 if not ptids or not ptids[0]['code']:
2308 raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
2309 res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2311 return res and res[0] or False
2314 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2317 'cparent_id': _get_def_cparent,
2320 def action_create(self,cr,uid,ids,context=None):
2321 acc_obj = self.pool.get('account.account')
2322 tmpl_obj = self.pool.get('account.account.template')
2323 data = self.read(cr, uid, ids)
2324 company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2325 account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2327 'name': account_template.name,
2328 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2329 'code': account_template.code,
2330 'type': account_template.type,
2331 'user_type': account_template.user_type and account_template.user_type.id or False,
2332 'reconcile': account_template.reconcile,
2333 'shortcut': account_template.shortcut,
2334 'note': account_template.note,
2335 'parent_id': data[0]['cparent_id'],
2336 'company_id': company_id,
2338 acc_obj.create(cr, uid, vals)
2339 return {'type':'state', 'state': 'end' }
2341 def action_cancel(self, cr, uid, ids, context=None):
2342 return { 'type': 'state', 'state': 'end' }
2344 account_add_tmpl_wizard()
2346 class account_tax_code_template(osv.osv):
2348 _name = 'account.tax.code.template'
2349 _description = 'Tax Code Template'
2353 'name': fields.char('Tax Case Name', size=64, required=True),
2354 'code': fields.char('Case Code', size=64),
2355 'info': fields.text('Description'),
2356 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2357 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2358 'sign': fields.float('Sign For Parent', required=True),
2359 '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"),
2364 'notprintable': False,
2367 def name_get(self, cr, uid, ids, context=None):
2370 if isinstance(ids, (int, long)):
2372 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2373 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2376 _check_recursion = check_cycle
2378 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2380 _order = 'code,name'
2381 account_tax_code_template()
2384 class account_chart_template(osv.osv):
2385 _name="account.chart.template"
2386 _description= "Templates for Account Chart"
2389 'name': fields.char('Name', size=64, required=True),
2390 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
2391 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
2392 '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'),
2393 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
2394 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2395 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2396 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2397 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2398 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2399 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2400 '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'),
2403 account_chart_template()
2405 class account_tax_template(osv.osv):
2407 _name = 'account.tax.template'
2408 _description = 'Templates for Taxes'
2411 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2412 'name': fields.char('Tax Name', size=64, required=True),
2413 '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."),
2414 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2415 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
2416 '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."),
2417 '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."),
2418 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2419 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2420 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2421 '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."),
2422 'python_compute':fields.text('Python Code'),
2423 'python_compute_inv':fields.text('Python Code (reverse)'),
2424 'python_applicable':fields.text('Python Code'),
2427 # Fields used for the VAT declaration
2429 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2430 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2431 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2432 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2434 # Same fields for refund invoices
2436 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2437 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2438 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2439 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2440 '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."),
2441 'description': fields.char('Internal Name', size=32),
2442 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,)
2445 def name_get(self, cr, uid, ids, context={}):
2449 for record in self.read(cr, uid, ids, ['description','name'], context):
2450 name = record['description'] and record['description'] or record['name']
2451 res.append((record['id'],name ))
2454 def _default_company(self, cr, uid, context={}):
2455 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2457 return user.company_id.id
2458 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2461 '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''',
2462 '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''',
2463 'applicable_type': 'true',
2471 'include_base_amount': False,
2472 'type_tax_use': 'all',
2477 account_tax_template()
2479 # Fiscal Position Templates
2481 class account_fiscal_position_template(osv.osv):
2482 _name = 'account.fiscal.position.template'
2483 _description = 'Template for Fiscal Position'
2486 'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
2487 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2488 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2489 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2492 account_fiscal_position_template()
2494 class account_fiscal_position_tax_template(osv.osv):
2495 _name = 'account.fiscal.position.tax.template'
2496 _description = 'Template Tax Fiscal Position'
2497 _rec_name = 'position_id'
2500 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2501 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2502 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2505 account_fiscal_position_tax_template()
2507 class account_fiscal_position_account_template(osv.osv):
2508 _name = 'account.fiscal.position.account.template'
2509 _description = 'Template Account Fiscal Mapping'
2510 _rec_name = 'position_id'
2512 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2513 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2514 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2517 account_fiscal_position_account_template()
2519 # Multi charts of Accounts wizard
2521 class wizard_multi_charts_accounts(osv.osv_memory):
2523 Create a new account chart for a company.
2526 * an account chart template
2527 * a number of digits for formatting code of non-view accounts
2528 * a list of bank accounts owned by the company
2530 * generates all accounts from the template and assigns them to the right company
2531 * generates all taxes and tax codes, changing account assignations
2532 * generates all accounting properties and assigns them correctly
2534 _name='wizard.multi.charts.accounts'
2535 _inherit = 'res.config'
2538 'company_id':fields.many2one('res.company', 'Company', required=True),
2539 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2540 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts', required=True),
2541 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2542 '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."),
2545 def _get_chart(self, cr, uid, context={}):
2546 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2551 def _get_default_accounts(self, cr, uid, context=None):
2552 accounts = [{'acc_name':'Current','account_type':'bank'},
2553 {'acc_name':'Deposit','account_type':'bank'},
2554 {'acc_name':'Cash','account_type':'cash'}]
2558 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, [uid], c)[0].company_id.id,
2559 'chart_template_id': _get_chart,
2560 'bank_accounts_id': _get_default_accounts,
2565 def execute(self, cr, uid, ids, context=None):
2566 obj_multi = self.browse(cr, uid, ids[0])
2567 obj_acc = self.pool.get('account.account')
2568 obj_acc_tax = self.pool.get('account.tax')
2569 obj_journal = self.pool.get('account.journal')
2570 obj_sequence = self.pool.get('ir.sequence')
2571 obj_acc_template = self.pool.get('account.account.template')
2572 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2573 obj_fiscal_position = self.pool.get('account.fiscal.position')
2574 obj_data = self.pool.get('ir.model.data')
2575 analytic_journal_obj = self.pool.get('account.analytic.journal')
2576 obj_tax_code = self.pool.get('account.tax.code')
2578 obj_acc_root = obj_multi.chart_template_id.account_root_id
2579 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2580 company_id = obj_multi.company_id.id
2583 acc_template_ref = {}
2584 tax_template_ref = {}
2585 tax_code_template_ref = {}
2588 #create all the tax code
2589 children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2590 children_tax_code_template.sort()
2591 for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2593 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2594 'code': tax_code_template.code,
2595 'info': tax_code_template.info,
2596 '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,
2597 'company_id': company_id,
2598 'sign': tax_code_template.sign,
2600 new_tax_code = obj_tax_code.create(cr, uid, vals)
2601 #recording the new tax code to do the mapping
2602 tax_code_template_ref[tax_code_template.id] = new_tax_code
2605 for tax in obj_multi.chart_template_id.tax_template_ids:
2609 'sequence': tax.sequence,
2610 'amount':tax.amount,
2612 'applicable_type': tax.applicable_type,
2613 'domain':tax.domain,
2614 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2615 'child_depend': tax.child_depend,
2616 'python_compute': tax.python_compute,
2617 'python_compute_inv': tax.python_compute_inv,
2618 'python_applicable': tax.python_applicable,
2619 '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,
2620 '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,
2621 'base_sign': tax.base_sign,
2622 'tax_sign': tax.tax_sign,
2623 '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,
2624 '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,
2625 'ref_base_sign': tax.ref_base_sign,
2626 'ref_tax_sign': tax.ref_tax_sign,
2627 'include_base_amount': tax.include_base_amount,
2628 'description':tax.description,
2629 'company_id': company_id,
2630 'type_tax_use': tax.type_tax_use
2632 new_tax = obj_acc_tax.create(cr, uid, vals_tax)
2633 #as the accounts have not been created yet, we have to wait before filling these fields
2634 todo_dict[new_tax] = {
2635 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2636 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2638 tax_template_ref[tax.id] = new_tax
2640 #deactivate the parent_store functionnality on account_account for rapidity purpose
2641 self.pool._init = True
2643 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2644 children_acc_template.sort()
2645 for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2647 for tax in account_template.tax_ids:
2648 tax_ids.append(tax_template_ref[tax.id])
2649 #create the account_account
2651 dig = obj_multi.code_digits
2652 code_main = account_template.code and len(account_template.code) or 0
2653 code_acc = account_template.code or ''
2654 if code_main>0 and code_main<=dig and account_template.type != 'view':
2655 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2657 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2658 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2660 'type': account_template.type,
2661 'user_type': account_template.user_type and account_template.user_type.id or False,
2662 'reconcile': account_template.reconcile,
2663 'shortcut': account_template.shortcut,
2664 'note': account_template.note,
2665 '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,
2666 'tax_ids': [(6,0,tax_ids)],
2667 'company_id': company_id,
2669 new_account = obj_acc.create(cr, uid, vals)
2670 acc_template_ref[account_template.id] = new_account
2671 #reactivate the parent_store functionnality on account_account
2672 self.pool._init = False
2673 self.pool.get('account.account')._parent_store_compute(cr)
2675 for key,value in todo_dict.items():
2676 if value['account_collected_id'] or value['account_paid_id']:
2677 obj_acc_tax.write(cr, uid, [key], {
2678 'account_collected_id': acc_template_ref[value['account_collected_id']],
2679 'account_paid_id': acc_template_ref[value['account_paid_id']],
2682 # Creating Journals Sales and Purchase
2684 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_sp_journal_view')])
2685 data = obj_data.browse(cr, uid, data_id[0])
2686 view_id = data.res_id
2688 seq_id = obj_sequence.search(cr, uid, [('name','=','Account Journal')])[0]
2690 if obj_multi.seq_journal:
2691 seq_id_sale = obj_sequence.search(cr, uid, [('name','=','Sale Journal')])[0]
2692 seq_id_purchase = obj_sequence.search(cr, uid, [('name','=','Purchase Journal')])[0]
2694 seq_id_sale = seq_id
2695 seq_id_purchase = seq_id
2697 vals_journal['view_id'] = view_id
2700 analitical_sale_ids = analytic_journal_obj.search(cr,uid,[('type','=','sale')])
2701 analitical_journal_sale = analitical_sale_ids and analitical_sale_ids[0] or False
2703 vals_journal['name'] = _('Sales Journal')
2704 vals_journal['type'] = 'sale'
2705 vals_journal['code'] = _('SAJ')
2706 vals_journal['sequence_id'] = seq_id_sale
2707 vals_journal['company_id'] = company_id
2708 vals_journal['analytic_journal_id'] = analitical_journal_sale
2710 if obj_multi.chart_template_id.property_account_receivable:
2711 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2712 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2714 obj_journal.create(cr,uid,vals_journal)
2717 analitical_purchase_ids = analytic_journal_obj.search(cr,uid,[('type','=','purchase')])
2718 analitical_journal_purchase = analitical_purchase_ids and analitical_purchase_ids[0] or False
2720 vals_journal['name'] = _('Purchase Journal')
2721 vals_journal['type'] = 'purchase'
2722 vals_journal['code'] = _('EXJ')
2723 vals_journal['sequence_id'] = seq_id_purchase
2724 vals_journal['view_id'] = view_id
2725 vals_journal['company_id'] = company_id
2726 vals_journal['analytic_journal_id'] = analitical_journal_purchase
2728 if obj_multi.chart_template_id.property_account_payable:
2729 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2730 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2732 obj_journal.create(cr,uid,vals_journal)
2735 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')])
2736 data = obj_data.browse(cr, uid, data_id[0])
2737 view_id_cash = data.res_id
2739 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view_multi')])
2740 data = obj_data.browse(cr, uid, data_id[0])
2741 view_id_cur = data.res_id
2742 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2745 for line in obj_multi.bank_accounts_id:
2746 #create the account_account for this bank journal
2748 dig = obj_multi.code_digits
2749 if ref_acc_bank.code:
2751 new_code = str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num)
2753 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)),'0')) + str(current_num)
2756 'currency_id': line.currency_id and line.currency_id.id or False,
2759 'user_type': account_template.user_type and account_template.user_type.id or False,
2761 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2762 'company_id': company_id,
2764 acc_cash_id = obj_acc.create(cr,uid,vals)
2766 if obj_multi.seq_journal:
2768 'name': _('Bank Journal ') + vals['name'],
2769 'code': 'account.journal',
2771 seq_id = obj_sequence.create(cr,uid,vals_seq)
2773 #create the bank journal
2774 analitical_bank_ids = analytic_journal_obj.search(cr,uid,[('type','=','situation')])
2775 analitical_journal_bank = analitical_bank_ids and analitical_bank_ids[0] or False
2777 vals_journal['name']= vals['name']
2778 vals_journal['code']= _('BNK') + str(current_num)
2779 vals_journal['sequence_id'] = seq_id
2780 vals_journal['type'] = 'cash'
2781 vals_journal['company_id'] = company_id
2782 vals_journal['analytic_journal_id'] = analitical_journal_bank
2784 if line.currency_id:
2785 vals_journal['view_id'] = view_id_cur
2786 vals_journal['currency'] = line.currency_id.id
2788 vals_journal['view_id'] = view_id_cash
2789 vals_journal['default_credit_account_id'] = acc_cash_id
2790 vals_journal['default_debit_account_id'] = acc_cash_id
2791 obj_journal.create(cr, uid, vals_journal)
2794 #create the properties
2795 property_obj = self.pool.get('ir.property')
2796 fields_obj = self.pool.get('ir.model.fields')
2799 ('property_account_receivable','res.partner','account.account'),
2800 ('property_account_payable','res.partner','account.account'),
2801 ('property_account_expense_categ','product.category','account.account'),
2802 ('property_account_income_categ','product.category','account.account'),
2803 ('property_account_expense','product.template','account.account'),
2804 ('property_account_income','product.template','account.account'),
2805 ('property_reserve_and_surplus_account','res.company','account.account')
2807 for record in todo_list:
2809 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2810 account = getattr(obj_multi.chart_template_id, record[0])
2811 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2814 'company_id': company_id,
2815 'fields_id': field[0],
2816 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2820 #the property exist: modify it
2821 property_obj.write(cr, uid, r, vals)
2823 #create the property
2824 property_obj.create(cr, uid, vals)
2826 fp_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)])
2830 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2831 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2833 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2836 'company_id': company_id,
2837 'name': position.name,
2839 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2841 for tax in position.tax_ids:
2843 'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2844 'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2845 'position_id': new_fp,
2847 obj_tax_fp.create(cr, uid, vals_tax)
2849 for acc in position.account_ids:
2851 'account_src_id': acc_template_ref[acc.account_src_id.id],
2852 'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2853 'position_id': new_fp,
2855 obj_ac_fp.create(cr, uid, vals_acc)
2857 wizard_multi_charts_accounts()
2859 class account_bank_accounts_wizard(osv.osv_memory):
2860 _name='account.bank.accounts.wizard'
2863 'acc_name':fields.char('Account Name.', size=64, required=True),
2864 'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2865 'currency_id':fields.many2one('res.currency', 'Currency'),
2866 'account_type':fields.selection([('cash','Cash'),('check','Check'),('bank','Bank')], 'Type', size=32),
2869 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
2872 account_bank_accounts_wizard()
2874 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: