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 next_first_date = next_date + relativedelta(day=1,months=1) #Getting 1st of next month
84 next_date = next_first_date + relativedelta(days=line.days2)
86 next_date += relativedelta(day=line.days2, months=1)
87 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
91 account_payment_term()
93 class account_payment_term_line(osv.osv):
94 _name = "account.payment.term.line"
95 _description = "Payment Term Line"
97 'name': fields.char('Line Name', size=32, required=True),
98 '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"),
99 'value': fields.selection([('procent', 'Percent'),
100 ('balance', 'Balance'),
101 ('fixed', 'Fixed Amount')], 'Valuation',
102 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."""),
104 'value_amount': fields.float('Value Amount', help="For Value percent enter % ratio between 0-1."),
105 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
106 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
107 '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)."),
108 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
117 def _check_percent(self, cr, uid, ids, context=None):
118 obj = self.browse(cr, uid, ids[0])
119 if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
124 (_check_percent, _('Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% '), ['value_amount']),
127 account_payment_term_line()
129 class account_account_type(osv.osv):
130 _name = "account.account.type"
131 _description = "Account Type"
133 'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
134 'code': fields.char('Code', size=32, required=True),
135 '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.
137 'None' means that nothing will be done.
138 'Balance' will generally be used for cash accounts.
139 'Detail' will copy each existing journal item of the previous year, even the reconciled ones.
140 'Unreconciled' will copy only the journal items that were unreconciled on the first day of the new fiscal year."""),
141 '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.'),
142 'report_type':fields.selection([
144 ('income','Profit & Loss (Income Accounts)'),
145 ('expense','Profit & Loss (Expense Accounts)'),
146 ('asset','Balance Sheet (Assets Accounts)'),
147 ('liability','Balance Sheet (Liability Accounts)')
148 ],'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),
149 'note': fields.text('Description'),
152 'close_method': 'none',
154 'report_type': 'none',
158 account_account_type()
160 def _code_get(self, cr, uid, context=None):
161 acc_type_obj = self.pool.get('account.account.type')
162 ids = acc_type_obj.search(cr, uid, [])
163 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context=context)
164 return [(r['code'], r['name']) for r in res]
166 #----------------------------------------------------------
168 #----------------------------------------------------------
170 class account_tax(osv.osv):
171 _name = 'account.tax'
174 class account_account(osv.osv):
175 _order = "parent_left"
176 _parent_order = "code"
177 _name = "account.account"
178 _description = "Account"
180 logger = netsvc.Logger()
182 def search(self, cr, uid, args, offset=0, limit=None, order=None,
183 context=None, count=False):
188 while pos < len(args):
190 if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
191 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
192 if args[pos][0] == 'journal_id':
196 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
197 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
198 args[pos] = ('type','not in',('consolidation','view'))
200 ids3 = map(lambda x: x.id, jour.type_control_ids)
201 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
202 ids1 += map(lambda x: x.id, jour.account_control_ids)
203 args[pos] = ('id', 'in', ids1)
206 if context and context.has_key('consolidate_childs'): #add consolidated childs of accounts
207 ids = super(account_account, self).search(cr, uid, args, offset, limit,
208 order, context=context, count=count)
209 for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
210 ids.append(consolidate_child.id)
213 return super(account_account, self).search(cr, uid, args, offset, limit,
214 order, context=context, count=count)
216 def _get_children_and_consol(self, cr, uid, ids, context=None):
219 #this function search for all the children and all consolidated children (recursively) of the given account ids
220 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
222 for rec in self.browse(cr, uid, ids2, context=context):
223 for child in rec.child_consol_ids:
224 ids3.append(child.id)
226 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
229 def __compute(self, cr, uid, ids, field_names, arg=None, context=None,
230 query='', query_params=()):
231 """ compute the balance, debit and/or credit for the provided
235 `field_names`: the fields to compute (a list of any of
236 'balance', 'debit' and 'credit')
237 `arg`: unused fields.function stuff
238 `query`: additional query filter (as a string)
239 `query_params`: parameters for the provided query string
240 (__compute will handle their escaping) as a
244 'balance': "COALESCE(SUM(l.debit),0) " \
245 "- COALESCE(SUM(l.credit), 0) as balance",
246 'debit': "COALESCE(SUM(l.debit), 0) as debit",
247 'credit': "COALESCE(SUM(l.credit), 0) as credit"
249 #get all the necessary accounts
250 children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
251 #compute for each account the balance/debit/credit from the move lines
253 if children_and_consolidated:
254 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
258 wheres.append(query.strip())
259 if aml_query.strip():
260 wheres.append(aml_query.strip())
261 filters = " AND ".join(wheres)
262 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
263 'Filters: %s'%filters)
264 # IN might not work ideally in case there are too many
265 # children_and_consolidated, in that case join on a
267 # SELECT l.account_id as id FROM account_move_line l
268 # INNER JOIN (VALUES (id1), (id2), (id3), ...) AS tmp (id)
269 # ON l.account_id = tmp.id
270 # or make _get_children_and_consol return a query and join on that
271 request = ("SELECT l.account_id as id, " +\
272 ', '.join(map(mapping.__getitem__, field_names)) +
273 " FROM account_move_line l" \
274 " WHERE l.account_id IN %s " \
276 " GROUP BY l.account_id")
277 params = (tuple(children_and_consolidated),) + query_params
278 cr.execute(request, params)
279 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
280 'Status: %s'%cr.statusmessage)
282 for res in cr.dictfetchall():
283 accounts[res['id']] = res
285 # consolidate accounts with direct children
286 children_and_consolidated.reverse()
287 brs = list(self.browse(cr, uid, children_and_consolidated, context=context))
292 for child in current.child_id:
293 if child.id not in sums:
296 brs.insert(0, brs.pop(brs.index(child)))
301 for fn in field_names:
302 sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
304 sums[current.id][fn] += sum(sums[child.id][fn] for child in current.child_id)
306 null_result = dict((fn, 0.0) for fn in field_names)
308 res[id] = sums.get(id, null_result)
311 def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
313 for rec in self.browse(cr, uid, ids, context):
314 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
317 def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
319 for record in self.browse(cr, uid, ids, context):
320 if record.child_parent_ids:
321 result[record.id] = [x.id for x in record.child_parent_ids]
323 result[record.id] = []
325 if record.child_consol_ids:
326 for acc in record.child_consol_ids:
327 if acc.id not in result[record.id]:
328 result[record.id].append(acc.id)
332 def _get_level(self, cr, uid, ids, field_name, arg, context={}):
334 accounts = self.browse(cr, uid, ids)
335 for account in accounts:
337 if account.parent_id:
338 obj = self.browse(cr, uid, account.parent_id.id)
339 level = obj.level + 1
340 res[account.id] = level
344 'name': fields.char('Name', size=128, required=True, select=True),
345 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
346 'code': fields.char('Code', size=64, required=True),
347 'type': fields.selection([
349 ('other', 'Regular'),
350 ('receivable', 'Receivable'),
351 ('payable', 'Payable'),
352 ('liquidity','Liquidity'),
353 ('consolidation', 'Consolidation'),
354 ('closed', 'Closed'),
355 ], 'Internal Type', required=True, help="This type is used to differentiate types with "\
356 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
357 "can have children accounts for multi-company consolidations, payable/receivable are for "\
358 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
359 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
360 help="These types are defined according to your country. The type contains more information "\
361 "about the account and its specificities."),
362 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
363 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
364 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
365 'child_id': fields.function(_get_child_ids, method=True, type='many2many', relation="account.account", string="Child Accounts"),
366 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Balance', multi='balance'),
367 'credit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Credit', multi='balance'),
368 'debit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Debit', multi='balance'),
369 'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."),
370 'shortcut': fields.char('Shortcut', size=12),
371 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
372 'account_id', 'tax_id', 'Default Taxes'),
373 'note': fields.text('Note'),
374 'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
375 'company_id': fields.many2one('res.company', 'Company', required=True),
376 '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."),
378 'parent_left': fields.integer('Parent Left', select=1),
379 'parent_right': fields.integer('Parent Right', select=1),
380 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
382 'This will select how the current currency rate for outgoing transactions is computed. '\
383 'In most countries the legal method is "average" but only a few software systems are able to '\
384 'manage this. So if you import from another software system you may have to use the rate at date. ' \
385 'Incoming transactions always use the rate at date.', \
387 'level': fields.function(_get_level, string='Level', method=True, store=True, type='integer'),
394 'currency_mode': 'current',
395 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
398 def _check_recursion(self, cr, uid, ids):
399 obj_self = self.browse(cr, uid, ids[0])
400 p_id = obj_self.parent_id and obj_self.parent_id.id
401 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
404 cr.execute('SELECT DISTINCT child_id '\
405 'FROM account_account_consol_rel '\
406 'WHERE parent_id IN %s', (tuple(ids),))
407 child_ids = map(itemgetter(0), cr.fetchall())
409 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
412 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
413 if p_id and (p_id in s_ids):
420 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
423 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
425 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
433 if name and str(name).startswith('partner:'):
434 part_id = int(name.split(':')[1])
435 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
436 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
438 if name and str(name).startswith('type:'):
439 type = name.split(':')[1]
440 args += [('type', '=', type)]
445 ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
447 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
449 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
450 if not ids and len(name.split()) >= 2:
451 #Separating code and name of account for searching
452 operand1,operand2 = name.split(' ',1) #name can contain spaces e.g. OpenERP S.A.
453 ids = self.search(cr, user, [('code', operator, operand1), ('name', operator, operand2)]+ args, limit=limit)
455 ids = self.search(cr, user, args, context=context, limit=limit)
456 return self.name_get(cr, user, ids, context=context)
458 def name_get(self, cr, uid, ids, context=None):
461 reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
464 name = record['name']
466 name = record['code'] + ' '+name
467 res.append((record['id'], name))
470 def copy(self, cr, uid, id, default={}, context=None, done_list=[], local=False):
471 account = self.browse(cr, uid, id, context=context)
475 default = default.copy()
476 default['code'] = (account['code'] or '') + '(copy)'
479 if account.id in done_list:
481 done_list.append(account.id)
483 for child in account.child_id:
484 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
486 new_child_ids.append(child_ids)
487 default['child_parent_ids'] = [(6, 0, new_child_ids)]
489 default['child_parent_ids'] = False
490 return super(account_account, self).copy(cr, uid, id, default, context=context)
492 def _check_moves(self, cr, uid, ids, method, context=None):
493 line_obj = self.pool.get('account.move.line')
494 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
496 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
497 if method == 'write':
498 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
499 elif method == 'unlink':
500 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
501 #Checking whether the account is set as a property to any Partner or not
502 value = 'account.account,' + str(ids[0])
503 partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
505 raise osv.except_osv(_('Warning !'), _('You cannot remove/deactivate an account which is set as a property to any Partner.'))
508 def _check_allow_type_change(self, cr, uid, ids, new_type, context=None):
509 group1 = ['payable', 'receivable', 'other']
510 group2 = ['consolidation','view']
511 line_obj = self.pool.get('account.move.line')
512 for account in self.browse(cr, uid, ids, context=context):
513 old_type = account.type
514 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
515 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
516 #Check for 'Closed' type
517 if old_type == 'closed' and new_type !='closed':
518 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains account entries!"))
519 #Check for change From group1 to group2 and vice versa
520 if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
521 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,))
524 def write(self, cr, uid, ids, vals, context=None):
528 if 'company_id' in vals:
529 move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
531 raise osv.except_osv(_('Warning !'), _('You cannot modify Company of account as its related record exist in Entry Lines'))
532 if 'active' in vals and not vals['active']:
533 self._check_moves(cr, uid, ids, "write", context=context)
534 if 'type' in vals.keys():
535 self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
536 return super(account_account, self).write(cr, uid, ids, vals, context=context)
538 def unlink(self, cr, uid, ids, context=None):
539 self._check_moves(cr, uid, ids, "unlink", context=context)
540 return super(account_account, self).unlink(cr, uid, ids, context=context)
544 class account_journal_view(osv.osv):
545 _name = "account.journal.view"
546 _description = "Journal View"
548 'name': fields.char('Journal View', size=64, required=True),
549 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
553 account_journal_view()
556 class account_journal_column(osv.osv):
558 def _col_get(self, cr, user, context=None):
560 cols = self.pool.get('account.move.line')._columns
562 if col in ('period_id', 'journal_id'):
564 result.append( (col, cols[col].string) )
568 _name = "account.journal.column"
569 _description = "Journal Column"
571 'name': fields.char('Column Name', size=64, required=True),
572 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
573 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
574 'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column."),
575 'required': fields.boolean('Required'),
576 'readonly': fields.boolean('Readonly'),
580 account_journal_column()
582 class account_journal(osv.osv):
583 _name = "account.journal"
584 _description = "Journal"
586 'name': fields.char('Journal Name', size=64, required=True, translate=True),
587 '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."),
588 '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,
589 help="Select 'Sale' for Sale journal to be used at the time of making invoice."\
590 " Select 'Purchase' for Purchase Journal to be used at the time of approving purchase order."\
591 " Select 'Cash' to be used at the time of making payment."\
592 " Select 'General' to be used at the time of stock input/output."\
593 " Select 'Situation' to be used at the time of making vouchers."),
594 'refund_journal': fields.boolean('Refund Journal', help='Fill this if the journal is to be used for refunds of invoices.'),
595 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
596 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
597 '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."),
598 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
599 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
600 '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."),
601 '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"),
602 '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."),
603 '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),
604 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
605 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
606 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
607 '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.'),
608 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
609 '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'),
613 'user_id': lambda self,cr,uid,context: uid,
614 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
617 def write(self, cr, uid, ids, vals, context=None):
618 if 'company_id' in vals:
619 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
621 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this journal as its related record exist in Entry Lines'))
622 return super(account_journal, self).write(cr, uid, ids, vals, context=context)
624 def create_sequence(self, cr, uid, vals, context=None):
626 Create new entry sequence for every new Joural
627 @param cr: cursor to database
628 @param user: id of current user
629 @param ids: list of record ids to be process
630 @param context: context arguments, like lang, time zone
631 @return: return a result
633 seq_pool = self.pool.get('ir.sequence')
634 seq_typ_pool = self.pool.get('ir.sequence.type')
637 code = vals['code'].lower()
643 seq_typ_pool.create(cr, uid, types)
649 'prefix': code + "/%(year)s/",
651 'number_increment': 1
653 return seq_pool.create(cr, uid, seq)
655 def create(self, cr, uid, vals, context=None):
656 if not 'sequence_id' in vals or not vals['sequence_id']:
657 vals.update({'sequence_id': self.create_sequence(cr, uid, vals, context)})
658 return super(account_journal, self).create(cr, uid, vals, context)
660 def name_get(self, cr, user, ids, context=None):
662 Returns a list of tupples containing id, name.
663 result format: {[(id, name), (id, name), ...]}
665 @param cr: A database cursor
666 @param user: ID of the user currently logged in
667 @param ids: list of ids for which name should be read
668 @param context: context arguments, like lang, time zone
670 @return: Returns a list of tupples containing id, name
672 result = self.browse(cr, user, ids, context)
677 name = "%s (%s)" % (rs.name, rs.currency.name)
678 res += [(rs.id, name)]
681 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
687 if context.get('journal_type', False):
688 args += [('type','=',context.get('journal_type'))]
690 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit, context=context)
692 ids = self.search(cr, user, [('name', 'ilike', name)]+ args, limit=limit, context=context)#fix it ilike should be replace with operator
694 return self.name_get(cr, user, ids, context=context)
696 def onchange_type(self, cr, uid, ids, type, currency):
697 obj_data = self.pool.get('ir.model.data')
698 user_pool = self.pool.get('res.users')
701 'sale':'account_sp_journal_view',
702 'sale_refund':'account_sp_refund_journal_view',
703 'purchase':'account_sp_journal_view',
704 'purchase_refund':'account_sp_refund_journal_view',
705 'cash':'account_journal_bank_view',
706 'bank':'account_journal_bank_view',
707 'general':'account_journal_view',
708 'situation':'account_journal_view'
713 view_id = type_map.get(type, 'general')
715 user = user_pool.browse(cr, uid, uid)
716 if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
717 view_id = 'account_journal_bank_view_multi'
719 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
720 data = obj_data.browse(cr, uid, data_id[0])
723 'centralisation':type == 'situation',
724 'view_id':data.res_id,
733 class account_fiscalyear(osv.osv):
734 _name = "account.fiscalyear"
735 _description = "Fiscal Year"
737 'name': fields.char('Fiscal Year', size=64, required=True),
738 'code': fields.char('Code', size=6, required=True),
739 'company_id': fields.many2one('res.company', 'Company', required=True),
740 'date_start': fields.date('Start Date', required=True),
741 'date_stop': fields.date('End Date', required=True),
742 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
743 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True),
747 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
749 _order = "date_start"
751 def _check_fiscal_year(self, cr, uid, ids, context=None):
752 current_fiscal_yr = self.browse(cr, uid, ids, context=context)[0]
753 obj_fiscal_ids = self.search(cr, uid, [('company_id', '=', current_fiscal_yr.company_id.id)], context=context)
754 obj_fiscal_ids.remove(ids[0])
755 data_fiscal_yr = self.browse(cr, uid, obj_fiscal_ids, context=context)
757 for old_fy in data_fiscal_yr:
758 if old_fy.company_id.id == current_fiscal_yr['company_id'].id:
759 # Condition to check if the current fiscal year falls in between any previously defined fiscal year
760 if old_fy.date_start <= current_fiscal_yr['date_start'] <= old_fy.date_stop or \
761 old_fy.date_start <= current_fiscal_yr['date_stop'] <= old_fy.date_stop:
765 def _check_duration(self,cr,uid,ids):
766 obj_fy = self.browse(cr,uid,ids[0])
767 if obj_fy.date_stop < obj_fy.date_start:
772 (_check_duration, 'Error! The duration of the Fiscal Year is invalid. ', ['date_stop']),
773 (_check_fiscal_year, 'Error! You cannot define overlapping fiscal years',['date_start', 'date_stop'])
776 def create_period3(self,cr, uid, ids, context={}):
777 return self.create_period(cr, uid, ids, context, 3)
779 def create_period(self,cr, uid, ids, context={}, interval=1):
780 for fy in self.browse(cr, uid, ids, context):
781 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
782 while ds.strftime('%Y-%m-%d')<fy.date_stop:
783 de = ds + relativedelta(months=interval, days=-1)
785 if de.strftime('%Y-%m-%d')>fy.date_stop:
786 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
788 self.pool.get('account.period').create(cr, uid, {
789 'name': ds.strftime('%m/%Y'),
790 'code': ds.strftime('%m/%Y'),
791 'date_start': ds.strftime('%Y-%m-%d'),
792 'date_stop': de.strftime('%Y-%m-%d'),
793 'fiscalyear_id': fy.id,
795 ds = ds + relativedelta(months=interval)
798 def find(self, cr, uid, dt=None, exception=True, context={}):
800 dt = time.strftime('%Y-%m-%d')
801 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
804 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
809 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
816 ids = self.search(cr, user, [('code', 'ilike', name)]+ args, limit=limit)
818 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
819 return self.name_get(cr, user, ids, context=context)
823 class account_period(osv.osv):
824 _name = "account.period"
825 _description = "Account period"
827 'name': fields.char('Period Name', size=64, required=True),
828 'code': fields.char('Code', size=12),
829 'special': fields.boolean('Opening/Closing Period', size=12,
830 help="These periods can overlap."),
831 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
832 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
833 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
834 'state': fields.selection([('draft','Open'), ('done','Closed')], 'State', readonly=True,
835 help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
836 'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True)
841 _order = "date_start"
843 def _check_duration(self,cr,uid,ids,context={}):
844 obj_period=self.browse(cr,uid,ids[0])
845 if obj_period.date_stop < obj_period.date_start:
849 def _check_year_limit(self,cr,uid,ids,context={}):
850 for obj_period in self.browse(cr,uid,ids):
851 if obj_period.special:
854 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
855 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
856 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
857 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
860 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
861 for period in self.browse(cr, uid, pids):
862 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
867 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
868 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
871 def next(self, cr, uid, period, step, context={}):
872 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
877 def find(self, cr, uid, dt=None, context={}):
879 dt = time.strftime('%Y-%m-%d')
880 #CHECKME: shouldn't we check the state of the period?
881 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
883 raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create a fiscal year.')%dt)
886 def action_draft(self, cr, uid, ids, *args):
889 cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
890 cr.execute('update account_period set state=%s where id=%s', (mode, id))
893 def name_search(self, cr, user, name, args=None, operator='ilike', context={}, limit=80):
900 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
902 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
903 return self.name_get(cr, user, ids, context=context)
905 def write(self, cr, uid, ids, vals, context={}):
906 if 'company_id' in vals:
907 move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
909 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines'))
910 return super(account_period, self).write(cr, uid, ids, vals, context=context)
912 def build_ctx_periods(self, cr, uid, period_from_id, period_to_id):
913 period_from = self.browse(cr, uid, period_from_id)
914 period_date_start = period_from.date_start
915 company1_id = period_from.company_id.id
916 period_to = self.browse(cr, uid, period_to_id)
917 period_date_stop = period_to.date_stop
918 company2_id = period_to.company_id.id
919 if company1_id != company2_id:
920 raise osv.except_osv(_('Error'), _('You should have chosen periods that belongs to the same company'))
921 if period_date_start > period_date_stop:
922 raise osv.except_osv(_('Error'), _('Start period should be smaller then End period'))
923 return self.search(cr, uid, [('date_start', '>=', period_date_start), ('date_stop', '<=', period_date_stop), ('company_id', '=', company1_id)])
927 class account_journal_period(osv.osv):
928 _name = "account.journal.period"
929 _description = "Journal Period"
931 def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
932 result = {}.fromkeys(ids, 'STOCK_NEW')
933 for r in self.read(cr, uid, ids, ['state']):
935 'draft': 'STOCK_NEW',
936 'printed': 'STOCK_PRINT_PREVIEW',
937 'done': 'STOCK_DIALOG_AUTHENTICATION',
938 }.get(r['state'], 'STOCK_NEW')
942 'name': fields.char('Journal-Period Name', size=64, required=True),
943 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
944 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
945 'icon': fields.function(_icon_get, method=True, string='Icon', type='char', size=32),
946 '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."),
947 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
948 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.'),
949 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
950 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company')
953 def _check(self, cr, uid, ids, context={}):
954 for obj in self.browse(cr, uid, ids, context):
955 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))
958 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
961 def write(self, cr, uid, ids, vals, context={}):
962 self._check(cr, uid, ids, context)
963 return super(account_journal_period, self).write(cr, uid, ids, vals, context)
965 def create(self, cr, uid, vals, context={}):
966 period_id=vals.get('period_id',False)
968 period = self.pool.get('account.period').browse(cr, uid,period_id)
969 vals['state']=period.state
970 return super(account_journal_period, self).create(cr, uid, vals, context)
972 def unlink(self, cr, uid, ids, context={}):
973 self._check(cr, uid, ids, context)
974 return super(account_journal_period, self).unlink(cr, uid, ids, context)
982 account_journal_period()
984 class account_fiscalyear(osv.osv):
985 _inherit = "account.fiscalyear"
986 _description = "Fiscal Year"
988 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
991 def copy(self, cr, uid, id, default={}, context=None):
994 'end_journal_period_id': False
996 return super(account_fiscalyear, self).copy(cr, uid, id, default=default, context=context)
999 #----------------------------------------------------------
1001 #----------------------------------------------------------
1002 class account_move(osv.osv):
1003 _name = "account.move"
1004 _description = "Account Entry"
1007 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1009 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1010 result format: {[(id, name), (id, name), ...]}
1012 @param cr: A database cursor
1013 @param user: ID of the user currently logged in
1014 @param name: name to search
1015 @param args: other arguments
1016 @param operator: default operator is 'ilike', it can be changed
1017 @param context: context arguments, like lang, time zone
1018 @param limit: Returns first 'n' ids of complete result, default is 80.
1020 @return: Returns a list of tuples containing id and name
1029 ids += self.search(cr, user, [('name','ilike',name)]+args, limit=limit, context=context)
1031 if not ids and name and type(name) == int:
1032 ids += self.search(cr, user, [('id','=',name)]+args, limit=limit, context=context)
1035 ids += self.search(cr, user, args, limit=limit, context=context)
1037 return self.name_get(cr, user, ids, context=context)
1039 def name_get(self, cursor, user, ids, context=None):
1040 if isinstance(ids, (int, long)):
1045 data_move = self.pool.get('account.move').browse(cursor,user,ids)
1046 for move in data_move:
1047 if move.state=='draft':
1048 name = '*' + str(move.id)
1051 res.append((move.id, name))
1054 def _get_period(self, cr, uid, context):
1055 periods = self.pool.get('account.period').find(cr, uid)
1060 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1061 if not ids: return {}
1062 cr.execute( 'SELECT move_id, SUM(debit) '\
1063 'FROM account_move_line '\
1064 'WHERE move_id IN %s '\
1065 'GROUP BY move_id', (tuple(ids),))
1066 result = dict(cr.fetchall())
1068 result.setdefault(id, 0.0)
1071 def _search_amount(self, cr, uid, obj, name, args, context):
1075 if isinstance(cond[2],(list,tuple)):
1076 if cond[1] in ['in','not in']:
1077 amount = tuple(cond[2])
1081 if cond[1] in ['=like', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'child_of']:
1084 cr.execute("select move_id from account_move_line group by move_id having sum(debit) %s %%s" % (cond[1]),(amount,))
1085 res_ids = set(id[0] for id in cr.fetchall())
1086 ids = ids and (ids & res_ids) or res_ids
1088 return [('id','in',tuple(ids))]
1090 return [('id', '=', '0')]
1093 'name': fields.char('Number', size=64, required=True),
1094 'ref': fields.char('Reference', size=64),
1095 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1096 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1097 'state': fields.selection([('draft','Unposted'), ('posted','Posted')], 'State', required=True, readonly=True,
1098 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.'),
1099 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1100 '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.'),
1101 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner", store=True),
1102 'amount': fields.function(_amount_compute, method=True, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1103 'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}),
1104 'narration':fields.text('Narration'),
1105 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1110 'period_id': _get_period,
1111 'date': time.strftime('%Y-%m-%d'),
1112 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1115 def _check_centralisation(self, cursor, user, ids):
1116 for move in self.browse(cursor, user, ids):
1117 if move.journal_id.centralisation:
1118 move_ids = self.search(cursor, user, [
1119 ('period_id', '=', move.period_id.id),
1120 ('journal_id', '=', move.journal_id.id),
1122 if len(move_ids) > 1:
1126 def _check_period_journal(self, cursor, user, ids):
1127 for move in self.browse(cursor, user, ids):
1128 for line in move.line_id:
1129 if line.period_id.id != move.period_id.id:
1131 if line.journal_id.id != move.journal_id.id:
1136 (_check_centralisation,
1137 'You cannot create more than one move per period on centralized journal',
1139 (_check_period_journal,
1140 'You cannot create entries on different periods/journals in the same move',
1144 def post(self, cr, uid, ids, context=None):
1147 invoice = context.get('invoice', False)
1148 valid_moves = self.validate(cr, uid, ids, context)
1151 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" !'))
1152 obj_sequence = self.pool.get('ir.sequence')
1153 for move in self.browse(cr, uid, valid_moves):
1156 journal = move.journal_id
1158 if invoice and invoice.internal_number:
1159 new_name = invoice.internal_number
1161 if journal.sequence_id:
1162 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1163 new_name = obj_sequence.get_id(cr, uid, journal.sequence_id.id, context=c)
1165 raise osv.except_osv(_('Error'), _('No sequence defined on the journal !'))
1168 self.write(cr, uid, [move.id], {'name':new_name})
1170 cr.execute('UPDATE account_move '\
1173 ('posted', tuple(valid_moves),))
1177 def button_validate(self, cursor, user, ids, context=None):
1178 for move in self.browse(cursor, user, ids):
1180 for line in move.line_id:
1181 account = line.account_id
1184 account = account.parent_id
1187 elif top<>account2.id:
1188 raise osv.except_osv(_('Error !'), _('You cannot validate a Journal Entry unless all journal items are in same chart of accounts !'))
1189 return self.post(cursor, user, ids, context=context)
1191 def button_cancel(self, cr, uid, ids, context={}):
1192 for line in self.browse(cr, uid, ids, context):
1193 if not line.journal_id.update_posted:
1194 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.'))
1196 cr.execute('UPDATE account_move '\
1198 'WHERE id IN %s', ('draft', tuple(ids),))
1201 def write(self, cr, uid, ids, vals, context={}):
1203 c['novalidate'] = True
1204 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
1205 self.validate(cr, uid, ids, context)
1209 # TODO: Check if period is closed !
1211 def create(self, cr, uid, vals, context=None):
1212 context = context or {}
1213 if 'line_id' in vals and context.get('copy'):
1214 for l in vals['line_id']:
1217 'reconcile_id':False,
1218 'reconcil_partial_id':False,
1219 'analytic_lines':False,
1223 'account_tax_id':False,
1226 if 'journal_id' in vals and vals.get('journal_id', False):
1227 for l in vals['line_id']:
1229 l[2]['journal_id'] = vals['journal_id']
1230 context['journal_id'] = vals['journal_id']
1231 if 'period_id' in vals:
1232 for l in vals['line_id']:
1234 l[2]['period_id'] = vals['period_id']
1235 context['period_id'] = vals['period_id']
1237 default_period = self._get_period(cr, uid, context)
1238 for l in vals['line_id']:
1240 l[2]['period_id'] = default_period
1241 context['period_id'] = default_period
1243 if 'line_id' in vals:
1245 c['novalidate'] = True
1246 result = super(account_move, self).create(cr, uid, vals, c)
1247 self.validate(cr, uid, [result], context)
1249 result = super(account_move, self).create(cr, uid, vals, context)
1252 def copy(self, cr, uid, id, default={}, context={}):
1253 context = context or {}
1261 return super(account_move, self).copy(cr, uid, id, default, context)
1263 def unlink(self, cr, uid, ids, context=None, check=True):
1264 context = context or {}
1266 obj_move_line = self.pool.get('account.move.line')
1267 for move in self.browse(cr, uid, ids, context):
1268 if move['state'] != 'draft':
1269 raise osv.except_osv(_('UserError'),
1270 _('You can not delete posted movement: "%s"!') % \
1272 line_ids = map(lambda x: x.id, move.line_id)
1273 context['journal_id'] = move.journal_id.id
1274 context['period_id'] = move.period_id.id
1275 obj_move_line._update_check(cr, uid, line_ids, context)
1276 obj_move_line.unlink(cr, uid, line_ids, context=context)
1277 toremove.append(move.id)
1278 result = super(account_move, self).unlink(cr, uid, toremove, context)
1281 def _compute_balance(self, cr, uid, id, context={}):
1282 move = self.browse(cr, uid, [id])[0]
1284 for line in move.line_id:
1285 amount+= (line.debit - line.credit)
1288 def _centralise(self, cr, uid, move, mode, context=None):
1289 assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1294 account_id = move.journal_id.default_debit_account_id.id
1297 raise osv.except_osv(_('UserError'),
1298 _('There is no default default debit account defined \n' \
1299 'on journal "%s"') % move.journal_id.name)
1301 account_id = move.journal_id.default_credit_account_id.id
1304 raise osv.except_osv(_('UserError'),
1305 _('There is no default default credit account defined \n' \
1306 'on journal "%s"') % move.journal_id.name)
1308 # find the first line of this move with the current mode
1309 # or create it if it doesn't exist
1310 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1315 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1316 line_id = self.pool.get('account.move.line').create(cr, uid, {
1317 'name': _(mode.capitalize()+' Centralisation'),
1318 'centralisation': mode,
1319 'account_id': account_id,
1321 'journal_id': move.journal_id.id,
1322 'period_id': move.period_id.id,
1323 'date': move.period_id.date_stop,
1328 # find the first line of this move with the other mode
1329 # so that we can exclude it from our calculation
1330 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1337 cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1338 result = cr.fetchone()[0] or 0.0
1339 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1343 # Validate a balanced move. If it is a centralised journal, create a move.
1345 def validate(self, cr, uid, ids, context={}):
1346 if context and ('__last_update' in context):
1347 del context['__last_update']
1349 valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1350 obj_analytic_line = self.pool.get('account.analytic.line')
1351 obj_move_line = self.pool.get('account.move.line')
1352 for move in self.browse(cr, uid, ids, context):
1353 # Unlink old analytic lines on move_lines
1354 for obj_line in move.line_id:
1355 for obj in obj_line.analytic_lines:
1356 obj_analytic_line.unlink(cr,uid,obj.id)
1358 journal = move.journal_id
1363 for line in move.line_id:
1364 amount += line.debit - line.credit
1365 line_ids.append(line.id)
1366 if line.state=='draft':
1367 line_draft_ids.append(line.id)
1370 company_id = line.account_id.company_id.id
1371 if not company_id == line.account_id.company_id.id:
1372 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1374 if line.account_id.currency_id and line.currency_id:
1375 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):
1376 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)))
1378 if abs(amount) < 10 ** -4:
1379 # If the move is balanced
1380 # Add to the list of valid moves
1381 # (analytic lines will be created later for valid moves)
1382 valid_moves.append(move)
1384 # Check whether the move lines are confirmed
1386 if not line_draft_ids:
1388 # Update the move lines (set them as valid)
1390 obj_move_line.write(cr, uid, line_draft_ids, {
1391 'journal_id': move.journal_id.id,
1392 'period_id': move.period_id.id,
1394 }, context, check=False)
1399 if journal.type in ('purchase','sale'):
1400 for line in move.line_id:
1402 key = (line.account_id.id, line.tax_code_id.id)
1404 code = account2[key][0]
1405 amount = account2[key][1] * (line.debit + line.credit)
1406 elif line.account_id.id in account:
1407 code = account[line.account_id.id][0]
1408 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1409 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1410 obj_move_line.write(cr, uid, [line.id], {
1411 'tax_code_id': code,
1412 'tax_amount': amount
1413 }, context, check=False)
1414 elif journal.centralisation:
1415 # If the move is not balanced, it must be centralised...
1417 # Add to the list of valid moves
1418 # (analytic lines will be created later for valid moves)
1419 valid_moves.append(move)
1422 # Update the move lines (set them as valid)
1424 self._centralise(cr, uid, move, 'debit', context=context)
1425 self._centralise(cr, uid, move, 'credit', context=context)
1426 obj_move_line.write(cr, uid, line_draft_ids, {
1428 }, context, check=False)
1430 # We can't validate it (it's unbalanced)
1431 # Setting the lines as draft
1432 obj_move_line.write(cr, uid, line_ids, {
1433 'journal_id': move.journal_id.id,
1434 'period_id': move.period_id.id,
1436 }, context, check=False)
1437 # Create analytic lines for the valid moves
1438 for record in valid_moves:
1439 obj_move_line.create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1441 valid_moves = [move.id for move in valid_moves]
1442 return len(valid_moves) > 0 and valid_moves or False
1446 class account_move_reconcile(osv.osv):
1447 _name = "account.move.reconcile"
1448 _description = "Account Reconciliation"
1450 'name': fields.char('Name', size=64, required=True),
1451 'type': fields.char('Type', size=16, required=True),
1452 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1453 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1454 'create_date': fields.date('Creation date', readonly=True),
1457 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1459 def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1461 for rec in self.browse(cr, uid, ids, context):
1462 for line in rec.line_partial_ids:
1463 total += (line.debit or 0.0) - (line.credit or 0.0)
1465 self.pool.get('account.move.line').write(cr, uid,
1466 map(lambda x: x.id, rec.line_partial_ids),
1467 {'reconcile_id': rec.id }
1471 def name_get(self, cr, uid, ids, context=None):
1475 for r in self.browse(cr, uid, ids, context):
1476 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1478 name = '%s (%.2f)' % (r.name, total)
1479 result.append((r.id,name))
1481 result.append((r.id,r.name))
1485 account_move_reconcile()
1487 #----------------------------------------------------------
1489 #----------------------------------------------------------
1492 child_depend: la taxe depend des taxes filles
1494 class account_tax_code(osv.osv):
1496 A code for the tax object.
1498 This code is used for some tax declarations.
1500 def _sum(self, cr, uid, ids, name, args, context, where ='', where_params=()):
1501 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1502 if context.get('based_on', 'invoices') == 'payments':
1503 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1504 FROM account_move_line AS line, \
1505 account_move AS move \
1506 LEFT JOIN account_invoice invoice ON \
1507 (invoice.move_id = move.id) \
1508 WHERE line.tax_code_id IN %s '+where+' \
1509 AND move.id = line.move_id \
1510 AND ((invoice.state = \'paid\') \
1511 OR (invoice.id IS NULL)) \
1512 GROUP BY line.tax_code_id',
1513 (parent_ids,) + where_params)
1515 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1516 FROM account_move_line AS line, \
1517 account_move AS move \
1518 WHERE line.tax_code_id IN %s '+where+' \
1519 AND move.id = line.move_id \
1520 GROUP BY line.tax_code_id',
1521 (parent_ids,) + where_params)
1522 res=dict(cr.fetchall())
1523 obj_precision = self.pool.get('decimal.precision')
1524 for record in self.browse(cr, uid, ids, context):
1525 def _rec_get(record):
1526 amount = res.get(record.id, 0.0)
1527 for rec in record.child_ids:
1528 amount += _rec_get(rec) * rec.sign
1530 res[record.id] = round(_rec_get(record), obj_precision.precision_get(cr, uid, 'Account'))
1533 def _sum_year(self, cr, uid, ids, name, args, context=None):
1536 move_state = ('posted', )
1537 if context.get('state', 'all') == 'all':
1538 move_state = ('draft', 'posted', )
1539 if context.get('fiscalyear_id', False):
1540 fiscalyear_id = context['fiscalyear_id']
1542 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1546 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1548 where = ' AND line.period_id IN %s AND move.state IN %s '
1549 where_params = (tuple(pids), move_state)
1550 return self._sum(cr, uid, ids, name, args, context,
1551 where=where, where_params=where_params)
1553 def _sum_period(self, cr, uid, ids, name, args, context):
1556 move_state = ('posted', )
1557 if context.get('state', False) == 'all':
1558 move_state = ('draft', 'posted', )
1559 if context.get('period_id', False):
1560 period_id = context['period_id']
1562 period_id = self.pool.get('account.period').find(cr, uid)
1564 return dict.fromkeys(ids, 0.0)
1565 period_id = period_id[0]
1566 return self._sum(cr, uid, ids, name, args, context,
1567 where=' AND line.period_id=%s AND move.state IN %s', where_params=(period_id, move_state))
1569 _name = 'account.tax.code'
1570 _description = 'Tax Code'
1573 'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1574 'code': fields.char('Case Code', size=64),
1575 'info': fields.text('Description'),
1576 'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1577 'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1578 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1579 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1580 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1581 'company_id': fields.many2one('res.company', 'Company', required=True),
1582 '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.'),
1583 '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"),
1587 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1592 ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1593 return self.name_get(cr, user, ids, context)
1596 def name_get(self, cr, uid, ids, context=None):
1597 if isinstance(ids, (int, long)):
1601 if isinstance(ids, (int, long)):
1603 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1604 return [(x['id'], (x['code'] and (x['code'] + ' - ') or '') + x['name']) \
1607 def _default_company(self, cr, uid, context={}):
1608 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1610 return user.company_id.id
1611 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1613 'company_id': _default_company,
1615 'notprintable': False,
1618 def copy(self, cr, uid, id, default=None, context=None):
1621 default = default.copy()
1622 default.update({'line_ids': []})
1623 return super(account_tax_code, self).copy(cr, uid, id, default, context)
1625 _check_recursion = check_cycle
1627 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1629 _order = 'code,name'
1632 class account_tax(osv.osv):
1636 Type: percent, fixed, none, code
1637 PERCENT: tax = price * amount
1638 FIXED: tax = price + amount
1640 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1641 return result in the context
1642 Ex: result=round(price_unit*0.21,4)
1644 _name = 'account.tax'
1645 _description = 'Tax'
1647 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1648 '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."),
1649 'amount': fields.float('Amount', required=True, digits_compute=dp.get_precision('Account'), help="For taxes of type percentage, enter % ratio between 0-1."),
1650 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the tax without removing it."),
1651 'type': fields.selection( [('percent','Percentage'), ('fixed','Fixed Amount'), ('none','None'), ('code','Python Code'), ('balance','Balance')], 'Tax Type', required=True,
1652 help="The computation method for the tax amount."),
1653 'applicable_type': fields.selection( [('true','Always'), ('code','Given by Python Code')], 'Applicability', required=True,
1654 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1655 '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."),
1656 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1657 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1658 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1659 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1660 '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."),
1661 'python_compute':fields.text('Python Code'),
1662 'python_compute_inv':fields.text('Python Code (reverse)'),
1663 'python_applicable':fields.text('Python Code'),
1666 # Fields used for the VAT declaration
1668 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the VAT declaration."),
1669 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the VAT declaration."),
1670 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1671 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1673 # Same fields for refund invoices
1675 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1676 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1677 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1678 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1679 '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"),
1680 'company_id': fields.many2one('res.company', 'Company', required=True),
1681 'description': fields.char('Tax Code',size=32),
1682 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1683 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1687 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1689 Returns a list of tupples containing id, name, as internally it is called {def name_get}
1690 result format: {[(id, name), (id, name), ...]}
1692 @param cr: A database cursor
1693 @param user: ID of the user currently logged in
1694 @param name: name to search
1695 @param args: other arguments
1696 @param operator: default operator is 'ilike', it can be changed
1697 @param context: context arguments, like lang, time zone
1698 @param limit: Returns first 'n' ids of complete result, default is 80.
1700 @return: Returns a list of tupples containing id and name
1707 ids = self.search(cr, user, args, limit=limit, context=context)
1708 return self.name_get(cr, user, ids, context=context)
1710 def write(self, cr, uid, ids, vals, context=None):
1711 if vals.get('type', False) and vals['type'] in ('none', 'code'):
1712 vals.update({'amount': 0.0})
1713 return super(account_tax, self).write(cr, uid, ids, vals, context=context)
1715 def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
1716 journal_pool = self.pool.get('account.journal')
1718 if context and context.has_key('type'):
1719 if context.get('type') in ('out_invoice','out_refund'):
1720 args += [('type_tax_use','in',['sale','all'])]
1721 elif context.get('type') in ('in_invoice','in_refund'):
1722 args += [('type_tax_use','in',['purchase','all'])]
1724 if context and context.has_key('journal_id'):
1725 journal = journal_pool.browse(cr, uid, context.get('journal_id'))
1726 if journal.type in ('sale', 'purchase'):
1727 args += [('type_tax_use','in',[journal.type,'all'])]
1729 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1731 def name_get(self, cr, uid, ids, context=None):
1735 for record in self.read(cr, uid, ids, ['description','name'], context=context):
1736 name = record['description'] and record['description'] or record['name']
1737 res.append((record['id'],name ))
1740 def _default_company(self, cr, uid, context=None):
1741 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1743 return user.company_id.id
1744 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1747 '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''',
1748 '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''',
1749 'applicable_type': 'true',
1754 'type_tax_use': 'all',
1760 'include_base_amount': False,
1761 'company_id': _default_company,
1765 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1767 obj_partener_address = self.pool.get('res.partner.address')
1769 if tax.applicable_type=='code':
1770 localdict = {'price_unit':price_unit, 'address':obj_partener_address.browse(cr, uid, address_id), 'product':product, 'partner':partner}
1771 exec tax.python_applicable in localdict
1772 if localdict.get('result', False):
1778 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1779 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1781 cur_price_unit=price_unit
1782 obj_partener_address = self.pool.get('res.partner.address')
1784 # we compute the amount for the current tax object and append it to the result
1786 data = {'id':tax.id,
1787 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1788 'account_collected_id':tax.account_collected_id.id,
1789 'account_paid_id':tax.account_paid_id.id,
1790 'base_code_id': tax.base_code_id.id,
1791 'ref_base_code_id': tax.ref_base_code_id.id,
1792 'sequence': tax.sequence,
1793 'base_sign': tax.base_sign,
1794 'tax_sign': tax.tax_sign,
1795 'ref_base_sign': tax.ref_base_sign,
1796 'ref_tax_sign': tax.ref_tax_sign,
1797 'price_unit': cur_price_unit,
1798 'tax_code_id': tax.tax_code_id.id,
1799 'ref_tax_code_id': tax.ref_tax_code_id.id,
1802 if tax.type=='percent':
1803 amount = cur_price_unit * tax.amount
1804 data['amount'] = amount
1806 elif tax.type=='fixed':
1807 data['amount'] = tax.amount
1808 data['tax_amount']=quantity
1809 # data['amount'] = quantity
1810 elif tax.type=='code':
1811 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1812 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1813 exec tax.python_compute in localdict
1814 amount = localdict['result']
1815 data['amount'] = amount
1816 elif tax.type=='balance':
1817 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1818 data['balance'] = cur_price_unit
1820 amount2 = data.get('amount', 0.0)
1822 if tax.child_depend:
1825 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1826 res.extend(child_tax)
1827 if tax.child_depend:
1829 for name in ('base','ref_base'):
1830 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1831 r[name+'_code_id'] = latest[name+'_code_id']
1832 r[name+'_sign'] = latest[name+'_sign']
1833 r['price_unit'] = latest['price_unit']
1834 latest[name+'_code_id'] = False
1835 for name in ('tax','ref_tax'):
1836 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1837 r[name+'_code_id'] = latest[name+'_code_id']
1838 r[name+'_sign'] = latest[name+'_sign']
1839 r['amount'] = data['amount']
1840 latest[name+'_code_id'] = False
1841 if tax.include_base_amount:
1842 cur_price_unit+=amount2
1845 def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1848 'total': 0.0, # Total without taxes
1849 'total_included: 0.0, # Total with taxes
1850 'taxes': [] # List of taxes, see compute for the format
1853 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1854 totalin = totalex = round(price_unit * quantity, precision)
1858 if tax.price_include:
1862 tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
1864 totalex -= r.get('amount', 0.0)
1867 totlex_qty=totalex/quantity
1870 tex = self._compute(cr, uid, tex, totlex_qty, quantity, address_id=address_id, product=product, partner=partner)
1872 totalin += r.get('amount', 0.0)
1875 'total_included': totalin,
1879 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1880 logger = netsvc.Logger()
1881 logger.notifyChannel("warning", netsvc.LOG_WARNING,
1882 "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
1883 return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
1885 def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1887 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1891 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1892 one tax for each tax id in IDS and their childs
1894 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1896 precision_pool = self.pool.get('decimal.precision')
1898 if r.get('balance',False):
1899 r['amount'] = round(r.get('balance', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account')) - total
1901 r['amount'] = round(r.get('amount', 0.0) * quantity, precision_pool.precision_get(cr, uid, 'Account'))
1902 total += r['amount']
1905 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1906 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1907 obj_partener_address = self.pool.get('res.partner.address')
1910 cur_price_unit = price_unit
1912 tax_parent_tot = 0.0
1914 if (tax.type=='percent') and not tax.include_base_amount:
1915 tax_parent_tot += tax.amount
1918 if (tax.type=='fixed') and not tax.include_base_amount:
1919 cur_price_unit -= tax.amount
1922 if tax.type=='percent':
1923 if tax.include_base_amount:
1924 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1926 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1928 elif tax.type=='fixed':
1931 elif tax.type=='code':
1932 address = address_id and obj_partener_address.browse(cr, uid, address_id) or None
1933 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1934 exec tax.python_compute_inv in localdict
1935 amount = localdict['result']
1936 elif tax.type=='balance':
1937 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1939 if tax.include_base_amount:
1940 cur_price_unit -= amount
1949 'account_collected_id': tax.account_collected_id.id,
1950 'account_paid_id': tax.account_paid_id.id,
1951 'base_code_id': tax.base_code_id.id,
1952 'ref_base_code_id': tax.ref_base_code_id.id,
1953 'sequence': tax.sequence,
1954 'base_sign': tax.base_sign,
1955 'tax_sign': tax.tax_sign,
1956 'ref_base_sign': tax.ref_base_sign,
1957 'ref_tax_sign': tax.ref_tax_sign,
1958 'price_unit': cur_price_unit,
1959 'tax_code_id': tax.tax_code_id.id,
1960 'ref_tax_code_id': tax.ref_tax_code_id.id,
1963 if tax.child_depend:
1967 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1968 res.extend(parent_tax)
1973 total += r['amount']
1975 r['price_unit'] -= total
1979 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1981 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1982 Price Unit is a VAT included price
1986 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1987 one tax for each tax id in IDS and their childs
1989 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1991 obj_precision = self.pool.get('decimal.precision')
1993 prec = obj_precision.precision_get(cr, uid, 'Account')
1994 if r.get('balance',False):
1995 r['amount'] = round(r['balance'] * quantity, prec) - total
1997 r['amount'] = round(r['amount'] * quantity, prec)
1998 total += r['amount']
2002 # ---------------------------------------------------------
2003 # Account Entries Models
2004 # ---------------------------------------------------------
2006 class account_model(osv.osv):
2007 _name = "account.model"
2008 _description = "Account Model"
2010 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
2011 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
2012 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
2013 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
2014 'legend': fields.text('Legend', readonly=True, size=100),
2018 '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'),
2020 def generate(self, cr, uid, ids, datas={}, context=None):
2023 account_move_obj = self.pool.get('account.move')
2024 account_move_line_obj = self.pool.get('account.move.line')
2025 pt_obj = self.pool.get('account.payment.term')
2030 if datas.get('date', False):
2031 context.update({'date': datas['date']})
2033 period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
2035 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
2036 period_id = period_id[0]
2038 for model in self.browse(cr, uid, ids, context):
2039 entry['name'] = model.name%{'year':time.strftime('%Y'), 'month':time.strftime('%m'), 'date':time.strftime('%Y-%m')}
2040 move_id = account_move_obj.create(cr, uid, {
2041 'ref': entry['name'],
2042 'period_id': period_id,
2043 'journal_id': model.journal_id.id,
2044 'date': context.get('date',time.strftime('%Y-%m-%d'))
2046 move_ids.append(move_id)
2047 for line in model.lines_id:
2048 analytic_account_id = False
2049 if line.analytic_account_id:
2050 if not model.journal_id.analytic_journal_id:
2051 raise osv.except_osv(_('No Analytic Journal !'),_("You have to define an analytic journal on the '%s' journal!") % (model.journal_id.name,))
2052 analytic_account_id = line.analytic_account_id.id
2055 'journal_id': model.journal_id.id,
2056 'period_id': period_id,
2057 'analytic_account_id': analytic_account_id
2060 date_maturity = time.strftime('%Y-%m-%d')
2061 if line.date_maturity == 'partner':
2062 if not line.partner_id:
2063 raise osv.except_osv(_('Error !'), _("Maturity date of entry line generated by model line '%s' of model '%s' is based on partner payment term! \
2064 \nPlease define partner on it!"%(line.name, model.name)))
2065 if line.partner_id.property_payment_term:
2066 payment_term_id = line.partner_id.property_payment_term.id
2067 pterm_list = pt_obj.compute(cr, uid, payment_term_id, value=1, date_ref=date_maturity)
2069 pterm_list = [l[0] for l in pterm_list]
2071 date_maturity = pterm_list[-1]
2075 'quantity': line.quantity,
2076 'debit': line.debit,
2077 'credit': line.credit,
2078 'account_id': line.account_id.id,
2080 'partner_id': line.partner_id.id,
2081 'date': context.get('date',time.strftime('%Y-%m-%d')),
2082 'date_maturity': date_maturity
2085 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
2086 account_move_line_obj.create(cr, uid, val, context=c)
2092 class account_model_line(osv.osv):
2093 _name = "account.model.line"
2094 _description = "Account Model Entries"
2096 'name': fields.char('Name', size=64, required=True),
2097 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
2098 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries"),
2099 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
2100 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
2101 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
2102 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete="cascade"),
2103 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
2104 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
2105 'currency_id': fields.many2one('res.currency', 'Currency'),
2106 'partner_id': fields.many2one('res.partner', 'Partner'),
2107 '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."),
2110 _sql_constraints = [
2111 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
2112 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
2114 account_model_line()
2116 # ---------------------------------------------------------
2117 # Account Subscription
2118 # ---------------------------------------------------------
2121 class account_subscription(osv.osv):
2122 _name = "account.subscription"
2123 _description = "Account Subscription"
2125 'name': fields.char('Name', size=64, required=True),
2126 'ref': fields.char('Reference', size=16),
2127 'model_id': fields.many2one('account.model', 'Model', required=True),
2129 'date_start': fields.date('Start Date', required=True),
2130 'period_total': fields.integer('Number of Periods', required=True),
2131 'period_nbr': fields.integer('Period', required=True),
2132 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
2133 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
2135 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
2138 'date_start': time.strftime('%Y-%m-%d'),
2139 'period_type': 'month',
2144 def state_draft(self, cr, uid, ids, context={}):
2145 self.write(cr, uid, ids, {'state':'draft'})
2148 def check(self, cr, uid, ids, context={}):
2150 for sub in self.browse(cr, uid, ids, context):
2152 for line in sub.lines_id:
2153 if not line.move_id.id:
2157 todone.append(sub.id)
2159 self.write(cr, uid, todone, {'state':'done'})
2162 def remove_line(self, cr, uid, ids, context={}):
2164 for sub in self.browse(cr, uid, ids, context):
2165 for line in sub.lines_id:
2166 if not line.move_id.id:
2167 toremove.append(line.id)
2169 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2170 self.write(cr, uid, ids, {'state':'draft'})
2173 def compute(self, cr, uid, ids, context={}):
2174 for sub in self.browse(cr, uid, ids, context):
2176 for i in range(sub.period_total):
2177 self.pool.get('account.subscription.line').create(cr, uid, {
2179 'subscription_id': sub.id,
2181 if sub.period_type=='day':
2182 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2183 if sub.period_type=='month':
2184 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2185 if sub.period_type=='year':
2186 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2187 self.write(cr, uid, ids, {'state':'running'})
2189 account_subscription()
2191 class account_subscription_line(osv.osv):
2192 _name = "account.subscription.line"
2193 _description = "Account Subscription Line"
2195 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2196 'date': fields.date('Date', required=True),
2197 'move_id': fields.many2one('account.move', 'Entry'),
2200 def move_create(self, cr, uid, ids, context=None):
2203 obj_model = self.pool.get('account.model')
2204 for line in self.browse(cr, uid, ids, context=context):
2208 move_ids = obj_model.generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2209 tocheck[line.subscription_id.id] = True
2210 self.write(cr, uid, [line.id], {'move_id':move_ids[0]})
2211 all_moves.extend(move_ids)
2213 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2217 account_subscription_line()
2219 # ---------------------------------------------------------------
2220 # Account Templates: Account, Tax, Tax Code and chart. + Wizard
2221 # ---------------------------------------------------------------
2223 class account_tax_template(osv.osv):
2224 _name = 'account.tax.template'
2225 account_tax_template()
2227 class account_account_template(osv.osv):
2229 _name = "account.account.template"
2230 _description ='Templates for Accounts'
2233 'name': fields.char('Name', size=128, required=True, select=True),
2234 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2235 'code': fields.char('Code', size=64),
2236 'type': fields.selection([
2237 ('receivable','Receivable'),
2238 ('payable','Payable'),
2240 ('consolidation','Consolidation'),
2241 ('liquidity','Liquidity'),
2242 ('other','Regular'),
2243 ('closed','Closed'),
2244 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2245 "special effects in OpenERP: view can not have entries, consolidation are accounts that "\
2246 "can have children accounts for multi-company consolidations, payable/receivable are for "\
2247 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2248 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2249 help="These types are defined according to your country. The type contains more information "\
2250 "about the account and its specificities."),
2251 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2252 'shortcut': fields.char('Shortcut', size=12),
2253 'note': fields.text('Note'),
2254 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2255 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2256 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2257 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2266 _check_recursion = check_cycle
2268 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
2272 def name_get(self, cr, uid, ids, context={}):
2275 reads = self.read(cr, uid, ids, ['name','code'], context)
2277 for record in reads:
2278 name = record['name']
2280 name = record['code']+' '+name
2281 res.append((record['id'],name ))
2284 account_account_template()
2286 class account_add_tmpl_wizard(osv.osv_memory):
2287 """Add one more account from the template.
2289 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2290 _name = 'account.addtmpl.wizard'
2292 def _get_def_cparent(self, cr, uid, context):
2293 acc_obj=self.pool.get('account.account')
2294 tmpl_obj=self.pool.get('account.account.template')
2295 tids=tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2296 if not tids or not tids[0]['parent_id']:
2298 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2300 if not ptids or not ptids[0]['code']:
2301 raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
2302 res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2304 return res and res[0] or False
2307 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2310 'cparent_id': _get_def_cparent,
2313 def action_create(self,cr,uid,ids,context=None):
2314 acc_obj = self.pool.get('account.account')
2315 tmpl_obj = self.pool.get('account.account.template')
2316 data = self.read(cr, uid, ids)
2317 company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2318 account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2320 'name': account_template.name,
2321 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2322 'code': account_template.code,
2323 'type': account_template.type,
2324 'user_type': account_template.user_type and account_template.user_type.id or False,
2325 'reconcile': account_template.reconcile,
2326 'shortcut': account_template.shortcut,
2327 'note': account_template.note,
2328 'parent_id': data[0]['cparent_id'],
2329 'company_id': company_id,
2331 acc_obj.create(cr, uid, vals)
2332 return {'type':'state', 'state': 'end' }
2334 def action_cancel(self, cr, uid, ids, context=None):
2335 return { 'type': 'state', 'state': 'end' }
2337 account_add_tmpl_wizard()
2339 class account_tax_code_template(osv.osv):
2341 _name = 'account.tax.code.template'
2342 _description = 'Tax Code Template'
2346 'name': fields.char('Tax Case Name', size=64, required=True),
2347 'code': fields.char('Case Code', size=64),
2348 'info': fields.text('Description'),
2349 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2350 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2351 'sign': fields.float('Sign For Parent', required=True),
2352 '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"),
2357 'notprintable': False,
2360 def name_get(self, cr, uid, ids, context=None):
2363 if isinstance(ids, (int, long)):
2365 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2366 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2369 _check_recursion = check_cycle
2371 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2373 _order = 'code,name'
2374 account_tax_code_template()
2377 class account_chart_template(osv.osv):
2378 _name="account.chart.template"
2379 _description= "Templates for Account Chart"
2382 'name': fields.char('Name', size=64, required=True),
2383 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
2384 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
2385 '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'),
2386 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
2387 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2388 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2389 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2390 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2391 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2392 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2393 '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'),
2396 account_chart_template()
2398 class account_tax_template(osv.osv):
2400 _name = 'account.tax.template'
2401 _description = 'Templates for Taxes'
2404 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2405 'name': fields.char('Tax Name', size=64, required=True),
2406 '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."),
2407 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2408 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
2409 '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."),
2410 '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."),
2411 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2412 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2413 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2414 '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."),
2415 'python_compute':fields.text('Python Code'),
2416 'python_compute_inv':fields.text('Python Code (reverse)'),
2417 'python_applicable':fields.text('Python Code'),
2420 # Fields used for the VAT declaration
2422 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2423 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2424 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2425 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2427 # Same fields for refund invoices
2429 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2430 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2431 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2432 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2433 '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."),
2434 'description': fields.char('Internal Name', size=32),
2435 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,)
2438 def name_get(self, cr, uid, ids, context={}):
2442 for record in self.read(cr, uid, ids, ['description','name'], context):
2443 name = record['description'] and record['description'] or record['name']
2444 res.append((record['id'],name ))
2447 def _default_company(self, cr, uid, context={}):
2448 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2450 return user.company_id.id
2451 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2454 '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''',
2455 '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''',
2456 'applicable_type': 'true',
2464 'include_base_amount': False,
2465 'type_tax_use': 'all',
2470 account_tax_template()
2472 # Fiscal Position Templates
2474 class account_fiscal_position_template(osv.osv):
2475 _name = 'account.fiscal.position.template'
2476 _description = 'Template for Fiscal Position'
2479 'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
2480 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2481 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2482 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2485 account_fiscal_position_template()
2487 class account_fiscal_position_tax_template(osv.osv):
2488 _name = 'account.fiscal.position.tax.template'
2489 _description = 'Template Tax Fiscal Position'
2490 _rec_name = 'position_id'
2493 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2494 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2495 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2498 account_fiscal_position_tax_template()
2500 class account_fiscal_position_account_template(osv.osv):
2501 _name = 'account.fiscal.position.account.template'
2502 _description = 'Template Account Fiscal Mapping'
2503 _rec_name = 'position_id'
2505 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Mapping', required=True, ondelete='cascade'),
2506 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2507 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2510 account_fiscal_position_account_template()
2512 # Multi charts of Accounts wizard
2514 class wizard_multi_charts_accounts(osv.osv_memory):
2516 Create a new account chart for a company.
2519 * an account chart template
2520 * a number of digits for formatting code of non-view accounts
2521 * a list of bank accounts owned by the company
2523 * generates all accounts from the template and assigns them to the right company
2524 * generates all taxes and tax codes, changing account assignations
2525 * generates all accounting properties and assigns them correctly
2527 _name='wizard.multi.charts.accounts'
2528 _inherit = 'res.config'
2531 'company_id':fields.many2one('res.company', 'Company', required=True),
2532 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2533 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts', required=True),
2534 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2535 '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."),
2536 "sale_tax": fields.many2one("account.tax.template", "Default Sale Tax"),
2537 "purchase_tax": fields.many2one("account.tax.template", "Default Purchase Tax"),
2539 def onchange_chart_template_id(self, cr, uid, ids, chart_template_id=False, context=None):
2542 res['value']["sale_tax"] = False
2543 res['value']["purchase_tax"] = False
2544 if chart_template_id:
2545 sale_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2546 , "=", chart_template_id), ('type_tax_use', 'in', ('sale','all'))], order="sequence")
2547 purchase_tax_ids = self.pool.get('account.tax.template').search(cr, uid, [("chart_template_id"
2548 , "=", chart_template_id), ('type_tax_use', 'in', ('purchase','all'))], order="sequence")
2549 res['value']["sale_tax"] = sale_tax_ids and sale_tax_ids[0] or False
2550 res['value']["purchase_tax"] = purchase_tax_ids and purchase_tax_ids[0] or False
2553 def _get_chart(self, cr, uid, context={}):
2554 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2559 def _get_default_accounts(self, cr, uid, context=None):
2560 accounts = [{'acc_name':'Current','account_type':'bank'},
2561 {'acc_name':'Deposit','account_type':'bank'},
2562 {'acc_name':'Cash','account_type':'cash'}]
2566 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, [uid], c)[0].company_id.id,
2567 'chart_template_id': _get_chart,
2568 'bank_accounts_id': _get_default_accounts,
2573 def execute(self, cr, uid, ids, context=None):
2574 obj_multi = self.browse(cr, uid, ids[0])
2575 obj_acc = self.pool.get('account.account')
2576 obj_acc_tax = self.pool.get('account.tax')
2577 obj_journal = self.pool.get('account.journal')
2578 obj_sequence = self.pool.get('ir.sequence')
2579 obj_acc_template = self.pool.get('account.account.template')
2580 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2581 obj_fiscal_position = self.pool.get('account.fiscal.position')
2582 obj_data = self.pool.get('ir.model.data')
2583 analytic_journal_obj = self.pool.get('account.analytic.journal')
2584 obj_tax_code = self.pool.get('account.tax.code')
2586 obj_acc_root = obj_multi.chart_template_id.account_root_id
2587 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2588 company_id = obj_multi.company_id.id
2591 acc_template_ref = {}
2592 tax_template_ref = {}
2593 tax_code_template_ref = {}
2596 #create all the tax code
2597 children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2598 children_tax_code_template.sort()
2599 for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2601 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2602 'code': tax_code_template.code,
2603 'info': tax_code_template.info,
2604 '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,
2605 'company_id': company_id,
2606 'sign': tax_code_template.sign,
2608 new_tax_code = obj_tax_code.create(cr, uid, vals)
2609 #recording the new tax code to do the mapping
2610 tax_code_template_ref[tax_code_template.id] = new_tax_code
2613 tax_template_to_tax = {}
2614 for tax in obj_multi.chart_template_id.tax_template_ids:
2618 'sequence': tax.sequence,
2619 'amount':tax.amount,
2621 'applicable_type': tax.applicable_type,
2622 'domain':tax.domain,
2623 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2624 'child_depend': tax.child_depend,
2625 'python_compute': tax.python_compute,
2626 'python_compute_inv': tax.python_compute_inv,
2627 'python_applicable': tax.python_applicable,
2628 '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,
2629 '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,
2630 'base_sign': tax.base_sign,
2631 'tax_sign': tax.tax_sign,
2632 '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,
2633 '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,
2634 'ref_base_sign': tax.ref_base_sign,
2635 'ref_tax_sign': tax.ref_tax_sign,
2636 'include_base_amount': tax.include_base_amount,
2637 'description':tax.description,
2638 'company_id': company_id,
2639 'type_tax_use': tax.type_tax_use
2641 new_tax = obj_acc_tax.create(cr, uid, vals_tax)
2642 tax_template_to_tax[tax.id] = new_tax
2643 #as the accounts have not been created yet, we have to wait before filling these fields
2644 todo_dict[new_tax] = {
2645 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2646 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2648 tax_template_ref[tax.id] = new_tax
2650 #deactivate the parent_store functionnality on account_account for rapidity purpose
2651 self.pool._init = True
2653 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2654 children_acc_template.sort()
2655 for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2657 for tax in account_template.tax_ids:
2658 tax_ids.append(tax_template_ref[tax.id])
2659 #create the account_account
2661 dig = obj_multi.code_digits
2662 code_main = account_template.code and len(account_template.code) or 0
2663 code_acc = account_template.code or ''
2664 if code_main>0 and code_main<=dig and account_template.type != 'view':
2665 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2667 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2668 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2670 'type': account_template.type,
2671 'user_type': account_template.user_type and account_template.user_type.id or False,
2672 'reconcile': account_template.reconcile,
2673 'shortcut': account_template.shortcut,
2674 'note': account_template.note,
2675 '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,
2676 'tax_ids': [(6,0,tax_ids)],
2677 'company_id': company_id,
2679 new_account = obj_acc.create(cr, uid, vals)
2680 acc_template_ref[account_template.id] = new_account
2681 #reactivate the parent_store functionnality on account_account
2682 self.pool._init = False
2683 self.pool.get('account.account')._parent_store_compute(cr)
2685 for key,value in todo_dict.items():
2686 if value['account_collected_id'] or value['account_paid_id']:
2687 obj_acc_tax.write(cr, uid, [key], {
2688 'account_collected_id': acc_template_ref[value['account_collected_id']],
2689 'account_paid_id': acc_template_ref[value['account_paid_id']],
2692 # Creating Journals Sales and Purchase
2694 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_sp_journal_view')])
2695 data = obj_data.browse(cr, uid, data_id[0])
2696 view_id = data.res_id
2698 seq_id = obj_sequence.search(cr, uid, [('name','=','Account Journal')])[0]
2700 if obj_multi.seq_journal:
2701 seq_id_sale = obj_sequence.search(cr, uid, [('name','=','Sale Journal')])[0]
2702 seq_id_purchase = obj_sequence.search(cr, uid, [('name','=','Purchase Journal')])[0]
2704 seq_id_sale = seq_id
2705 seq_id_purchase = seq_id
2707 vals_journal['view_id'] = view_id
2710 analitical_sale_ids = analytic_journal_obj.search(cr,uid,[('type','=','sale')])
2711 analitical_journal_sale = analitical_sale_ids and analitical_sale_ids[0] or False
2713 vals_journal['name'] = _('Sales Journal')
2714 vals_journal['type'] = 'sale'
2715 vals_journal['code'] = _('SAJ')
2716 vals_journal['sequence_id'] = seq_id_sale
2717 vals_journal['company_id'] = company_id
2718 vals_journal['analytic_journal_id'] = analitical_journal_sale
2720 if obj_multi.chart_template_id.property_account_receivable:
2721 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2722 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2724 obj_journal.create(cr,uid,vals_journal)
2727 analitical_purchase_ids = analytic_journal_obj.search(cr,uid,[('type','=','purchase')])
2728 analitical_journal_purchase = analitical_purchase_ids and analitical_purchase_ids[0] or False
2730 vals_journal['name'] = _('Purchase Journal')
2731 vals_journal['type'] = 'purchase'
2732 vals_journal['code'] = _('EXJ')
2733 vals_journal['sequence_id'] = seq_id_purchase
2734 vals_journal['view_id'] = view_id
2735 vals_journal['company_id'] = company_id
2736 vals_journal['analytic_journal_id'] = analitical_journal_purchase
2738 if obj_multi.chart_template_id.property_account_payable:
2739 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2740 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2742 obj_journal.create(cr,uid,vals_journal)
2745 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')])
2746 data = obj_data.browse(cr, uid, data_id[0])
2747 view_id_cash = data.res_id
2749 data_id = obj_data.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view_multi')])
2750 data = obj_data.browse(cr, uid, data_id[0])
2751 view_id_cur = data.res_id
2752 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2755 for line in obj_multi.bank_accounts_id:
2756 #create the account_account for this bank journal
2758 dig = obj_multi.code_digits
2759 if ref_acc_bank.code:
2761 new_code = str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num)
2763 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)),'0')) + str(current_num)
2766 'currency_id': line.currency_id and line.currency_id.id or False,
2769 'user_type': account_template.user_type and account_template.user_type.id or False,
2771 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2772 'company_id': company_id,
2774 acc_cash_id = obj_acc.create(cr,uid,vals)
2776 if obj_multi.seq_journal:
2778 'name': _('Bank Journal ') + vals['name'],
2779 'code': 'account.journal',
2781 seq_id = obj_sequence.create(cr,uid,vals_seq)
2783 #create the bank journal
2784 analitical_bank_ids = analytic_journal_obj.search(cr,uid,[('type','=','situation')])
2785 analitical_journal_bank = analitical_bank_ids and analitical_bank_ids[0] or False
2787 vals_journal['name']= vals['name']
2788 vals_journal['code']= _('BNK') + str(current_num)
2789 vals_journal['sequence_id'] = seq_id
2790 vals_journal['type'] = 'cash'
2791 vals_journal['company_id'] = company_id
2792 vals_journal['analytic_journal_id'] = analitical_journal_bank
2794 if line.currency_id:
2795 vals_journal['view_id'] = view_id_cur
2796 vals_journal['currency'] = line.currency_id.id
2798 vals_journal['view_id'] = view_id_cash
2799 vals_journal['default_credit_account_id'] = acc_cash_id
2800 vals_journal['default_debit_account_id'] = acc_cash_id
2801 obj_journal.create(cr, uid, vals_journal)
2804 #create the properties
2805 property_obj = self.pool.get('ir.property')
2806 fields_obj = self.pool.get('ir.model.fields')
2809 ('property_account_receivable','res.partner','account.account'),
2810 ('property_account_payable','res.partner','account.account'),
2811 ('property_account_expense_categ','product.category','account.account'),
2812 ('property_account_income_categ','product.category','account.account'),
2813 ('property_account_expense','product.template','account.account'),
2814 ('property_account_income','product.template','account.account'),
2815 ('property_reserve_and_surplus_account','res.company','account.account')
2817 for record in todo_list:
2819 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2820 account = getattr(obj_multi.chart_template_id, record[0])
2821 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2824 'company_id': company_id,
2825 'fields_id': field[0],
2826 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2830 #the property exist: modify it
2831 property_obj.write(cr, uid, r, vals)
2833 #create the property
2834 property_obj.create(cr, uid, vals)
2836 fp_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)])
2840 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2841 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2843 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2846 'company_id': company_id,
2847 'name': position.name,
2849 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2851 for tax in position.tax_ids:
2853 'tax_src_id': tax_template_ref[tax.tax_src_id.id],
2854 'tax_dest_id': tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2855 'position_id': new_fp,
2857 obj_tax_fp.create(cr, uid, vals_tax)
2859 for acc in position.account_ids:
2861 'account_src_id': acc_template_ref[acc.account_src_id.id],
2862 'account_dest_id': acc_template_ref[acc.account_dest_id.id],
2863 'position_id': new_fp,
2865 obj_ac_fp.create(cr, uid, vals_acc)
2867 ir_values = self.pool.get('ir.values')
2868 if obj_multi.sale_tax:
2869 ir_values.set(cr, uid, key='default', key2=False, name="taxes_id", company=obj_multi.company_id.id,
2870 models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.sale_tax.id]])
2871 if obj_multi.purchase_tax:
2872 ir_values.set(cr, uid, key='default', key2=False, name="supplier_taxes_id", company=obj_multi.company_id.id,
2873 models =[('product.product',False)], value=[tax_template_to_tax[obj_multi.purchase_tax.id]])
2875 wizard_multi_charts_accounts()
2877 class account_bank_accounts_wizard(osv.osv_memory):
2878 _name='account.bank.accounts.wizard'
2881 'acc_name':fields.char('Account Name.', size=64, required=True),
2882 'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2883 'currency_id':fields.many2one('res.currency', 'Currency'),
2884 'account_type':fields.selection([('cash','Cash'),('check','Check'),('bank','Bank')], 'Type', size=32),
2887 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
2890 account_bank_accounts_wizard()
2892 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: