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 ##############################################################################
22 from datetime import datetime
23 from dateutil.relativedelta import relativedelta
24 from operator import itemgetter
28 from osv import fields, osv
29 import decimal_precision as dp
30 from tools.misc import currency
31 from tools.translate import _
32 from tools import config
34 def check_cycle(self, cr, uid, ids):
35 """ climbs the ``self._table.parent_id`` chains for 100 levels or
36 until it can't find any more parent(s)
38 Returns true if it runs out of parents (no cycle), false if
39 it can recurse 100 times without ending all chains
43 cr.execute('SELECT DISTINCT parent_id '\
44 'FROM '+self._table+' '\
46 'AND parent_id IS NOT NULL',(tuple(ids),))
47 ids = map(itemgetter(0), cr.fetchall())
53 class account_payment_term(osv.osv):
54 _name = "account.payment.term"
55 _description = "Payment Term"
57 'name': fields.char('Payment Term', size=64, translate=True, required=True),
58 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the payment term without removing it."),
59 'note': fields.text('Description', translate=True),
60 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
63 'active': lambda *a: 1,
67 def compute(self, cr, uid, id, value, date_ref=False, context={}):
69 date_ref = datetime.now().strftime('%Y-%m-%d')
70 pt = self.browse(cr, uid, id, context)
73 for line in pt.line_ids:
74 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
75 if line.value == 'fixed':
76 amt = round(line.value_amount, prec)
77 elif line.value == 'procent':
78 amt = round(value * line.value_amount, prec)
79 elif line.value == 'balance':
80 amt = round(amount, prec)
82 next_date = datetime.strptime(date_ref, '%Y-%m-%d') + relativedelta(days=line.days)
84 next_date += relativedelta(day=31)
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')], 'Value',
102 required=True, help="""Example: 14 days 2%, 30 days net
103 1. Line 1: percent 0.02 14 days
104 2. Line 2: balance 30 days"""),
106 'value_amount': fields.float('Value Amount', help="For Value percent enter % ratio between 0-1."),
107 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
108 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
109 'days2': fields.integer('Day of the Month', required=True, help="Day of the month, set -1 for the last day of the current month. If it's positive, it gives the day of the next month. Set 0 for net days (otherwise it's based on the beginning of the month)."),
110 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
113 'value': lambda *a: 'balance',
114 'sequence': lambda *a: 5,
115 'days2': lambda *a: 0,
119 def _check_percent(self, cr, uid, ids, context={}):
120 obj = self.browse(cr, uid, ids[0])
121 if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
126 (_check_percent, _('Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% '), ['value_amount']),
129 account_payment_term_line()
132 class account_account_type(osv.osv):
133 _name = "account.account.type"
134 _description = "Account Type"
136 'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
137 'code': fields.char('Code', size=32, required=True),
138 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of account types."),
139 'close_method': fields.selection([('none', 'None'), ('balance', 'Balance'), ('detail', 'Detail'), ('unreconciled', 'Unreconciled')], 'Deferral Method', required=True),
140 '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.'),
141 'report_type':fields.selection([
143 ('income','Profit & Loss (Income Accounts)'),
144 ('expense','Profit & Loss (Expense Accounts)'),
145 ('asset','Balance Sheet (Assets Accounts)'),
146 ('liability','Balance Sheet (Liability Accounts)')
147 ],'Type Heads', select=True, readonly=False, help="According value related accounts will be display on respective reports (Balance Sheet Profit & Loss Account)"),
148 'parent_id':fields.many2one('account.account.type', 'Parent Type', required=False),
149 'child_ids':fields.one2many('account.account.type', 'parent_id', 'Child Types', required=False),
150 'note': fields.text('Description'),
153 'close_method': lambda *a: 'none',
154 'sequence': lambda *a: 5,
155 'sign': lambda *a: 1,
159 def _check_recursion(self, cr, uid, ids):
160 #TODO: Need to check for recusrion
164 (_check_recursion, 'Error ! You can not create recursive types.', ['parent_id'])
167 account_account_type()
169 def _code_get(self, cr, uid, context={}):
170 acc_type_obj = self.pool.get('account.account.type')
171 ids = acc_type_obj.search(cr, uid, [])
172 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context)
173 return [(r['code'], r['name']) for r in res]
175 #----------------------------------------------------------
177 #----------------------------------------------------------
179 class account_tax(osv.osv):
180 _name = 'account.tax'
183 class account_account(osv.osv):
184 _order = "parent_left"
185 _parent_order = "code"
186 _name = "account.account"
187 _description = "Account"
189 logger = netsvc.Logger()
191 def _get_children_and_consol(self, cr, uid, ids, context={}):
194 read_data= self.read(cr, uid, ids,['id','child_id'], context)
195 for data in read_data:
196 ids2.append(data['id'])
199 for x in data['child_id']:
201 ids2 += self._get_children_and_consol(cr, uid, temp, context)
204 def search(self, cr, uid, args, offset=0, limit=None, order=None,
205 context=None, count=False):
210 while pos < len(args):
212 if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
213 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
214 if args[pos][0] == 'journal_id':
218 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
219 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
220 args[pos] = ('type','not in',('consolidation','view'))
222 ids3 = map(lambda x: x.id, jour.type_control_ids)
223 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
224 ids1 += map(lambda x: x.id, jour.account_control_ids)
225 args[pos] = ('id', 'in', ids1)
228 if context and context.has_key('consolidate_childs'): #add consolidated childs of accounts
229 ids = super(account_account, self).search(cr, uid, args, offset, limit,
230 order, context=context, count=count)
231 for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
232 ids.append(consolidate_child.id)
235 return super(account_account, self).search(cr, uid, args, offset, limit,
236 order, context=context, count=count)
238 def _get_children_and_consol(self, cr, uid, ids, context={}):
239 #this function search for all the children and all consolidated children (recursively) of the given account ids
240 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
242 for rec in self.browse(cr, uid, ids2, context=context):
243 for child in rec.child_consol_ids:
244 ids3.append(child.id)
246 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
249 def __compute(self, cr, uid, ids, field_names, arg=None, context=None,
250 query='', query_params=()):
251 """ compute the balance, debit and/or credit for the provided
255 `field_names`: the fields to compute (a list of any of
256 'balance', 'debit' and 'credit')
257 `arg`: unused fields.function stuff
258 `query`: additional query filter (as a string)
259 `query_params`: parameters for the provided query string
260 (__compute will handle their escaping) as a
264 'balance': "COALESCE(SUM(l.debit),0) " \
265 "- COALESCE(SUM(l.credit), 0) as balance",
266 'debit': "COALESCE(SUM(l.debit), 0) as debit",
267 'credit': "COALESCE(SUM(l.credit), 0) as credit"
269 #get all the necessary accounts
270 children_and_consolidated = self._get_children_and_consol(cr, uid, ids, context=context)
271 #compute for each account the balance/debit/credit from the move lines
273 if children_and_consolidated:
274 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
278 wheres.append(query.strip())
279 if aml_query.strip():
280 wheres.append(aml_query.strip())
281 filters = " AND ".join(wheres)
282 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
283 'Filters: %s'%filters)
284 # IN might not work ideally in case there are too many
285 # children_and_consolidated, in that case join on a
287 # SELECT l.account_id as id FROM account_move_line l
288 # INNER JOIN (VALUES (id1), (id2), (id3), ...) AS tmp (id)
289 # ON l.account_id = tmp.id
290 # or make _get_children_and_consol return a query and join on that
291 request = ("SELECT l.account_id as id, " +\
292 ' , '.join(map(mapping.__getitem__, field_names)) +
293 " FROM account_move_line l" \
294 " WHERE l.account_id IN %s " \
296 " GROUP BY l.account_id")
297 params = (tuple(children_and_consolidated),) + query_params
298 cr.execute(request, params)
299 self.logger.notifyChannel('addons.'+self._name, netsvc.LOG_DEBUG,
300 'Status: %s'%cr.statusmessage)
302 for res in cr.dictfetchall():
303 accounts[res['id']] = res
305 # consolidate accounts with direct children
306 children_and_consolidated.reverse()
307 brs = list(self.browse(cr, uid, children_and_consolidated, context=context))
312 for child in current.child_id:
313 if child.id not in sums:
316 brs.insert(0, brs.pop(brs.index(child)))
321 for fn in field_names:
322 sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
324 sums[current.id][fn] += sum(sums[child.id][fn] for child in current.child_id)
326 null_result = dict((fn, 0.0) for fn in field_names)
328 res[id] = sums.get(id, null_result)
331 def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
333 for rec in self.browse(cr, uid, ids, context):
334 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
337 def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
339 for record in self.browse(cr, uid, ids, context):
340 if record.child_parent_ids:
341 result[record.id] = [x.id for x in record.child_parent_ids]
343 result[record.id] = []
345 if record.child_consol_ids:
346 for acc in record.child_consol_ids:
347 if acc.id not in result[record.id]:
348 result[record.id].append(acc.id)
352 def _get_level(self, cr, uid, ids, field_name, arg, context={}):
354 accounts = self.browse(cr, uid, ids)
355 for account in accounts:
357 if account.parent_id :
358 obj = self.browse(cr, uid, account.parent_id.id)
359 level = obj.level + 1
360 res[account.id] = level
364 'name': fields.char('Name', size=128, required=True, select=True),
365 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
366 'code': fields.char('Code', size=64, required=True),
367 'type': fields.selection([
369 ('other', 'Regular'),
370 ('receivable', 'Receivable'),
371 ('payable', 'Payable'),
372 ('consolidation', 'Consolidation'),
373 ('closed', 'Closed'),
374 ], 'Internal Type', required=True, help="This type is used to differentiate types with "\
375 "special effects in Open ERP: view can not have entries, consolidation are accounts that "\
376 "can have children accounts for multi-company consolidations, payable/receivable are for "\
377 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
378 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
379 help="These types are defined according to your country. The type contains more information "\
380 "about the account and its specificities."),
381 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade', domain=[('type','=','view')]),
382 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
383 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
384 'child_id': fields.function(_get_child_ids, method=True, type='many2many', relation="account.account", string="Child Accounts"),
385 'balance': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Balance', multi='balance'),
386 'credit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Credit', multi='balance'),
387 'debit': fields.function(__compute, digits_compute=dp.get_precision('Account'), method=True, string='Debit', multi='balance'),
388 'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."),
389 'shortcut': fields.char('Shortcut', size=12),
390 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
391 'account_id', 'tax_id', 'Default Taxes'),
392 'note': fields.text('Note'),
393 'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
394 'company_id': fields.many2one('res.company', 'Company', required=True),
395 '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."),
397 'parent_left': fields.integer('Parent Left', select=1),
398 'parent_right': fields.integer('Parent Right', select=1),
399 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
401 'This will select how the current currency rate for outgoing transactions is computed. '\
402 'In most countries the legal method is "average" but only a few software systems are able to '\
403 'manage this. So if you import from another software system you may have to use the rate at date. ' \
404 'Incoming transactions always use the rate at date.', \
406 'check_history': fields.boolean('Display History',
407 help="Check this box if you want to print all entries when printing the General Ledger, "\
408 "otherwise it will only print its balance."),
409 'level': fields.function(_get_level, string='Level', method=True, store=True, type='integer'),
412 def _default_company(self, cr, uid, context={}):
413 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
415 return user.company_id.id
416 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
419 'type': lambda *a : 'view',
420 'reconcile': lambda *a: False,
421 'company_id': _default_company,
422 'active': lambda *a: True,
423 'check_history': lambda *a: True,
424 'currency_mode': lambda *a: 'current',
425 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.account', context=c),
428 def _check_recursion(self, cr, uid, ids):
429 obj_self = self.browse(cr, uid, ids[0])
430 p_id = obj_self.parent_id and obj_self.parent_id.id
431 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
434 cr.execute('SELECT DISTINCT child_id '\
435 'FROM account_account_consol_rel '\
436 'WHERE parent_id IN %s', (tuple(ids),))
437 child_ids = map(itemgetter(0), cr.fetchall())
439 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
442 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
443 if p_id and (p_id in s_ids):
450 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
453 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
455 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
463 if name and str(name).startswith('partner:'):
464 part_id = int(name.split(':')[1])
465 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
466 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
468 if name and str(name).startswith('type:'):
469 type = name.split(':')[1]
470 args += [('type', '=', type)]
475 ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
477 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
479 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
481 ids = self.search(cr, user, args, context=context, limit=limit)
482 return self.name_get(cr, user, ids, context=context)
484 def name_get(self, cr, uid, ids, context={}):
487 reads = self.read(cr, uid, ids, ['name', 'code'], context)
490 name = record['name']
492 name = record['code'] + ' '+name
493 res.append((record['id'], name))
496 def copy(self, cr, uid, id, default={}, context={}, done_list=[], local=False):
497 account = self.browse(cr, uid, id, context=context)
501 default = default.copy()
502 default['code'] = (account['code'] or '') + '(copy)'
505 if account.id in done_list:
507 done_list.append(account.id)
509 for child in account.child_id:
510 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
512 new_child_ids.append(child_ids)
513 default['child_parent_ids'] = [(6, 0, new_child_ids)]
515 default['child_parent_ids'] = False
516 return super(account_account, self).copy(cr, uid, id, default, context=context)
518 def _check_moves(self, cr, uid, ids, method, context):
519 line_obj = self.pool.get('account.move.line')
520 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
522 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
523 if method == 'write':
524 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
525 elif method == 'unlink':
526 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
527 #Checking whether the account is set as a property to any Partner or not
528 value = 'account.account,' + str(ids[0])
529 partner_prop_acc = self.pool.get('ir.property').search(cr, uid, [('value_reference','=',value)], context=context)
531 raise osv.except_osv(_('Warning !'), _('You cannot remove/deactivate an account which is set as a property to any Partner.'))
534 def _check_allow_type_change(self, cr, uid, ids, new_type, context):
535 group1 = ['payable', 'receivable', 'other']
536 group2 = ['consolidation','view']
537 line_obj = self.pool.get('account.move.line')
538 for account in self.browse(cr, uid, ids, context=context):
539 old_type = account.type
540 account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])])
541 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
542 #Check for 'Closed' type
543 if old_type == 'closed' and new_type !='closed':
544 raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type which contains account entries!"))
545 #Check for change From group1 to group2 and vice versa
546 if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1):
547 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,))
550 def write(self, cr, uid, ids, vals, context=None):
554 if 'company_id' in vals:
555 move_lines = self.pool.get('account.move.line').search(cr, uid, [('account_id', 'in', ids)])
557 raise osv.except_osv(_('Warning !'), _('You cannot modify Company of account as its related record exist in Entry Lines'))
558 if 'active' in vals and not vals['active']:
559 self._check_moves(cr, uid, ids, "write", context=context)
560 if 'type' in vals.keys():
561 self._check_allow_type_change(cr, uid, ids, vals['type'], context=context)
562 return super(account_account, self).write(cr, uid, ids, vals, context=context)
564 def unlink(self, cr, uid, ids, context={}):
565 self._check_moves(cr, uid, ids, "unlink", context)
566 return super(account_account, self).unlink(cr, uid, ids, context)
570 class account_journal_view(osv.osv):
571 _name = "account.journal.view"
572 _description = "Journal View"
574 'name': fields.char('Journal View', size=64, required=True),
575 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
578 account_journal_view()
581 class account_journal_column(osv.osv):
582 def _col_get(self, cr, user, context={}):
584 cols = self.pool.get('account.move.line')._columns
586 if col in ('period_id', 'journal_id'):
589 result.append( (col, cols[col].string) )
592 _name = "account.journal.column"
593 _description = "Journal Column"
595 'name': fields.char('Column Name', size=64, required=True),
596 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
597 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
598 'sequence': fields.integer('Sequence', help="Gives the sequence order to journal column."),
599 'required': fields.boolean('Required'),
600 'readonly': fields.boolean('Readonly'),
603 account_journal_column()
605 class account_journal(osv.osv):
606 _name = "account.journal"
607 _description = "Journal"
609 'name': fields.char('Journal Name', size=64, required=True, translate=True,help="Name of the journal"),
610 'code': fields.char('Code', size=16,required=True,help="Code of the journal"),
611 'type': fields.selection([('sale', 'Sale'),('sale_refund','Sale Refund'), ('purchase', 'Purchase'), ('purchase_refund','Purchase Refund'),('expense', 'Expense'), ('cash', 'Cash'), ('bank', 'Bank'), ('general', 'General'), ('situation', 'Situation')], 'Type', size=32, required=True,
612 help="Select 'Sale' for Sale journal to be used at the time of making invoice."\
613 " Select 'Purchase' for Purchase Journal to be used at the time of approving purchase order."\
614 " Select 'Cash' to be used at the time of making payment."\
615 " Select 'General' to be used at the time of stock input/output."\
616 " Select 'Situation' to be used at the time of making vouchers."),
617 'refund_journal': fields.boolean('Refund Journal', help='Fill this if the journal is to be used for refunds of invoices.'),
618 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
619 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
620 '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 Open ERP 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."),
621 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]", help="It acts as a default account for credit amount"),
622 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]", help="It acts as a default account for debit amount"),
623 '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."),
624 '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"),
625 '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."),
626 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals", required=False),
627 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
628 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
629 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
630 'entry_posted': fields.boolean('Skip \'Draft\' State for Created Entries', help='Check this box if you don\'t want new account moves to pass through the \'draft\' state and instead goes directly to the \'posted state\' without any manual validation.'),
631 'company_id': fields.many2one('res.company', 'Company', required=True, select=1, help="Company related to this journal"),
632 'invoice_sequence_id': fields.many2one('ir.sequence', 'Invoice Sequence', \
633 help="The sequence used for invoice numbers in this journal."),
634 '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'),
638 'user_id': lambda self,cr,uid,context: uid,
639 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
642 def write(self, cr, uid, ids, vals, context=None):
644 if 'company_id' in vals:
645 move_lines = self.pool.get('account.move.line').search(cr, uid, [('journal_id', 'in', ids)])
647 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this journal as its related record exist in Entry Lines'))
648 return super(account_journal, self).write(cr, uid, ids, vals, context=context)
650 def create_sequence(self, cr, uid, ids, context={}):
652 Create new entry sequence for every new Joural
653 @param cr: cursor to database
654 @param user: id of current user
655 @param ids: list of record ids to be process
656 @param context: context arguments, like lang, time zone
657 @return: return a result
660 seq_pool = self.pool.get('ir.sequence')
661 seq_typ_pool = self.pool.get('ir.sequence.type')
665 journal = self.browse(cr, uid, ids[0], context)
666 code = journal.code.lower()
671 type_id = seq_typ_pool.create(cr, uid, types)
677 'prefix':journal.code + "/%(year)s/",
681 seq_id = seq_pool.create(cr, uid, seq)
684 if not journal.sequence_id:
689 if not journal.invoice_sequence_id:
691 'invoice_sequence_id':seq_id
694 result = self.write(cr, uid, [journal.id], res)
698 def create(self, cr, uid, vals, context={}):
699 journal_id = super(account_journal, self).create(cr, uid, vals, context)
700 self.create_sequence(cr, uid, [journal_id], context)
702 # journal_name = self.browse(cr, uid, [journal_id])[0].code
703 # periods = self.pool.get('account.period')
704 # ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
705 # for period in periods.browse(cr, uid, ids):
706 # self.pool.get('account.journal.period').create(cr, uid, {
707 # 'name': (journal_name or '')+':'+(period.code or ''),
708 # 'journal_id': journal_id,
709 # 'period_id': period.id
713 def name_search(self, cr, user, name, args=None, operator='ilike', context={}, limit=100):
718 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit, context=context)
720 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit, context=context)
721 return self.name_get(cr, user, ids, context=context)
723 def onchange_type(self, cr, uid, ids, type, currency):
724 data_pool = self.pool.get('ir.model.data')
725 user_pool = self.pool.get('res.users')
728 'sale':'account_sp_journal_view',
729 'sale_refund':'account_sp_refund_journal_view',
730 'purchase':'account_sp_journal_view',
731 'purchase_refund':'account_sp_refund_journal_view',
732 'expense':'account_sp_journal_view',
733 'cash':'account_journal_bank_view',
734 'bank':'account_journal_bank_view',
735 'general':'account_journal_view',
736 'situation':'account_journal_view'
741 view_id = type_map.get(type, 'general')
743 user = user_pool.browse(cr, uid, uid)
744 if type in ('cash', 'bank') and currency and user.company_id.currency_id.id != currency:
745 view_id = 'account_journal_bank_view_multi'
747 data_id = data_pool.search(cr, uid, [('model','=','account.journal.view'), ('name','=',view_id)])
748 data = data_pool.browse(cr, uid, data_id[0])
751 'centralisation':type == 'situation',
752 'view_id':data.res_id,
761 class account_fiscalyear(osv.osv):
762 _name = "account.fiscalyear"
763 _description = "Fiscal Year"
765 'name': fields.char('Fiscal Year', size=64, required=True),
766 'code': fields.char('Code', size=6, required=True),
767 'company_id': fields.many2one('res.company', 'Company', required=True),
768 'date_start': fields.date('Start Date', required=True),
769 'date_stop': fields.date('End Date', required=True),
770 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
771 'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', readonly=True,
772 help='When fiscal year is created. The state is \'Draft\'. At the end of the year it is in \'Done\' state.'),
776 'state': lambda *a: 'draft',
777 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
779 _order = "date_start"
781 def _check_duration(self,cr,uid,ids):
782 obj_fy=self.browse(cr,uid,ids[0])
783 if obj_fy.date_stop < obj_fy.date_start:
788 (_check_duration, 'Error ! The duration of the Fiscal Year is invalid. ', ['date_stop'])
791 def create_period3(self,cr, uid, ids, context={}):
792 return self.create_period(cr, uid, ids, context, 3)
794 def create_period(self,cr, uid, ids, context={}, interval=1):
795 for fy in self.browse(cr, uid, ids, context):
796 ds = datetime.strptime(fy.date_start, '%Y-%m-%d')
797 while ds.strftime('%Y-%m-%d')<fy.date_stop:
798 de = ds + relativedelta(months=interval, days=-1)
800 if de.strftime('%Y-%m-%d')>fy.date_stop:
801 de = datetime.strptime(fy.date_stop, '%Y-%m-%d')
803 self.pool.get('account.period').create(cr, uid, {
804 'name': ds.strftime('%m/%Y'),
805 'code': ds.strftime('%m/%Y'),
806 'date_start': ds.strftime('%Y-%m-%d'),
807 'date_stop': de.strftime('%Y-%m-%d'),
808 'fiscalyear_id': fy.id,
810 ds = ds + relativedelta(months=interval)
813 def find(self, cr, uid, dt=None, exception=True, context={}):
815 dt = time.strftime('%Y-%m-%d')
816 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
819 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
824 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
831 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
833 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
834 return self.name_get(cr, user, ids, context=context)
838 class account_period(osv.osv):
839 _name = "account.period"
840 _description = "Account period"
842 'name': fields.char('Period Name', size=64, required=True),
843 'code': fields.char('Code', size=12),
844 'special': fields.boolean('Opening/Closing Period', size=12,
845 help="These periods can overlap."),
846 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
847 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
848 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
849 'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', readonly=True,
850 help='When monthly periods are created. The state is \'Draft\'. At the end of monthly period it is in \'Done\' state.'),
851 'company_id': fields.related('fiscalyear_id', 'company_id', type='many2one', relation='res.company', string='Company'),
854 'state': lambda *a: 'draft',
855 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
857 _order = "date_start"
859 def _check_duration(self,cr,uid,ids,context={}):
860 obj_period=self.browse(cr,uid,ids[0])
861 if obj_period.date_stop < obj_period.date_start:
865 def _check_year_limit(self,cr,uid,ids,context={}):
866 for obj_period in self.browse(cr,uid,ids):
867 if obj_period.special:
870 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
871 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
872 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
873 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
876 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
877 for period in self.browse(cr, uid, pids):
878 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
883 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
884 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
887 def next(self, cr, uid, period, step, context={}):
888 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
893 def find(self, cr, uid, dt=None, context={}):
895 dt = time.strftime('%Y-%m-%d')
896 #CHECKME: shouldn't we check the state of the period?
897 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
899 raise osv.except_osv(_('Error !'), _('No period defined for this date: %s !\nPlease create a fiscal year.')%dt)
902 def action_draft(self, cr, uid, ids, *args):
903 users_roles = self.pool.get('res.users').browse(cr, uid, uid).roles_id
904 for role in users_roles:
905 if role.name=='Period':
908 cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
909 cr.execute('update account_period set state=%s where id=%s', (mode, id))
912 def name_search(self, cr, user, name, args=None, operator='ilike', context={}, limit=80):
919 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
921 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
922 return self.name_get(cr, user, ids, context=context)
924 def write(self, cr, uid, ids, vals, context={}):
926 if 'company_id' in vals:
927 move_lines = self.pool.get('account.move.line').search(cr, uid, [('period_id', 'in', ids)])
929 raise osv.except_osv(_('Warning !'), _('You cannot modify company of this period as its related record exist in Entry Lines'))
930 return super(account_period, self).write(cr, uid, ids, vals, context=context)
934 class account_journal_period(osv.osv):
935 _name = "account.journal.period"
936 _description = "Journal Period"
938 def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
939 result = {}.fromkeys(ids, 'STOCK_NEW')
940 for r in self.read(cr, uid, ids, ['state']):
942 'draft': 'STOCK_NEW',
943 'printed': 'STOCK_PRINT_PREVIEW',
944 'done': 'STOCK_DIALOG_AUTHENTICATION',
945 }.get(r['state'], 'STOCK_NEW')
949 'name': fields.char('Journal-Period Name', size=64, required=True),
950 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
951 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
952 'icon': fields.function(_icon_get, method=True, string='Icon', type='char', size=32),
953 'active': fields.boolean('Active', required=True, help="If the active field is set to true, it will allow you to hide the journal period without removing it."),
954 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True,
955 help='When journal period is created. The state is \'Draft\'. If a report is printed it comes to \'Printed\' state. When all transactions are done, it comes in \'Done\' state.'),
956 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
957 'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company')
960 def _check(self, cr, uid, ids, context={}):
961 for obj in self.browse(cr, uid, ids, context):
962 cr.execute('select * from account_move_line where journal_id=%s and period_id=%s limit 1', (obj.journal_id.id, obj.period_id.id))
965 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
968 def write(self, cr, uid, ids, vals, context={}):
969 self._check(cr, uid, ids, context)
970 return super(account_journal_period, self).write(cr, uid, ids, vals, context)
972 def create(self, cr, uid, vals, context={}):
973 period_id=vals.get('period_id',False)
975 period = self.pool.get('account.period').browse(cr, uid,period_id)
976 vals['state']=period.state
977 return super(account_journal_period, self).create(cr, uid, vals, context)
979 def unlink(self, cr, uid, ids, context={}):
980 self._check(cr, uid, ids, context)
981 return super(account_journal_period, self).unlink(cr, uid, ids, context)
984 'state': lambda *a: 'draft',
985 'active': lambda *a: True,
986 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
990 account_journal_period()
992 class account_fiscalyear(osv.osv):
993 _inherit = "account.fiscalyear"
994 _description = "Fiscal Year"
996 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
1000 #----------------------------------------------------------
1002 #----------------------------------------------------------
1003 class account_move(osv.osv):
1004 _name = "account.move"
1005 _description = "Account Entry"
1008 def name_get(self, cursor, user, ids, context=None):
1012 data_move = self.pool.get('account.move').browse(cursor,user,ids)
1013 for move in data_move:
1014 if move.state=='draft':
1015 name = '*' + str(move.id)
1018 res.append((move.id, name))
1022 def _get_period(self, cr, uid, context):
1023 periods = self.pool.get('account.period').find(cr, uid)
1029 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
1030 if not ids: return {}
1031 cr.execute( 'SELECT move_id, SUM(debit) '\
1032 'FROM account_move_line '\
1033 'WHERE move_id IN %s '\
1034 'GROUP BY move_id', (tuple(ids),))
1035 result = dict(cr.fetchall())
1037 result.setdefault(id, 0.0)
1040 def _search_amount(self, cr, uid, obj, name, args, context):
1042 cr.execute('select move_id,sum(debit) from account_move_line group by move_id')
1043 result = dict(cr.fetchall())
1047 res = [('id', 'in', [k for k,v in result.iteritems() if v >= item[2]])]
1049 res = [('id', 'in', [k for k,v in result.iteritems() if v <= item[2]])]
1053 return [('id', '>', '0')]
1058 'name': fields.char('Number', size=64, required=True),
1059 'ref': fields.char('Reference', size=64),
1060 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
1061 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
1062 'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'State', required=True, readonly=True,
1063 help='When new account move is created the state will be \'Draft\'. When all the payments are done it will be in \'Posted\' state.'),
1064 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
1065 'to_check': fields.boolean('To Be Verified'),
1066 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner"),
1067 'amount': fields.function(_amount_compute, method=True, string='Amount', digits_compute=dp.get_precision('Account'), type='float', fnct_search=_search_amount),
1068 'date': fields.date('Date', required=True, states={'posted':[('readonly',True)]}),
1069 'type': fields.selection([
1070 ('pay_voucher','Cash Payment'),
1071 ('bank_pay_voucher','Bank Payment'),
1072 ('rec_voucher','Cash Receipt'),
1073 ('bank_rec_voucher','Bank Receipt'),
1074 ('cont_voucher','Contra'),
1075 ('journal_sale_vou','Journal Sale'),
1076 ('journal_pur_voucher','Journal Purchase'),
1077 ('journal_voucher','Journal Voucher'),
1078 ],'Entry Type', select=True , size=128, readonly=True, states={'draft':[('readonly',False)]}),
1079 'narration':fields.text('Narration', readonly=True, select=True, states={'draft':[('readonly',False)]}),
1080 'company_id': fields.related('journal_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
1083 'name': lambda *a: '/',
1084 'state': lambda *a: 'draft',
1085 'period_id': _get_period,
1086 'type' : lambda *a : 'journal_voucher',
1087 'date': lambda *a:time.strftime('%Y-%m-%d'),
1088 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
1091 def _check_centralisation(self, cursor, user, ids):
1092 for move in self.browse(cursor, user, ids):
1093 if move.journal_id.centralisation:
1094 move_ids = self.search(cursor, user, [
1095 ('period_id', '=', move.period_id.id),
1096 ('journal_id', '=', move.journal_id.id),
1098 if len(move_ids) > 1:
1102 def _check_period_journal(self, cursor, user, ids):
1103 for move in self.browse(cursor, user, ids):
1104 for line in move.line_id:
1105 if line.period_id.id != move.period_id.id:
1107 if line.journal_id.id != move.journal_id.id:
1112 (_check_centralisation,
1113 'You cannot create more than one move per period on centralized journal',
1115 (_check_period_journal,
1116 'You cannot create entries on different periods/journals in the same move',
1119 def post(self, cr, uid, ids, context=None):
1120 if self.validate(cr, uid, ids, context) and len(ids):
1121 for move in self.browse(cr, uid, ids):
1124 journal = move.journal_id
1125 if journal.sequence_id:
1126 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
1127 new_name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id, context=c)
1129 raise osv.except_osv(_('Error'), _('No sequence defined in the journal !'))
1131 self.write(cr, uid, [move.id], {'name':new_name})
1133 cr.execute('UPDATE account_move '\
1136 ('posted', tuple(ids),))
1138 raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non-balanced entry !\nMake sure you have configured Payment Term properly !\nIt should contain atleast one Payment Term Line with type "Balance" !'))
1141 def button_validate(self, cursor, user, ids, context=None):
1142 return self.post(cursor, user, ids, context=context)
1144 def button_cancel(self, cr, uid, ids, context={}):
1145 for line in self.browse(cr, uid, ids, context):
1146 if not line.journal_id.update_posted:
1147 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.'))
1149 cr.execute('UPDATE account_move '\
1151 'WHERE id IN %s', ('draft', tuple(ids),))
1154 def write(self, cr, uid, ids, vals, context={}):
1156 c['novalidate'] = True
1157 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
1158 self.validate(cr, uid, ids, context)
1162 # TODO: Check if period is closed !
1164 def create(self, cr, uid, vals, context={}):
1165 if 'line_id' in vals:
1166 if 'journal_id' in vals:
1167 for l in vals['line_id']:
1169 l[2]['journal_id'] = vals['journal_id']
1170 context['journal_id'] = vals['journal_id']
1171 if 'period_id' in vals:
1172 for l in vals['line_id']:
1174 l[2]['period_id'] = vals['period_id']
1175 context['period_id'] = vals['period_id']
1177 default_period = self._get_period(cr, uid, context)
1178 for l in vals['line_id']:
1180 l[2]['period_id'] = default_period
1181 context['period_id'] = default_period
1183 if 'line_id' in vals:
1185 c['novalidate'] = True
1186 result = super(account_move, self).create(cr, uid, vals, c)
1187 self.validate(cr, uid, [result], context)
1189 result = super(account_move, self).create(cr, uid, vals, context)
1192 def copy(self, cr, uid, id, default=None, context=None):
1195 default = default.copy()
1196 default.update({'state':'draft', 'name':'/',})
1197 return super(account_move, self).copy(cr, uid, id, default, context)
1199 def unlink(self, cr, uid, ids, context={}, check=True):
1201 for move in self.browse(cr, uid, ids, context):
1202 if move['state'] != 'draft':
1203 raise osv.except_osv(_('UserError'),
1204 _('You can not delete posted movement: "%s"!') % \
1206 line_ids = map(lambda x: x.id, move.line_id)
1207 context['journal_id'] = move.journal_id.id
1208 context['period_id'] = move.period_id.id
1209 self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
1210 self.pool.get('account.move.line').unlink(cr, uid, line_ids, context=context)
1211 toremove.append(move.id)
1212 result = super(account_move, self).unlink(cr, uid, toremove, context)
1215 def _compute_balance(self, cr, uid, id, context={}):
1216 move = self.browse(cr, uid, [id])[0]
1218 for line in move.line_id:
1219 amount+= (line.debit - line.credit)
1222 def _centralise(self, cr, uid, move, mode, context=None):
1223 assert mode in ('debit', 'credit'), 'Invalid Mode' #to prevent sql injection
1228 account_id = move.journal_id.default_debit_account_id.id
1231 raise osv.except_osv(_('UserError'),
1232 _('There is no default default debit account defined \n' \
1233 'on journal "%s"') % move.journal_id.name)
1235 account_id = move.journal_id.default_credit_account_id.id
1238 raise osv.except_osv(_('UserError'),
1239 _('There is no default default credit account defined \n' \
1240 'on journal "%s"') % move.journal_id.name)
1242 # find the first line of this move with the current mode
1243 # or create it if it doesn't exist
1244 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
1249 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
1250 line_id = self.pool.get('account.move.line').create(cr, uid, {
1251 'name': _(mode.capitalize()+' Centralisation'),
1252 'centralisation': mode,
1253 'account_id': account_id,
1255 'journal_id': move.journal_id.id,
1256 'period_id': move.period_id.id,
1257 'date': move.period_id.date_stop,
1262 # find the first line of this move with the other mode
1263 # so that we can exclude it from our calculation
1264 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
1271 cr.execute('SELECT SUM(%s) FROM account_move_line WHERE move_id=%%s AND id!=%%s' % (mode,), (move.id, line_id2))
1272 result = cr.fetchone()[0] or 0.0
1273 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1277 # Validate a balanced move. If it is a centralised journal, create a move.
1280 def validate(self, cr, uid, ids, context={}):
1281 if context and ('__last_update' in context):
1282 del context['__last_update']
1284 valid_moves = [] #Maintains a list of moves which can be responsible to create analytic entries
1286 for move in self.browse(cr, uid, ids, context):
1287 # Unlink old analytic lines on move_lines
1288 for obj_line in move.line_id:
1289 for obj in obj_line.analytic_lines:
1290 self.pool.get('account.analytic.line').unlink(cr,uid,obj.id)
1292 journal = move.journal_id
1297 for line in move.line_id:
1298 amount += line.debit - line.credit
1299 line_ids.append(line.id)
1300 if line.state=='draft':
1301 line_draft_ids.append(line.id)
1304 company_id = line.account_id.company_id.id
1305 if not company_id == line.account_id.company_id.id:
1306 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1308 if line.account_id.currency_id:
1309 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 or line.currency_id):
1310 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)))
1312 if abs(amount) < 10 ** -4:
1313 # If the move is balanced
1314 # Add to the list of valid moves
1315 # (analytic lines will be created later for valid moves)
1316 valid_moves.append(move)
1318 # Check whether the move lines are confirmed
1320 if not len(line_draft_ids):
1322 # Update the move lines (set them as valid)
1324 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1325 'journal_id': move.journal_id.id,
1326 'period_id': move.period_id.id,
1328 }, context, check=False)
1333 if journal.type in ('purchase','sale'):
1334 for line in move.line_id:
1336 key = (line.account_id.id, line.tax_code_id.id)
1338 code = account2[key][0]
1339 amount = account2[key][1] * (line.debit + line.credit)
1340 elif line.account_id.id in account:
1341 code = account[line.account_id.id][0]
1342 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1343 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1344 self.pool.get('account.move.line').write(cr, uid, [line.id], {
1345 'tax_code_id': code,
1346 'tax_amount': amount
1347 }, context, check=False)
1348 elif journal.centralisation:
1349 # If the move is not balanced, it must be centralised...
1351 # Add to the list of valid moves
1352 # (analytic lines will be created later for valid moves)
1353 valid_moves.append(move)
1356 # Update the move lines (set them as valid)
1358 self._centralise(cr, uid, move, 'debit', context=context)
1359 self._centralise(cr, uid, move, 'credit', context=context)
1360 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1362 }, context, check=False)
1364 # We can't validate it (it's unbalanced)
1365 # Setting the lines as draft
1366 self.pool.get('account.move.line').write(cr, uid, line_ids, {
1367 'journal_id': move.journal_id.id,
1368 'period_id': move.period_id.id,
1369 #'tax_code_id': False,
1370 #'tax_amount': False,
1372 }, context, check=False)
1373 # Create analytic lines for the valid moves
1374 for record in valid_moves:
1375 self.pool.get('account.move.line').create_analytic_lines(cr, uid, [line.id for line in record.line_id], context)
1377 return len(valid_moves) > 0
1381 class account_move_reconcile(osv.osv):
1382 _name = "account.move.reconcile"
1383 _description = "Account Reconciliation"
1385 'name': fields.char('Name', size=64, required=True),
1386 'type': fields.char('Type', size=16, required=True),
1387 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1388 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1389 'create_date': fields.date('Creation date', readonly=True),
1392 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1394 def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1396 for rec in self.browse(cr, uid, ids, context):
1397 for line in rec.line_partial_ids:
1398 total += (line.debit or 0.0) - (line.credit or 0.0)
1400 self.pool.get('account.move.line').write(cr, uid,
1401 map(lambda x: x.id, rec.line_partial_ids),
1402 {'reconcile_id': rec.id }
1406 def name_get(self, cr, uid, ids, context=None):
1410 for r in self.browse(cr, uid, ids, context):
1411 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1413 name = '%s (%.2f)' % (r.name, total)
1414 result.append((r.id,name))
1416 result.append((r.id,r.name))
1420 account_move_reconcile()
1422 #----------------------------------------------------------
1424 #----------------------------------------------------------
1427 child_depend: la taxe depend des taxes filles
1429 class account_tax_code(osv.osv):
1431 A code for the tax object.
1433 This code is used for some tax declarations.
1435 def _sum(self, cr, uid, ids, name, args, context,where ='', where_params=()):
1436 parent_ids = tuple(self.search(cr, uid, [('parent_id', 'child_of', ids)]))
1437 if context.get('based_on', 'invoices') == 'payments':
1438 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1439 FROM account_move_line AS line, \
1440 account_move AS move \
1441 LEFT JOIN account_invoice invoice ON \
1442 (invoice.move_id = move.id) \
1443 WHERE line.tax_code_id IN %s '+where+' \
1444 AND move.id = line.move_id \
1445 AND ((invoice.state = \'paid\') \
1446 OR (invoice.id IS NULL)) \
1447 GROUP BY line.tax_code_id',
1448 (parent_ids,)+where_params)
1450 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1451 FROM account_move_line AS line \
1452 WHERE line.tax_code_id IN %s '+where+' \
1453 GROUP BY line.tax_code_id',
1454 (parent_ids,)+where_params)
1455 res=dict(cr.fetchall())
1456 for record in self.browse(cr, uid, ids, context):
1457 def _rec_get(record):
1458 amount = res.get(record.id, 0.0)
1459 for rec in record.child_ids:
1460 amount += _rec_get(rec) * rec.sign
1462 res[record.id] = round(_rec_get(record), self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
1465 def _sum_year(self, cr, uid, ids, name, args, context):
1466 if 'fiscalyear_id' in context and context['fiscalyear_id']:
1467 fiscalyear_id = context['fiscalyear_id']
1469 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1473 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1475 where = ' and period_id IN %s'
1476 where_params = (tuple(pids),)
1477 return self._sum(cr, uid, ids, name, args, context,
1478 where=where, where_params=where_params)
1480 def _sum_period(self, cr, uid, ids, name, args, context):
1481 if 'period_id' in context and context['period_id']:
1482 period_id = context['period_id']
1484 period_id = self.pool.get('account.period').find(cr, uid)
1485 if not len(period_id):
1486 return dict.fromkeys(ids, 0.0)
1487 period_id = period_id[0]
1488 return self._sum(cr, uid, ids, name, args, context,
1489 where=' and line.period_id=%s', where_params=(period_id,))
1491 _name = 'account.tax.code'
1492 _description = 'Tax Code'
1495 'name': fields.char('Tax Case Name', size=64, required=True, translate=True),
1496 'code': fields.char('Case Code', size=64),
1497 'info': fields.text('Description'),
1498 'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1499 'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1500 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1501 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1502 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1503 'company_id': fields.many2one('res.company', 'Company', required=True),
1504 'sign': fields.float('Sign for parent', required=True),
1505 '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"),
1509 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
1514 ids = self.search(cr, user, ['|',('name',operator,name),('code',operator,name)] + args, limit=limit, context=context)
1515 return self.name_get(cr, user, ids, context)
1518 def name_get(self, cr, uid, ids, context=None):
1521 if isinstance(ids, (int, long)):
1523 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1524 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1527 def _default_company(self, cr, uid, context={}):
1528 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1530 return user.company_id.id
1531 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1533 'company_id': _default_company,
1534 'sign': lambda *args: 1.0,
1535 'notprintable': lambda *a: False,
1538 def copy(self, cr, uid, id, default=None, context=None):
1541 default = default.copy()
1542 default.update({'line_ids': []})
1543 return super(account_tax_code, self).copy(cr, uid, id, default, context)
1545 _check_recursion = check_cycle
1547 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1549 _order = 'code,name'
1552 class account_tax(osv.osv):
1556 Type: percent, fixed, none, code
1557 PERCENT: tax = price * amount
1558 FIXED: tax = price + amount
1560 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1561 return result in the context
1562 Ex: result=round(price_unit*0.21,4)
1564 _name = 'account.tax'
1565 _description = 'Tax'
1567 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1568 '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."),
1569 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
1570 'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the tax without removing it."),
1571 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'),('balance','Balance')], 'Tax Type', required=True,
1572 help="The computation method for the tax amount."),
1573 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True,
1574 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1575 '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."),
1576 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1577 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1578 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1579 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1580 '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."),
1581 'python_compute':fields.text('Python Code'),
1582 'python_compute_inv':fields.text('Python Code (reverse)'),
1583 'python_applicable':fields.text('Python Code'),
1584 'tax_group': fields.selection([('vat','VAT'),('other','Other')], 'Tax Group', help="If a default tax is given in the partner it only overrides taxes from accounts (or products) in the same group."),
1587 # Fields used for the VAT declaration
1589 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1590 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1591 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1592 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1594 # Same fields for refund invoices
1596 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1597 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1598 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1599 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1600 '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"),
1601 'company_id': fields.many2one('res.company', 'Company', required=True),
1602 'description': fields.char('Tax Code',size=32),
1603 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1604 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1607 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1608 context=None, count=False):
1609 if context and context.has_key('type'):
1610 if context['type'] in ('out_invoice','out_refund'):
1611 args.append(('type_tax_use','in',['sale','all']))
1612 elif context['type'] in ('in_invoice','in_refund'):
1613 args.append(('type_tax_use','in',['purchase','all']))
1614 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1616 def name_get(self, cr, uid, ids, context={}):
1620 for record in self.read(cr, uid, ids, ['description','name'], context):
1621 name = record['description'] and record['description'] or record['name']
1622 res.append((record['id'],name ))
1625 def _default_company(self, cr, uid, context={}):
1626 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1628 return user.company_id.id
1629 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1631 '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''',
1632 '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''',
1633 'applicable_type': lambda *a: 'true',
1634 'type': lambda *a: 'percent',
1635 'amount': lambda *a: 0,
1636 'price_include': lambda *a: 0,
1637 'active': lambda *a: 1,
1638 'type_tax_use': lambda *a: 'all',
1639 'sequence': lambda *a: 1,
1640 'tax_group': lambda *a: 'vat',
1641 'ref_tax_sign': lambda *a: 1,
1642 'ref_base_sign': lambda *a: 1,
1643 'tax_sign': lambda *a: 1,
1644 'base_sign': lambda *a: 1,
1645 'include_base_amount': lambda *a: False,
1646 'company_id': _default_company,
1650 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1653 if tax.applicable_type=='code':
1654 localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id), 'product':product, 'partner':partner}
1655 exec tax.python_applicable in localdict
1656 if localdict.get('result', False):
1662 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1663 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1666 cur_price_unit=price_unit
1668 # we compute the amount for the current tax object and append it to the result
1670 data = {'id':tax.id,
1671 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1672 'account_collected_id':tax.account_collected_id.id,
1673 'account_paid_id':tax.account_paid_id.id,
1674 'base_code_id': tax.base_code_id.id,
1675 'ref_base_code_id': tax.ref_base_code_id.id,
1676 'sequence': tax.sequence,
1677 'base_sign': tax.base_sign,
1678 'tax_sign': tax.tax_sign,
1679 'ref_base_sign': tax.ref_base_sign,
1680 'ref_tax_sign': tax.ref_tax_sign,
1681 'price_unit': cur_price_unit,
1682 'tax_code_id': tax.tax_code_id.id,
1683 'ref_tax_code_id': tax.ref_tax_code_id.id,
1686 if tax.type=='percent':
1687 amount = cur_price_unit * tax.amount
1688 data['amount'] = amount
1690 elif tax.type=='fixed':
1691 data['amount'] = tax.amount
1692 data['tax_amount']=quantity
1693 # data['amount'] = quantity
1694 elif tax.type=='code':
1695 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1696 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1697 exec tax.python_compute in localdict
1698 amount = localdict['result']
1699 data['amount'] = amount
1700 elif tax.type=='balance':
1701 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1702 data['balance'] = cur_price_unit
1704 amount2 = data['amount']
1705 if len(tax.child_ids):
1706 if tax.child_depend:
1709 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1710 res.extend(child_tax)
1711 if tax.child_depend:
1713 for name in ('base','ref_base'):
1714 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1715 r[name+'_code_id'] = latest[name+'_code_id']
1716 r[name+'_sign'] = latest[name+'_sign']
1717 r['price_unit'] = latest['price_unit']
1718 latest[name+'_code_id'] = False
1719 for name in ('tax','ref_tax'):
1720 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1721 r[name+'_code_id'] = latest[name+'_code_id']
1722 r[name+'_sign'] = latest[name+'_sign']
1723 r['amount'] = data['amount']
1724 latest[name+'_code_id'] = False
1725 if tax.include_base_amount:
1726 cur_price_unit+=amount2
1729 def compute_all(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1732 'total': 0.0, # Total without taxes
1733 'total_included: 0.0, # Total with taxes
1734 'taxes': [] # List of taxes, see compute for the format
1737 precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1738 totalin = totalex = round(price_unit * quantity, precision)
1742 if tax.price_include:
1746 tin = self.compute_inv(cr, uid, tin, price_unit, quantity, address_id=address_id, product=product, partner=partner)
1748 totalex -= r['amount']
1749 tex = self._compute(cr, uid, tex, totalex/quantity, quantity, address_id=address_id, product=product, partner=partner)
1751 totalin += r['amount']
1754 'total_included': totalin,
1758 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1759 logger = netsvc.Logger()
1760 logger.notifyChannel("warning", netsvc.LOG_WARNING,
1761 "Deprecated, use compute_all(...)['taxes'] instead of compute(...) to manage prices with tax included")
1762 return self._compute(cr, uid, taxes, price_unit, quantity, address_id, product, partner)
1764 def _compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1766 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1770 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1771 one tax for each tax id in IDS and their childs
1773 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1776 if r.get('balance',False):
1777 r['amount'] = round(r['balance'] * quantity, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')) - total
1779 r['amount'] = round(r['amount'] * quantity, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
1780 total += r['amount']
1783 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1784 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1788 cur_price_unit = price_unit
1790 tax_parent_tot = 0.0
1792 if (tax.type=='percent') and not tax.include_base_amount:
1793 tax_parent_tot += tax.amount
1796 if (tax.type=='fixed') and not tax.include_base_amount:
1797 cur_price_unit -= tax.amount
1800 if tax.type=='percent':
1801 if tax.include_base_amount:
1802 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1804 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1806 elif tax.type=='fixed':
1809 elif tax.type=='code':
1810 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1811 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1812 exec tax.python_compute_inv in localdict
1813 amount = localdict['result']
1814 elif tax.type=='balance':
1815 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1816 # data['balance'] = cur_price_unit
1819 if tax.include_base_amount:
1820 cur_price_unit -= amount
1829 'account_collected_id': tax.account_collected_id.id,
1830 'account_paid_id': tax.account_paid_id.id,
1831 'base_code_id': tax.base_code_id.id,
1832 'ref_base_code_id': tax.ref_base_code_id.id,
1833 'sequence': tax.sequence,
1834 'base_sign': tax.base_sign,
1835 'tax_sign': tax.tax_sign,
1836 'ref_base_sign': tax.ref_base_sign,
1837 'ref_tax_sign': tax.ref_tax_sign,
1838 'price_unit': cur_price_unit,
1839 'tax_code_id': tax.tax_code_id.id,
1840 'ref_tax_code_id': tax.ref_tax_code_id.id,
1842 if len(tax.child_ids):
1843 if tax.child_depend:
1847 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1848 res.extend(parent_tax)
1853 total += r['amount']
1855 r['price_unit'] -= total
1859 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1861 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1862 Price Unit is a VAT included price
1866 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1867 one tax for each tax id in IDS and their childs
1869 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1872 prec = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
1873 if r.get('balance',False):
1874 r['amount'] = round(r['balance'] * quantity, prec) - total
1876 r['amount'] = round(r['amount'] * quantity, prec)
1877 total += r['amount']
1881 # ---------------------------------------------------------
1882 # Account Entries Models
1883 # ---------------------------------------------------------
1885 class account_model(osv.osv):
1886 _name = "account.model"
1887 _description = "Account Model"
1889 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
1890 'ref': fields.char('Reference', size=64),
1891 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
1892 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
1893 'legend' :fields.text('Legend', readonly=True, size=100),
1897 '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'),
1899 def generate(self, cr, uid, ids, datas={}, context={}):
1901 for model in self.browse(cr, uid, ids, context):
1902 context.update({'date':datas['date']})
1903 period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date', False))
1905 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
1906 period_id = period_id[0]
1907 move_id = self.pool.get('account.move').create(cr, uid, {
1909 'period_id': period_id,
1910 'journal_id': model.journal_id.id,
1911 'date': context.get('date',time.strftime('%Y-%m-%d'))
1913 move_ids.append(move_id)
1914 for line in model.lines_id:
1917 'journal_id': model.journal_id.id,
1918 'period_id': period_id
1922 'quantity': line.quantity,
1923 'debit': line.debit,
1924 'credit': line.credit,
1925 'account_id': line.account_id.id,
1928 'partner_id': line.partner_id.id,
1929 'date': context.get('date',time.strftime('%Y-%m-%d')),
1930 'date_maturity': time.strftime('%Y-%m-%d')
1933 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
1934 self.pool.get('account.move.line').create(cr, uid, val, context=c)
1938 class account_model_line(osv.osv):
1939 _name = "account.model.line"
1940 _description = "Account Model Entries"
1942 'name': fields.char('Name', size=64, required=True),
1943 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
1944 'quantity': fields.float('Quantity', digits_compute=dp.get_precision('Account'), help="The optional quantity on entries"),
1945 'debit': fields.float('Debit', digits_compute=dp.get_precision('Account')),
1946 'credit': fields.float('Credit', digits_compute=dp.get_precision('Account')),
1948 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
1950 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
1952 'ref': fields.char('Reference', size=16),
1954 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
1955 'currency_id': fields.many2one('res.currency', 'Currency'),
1957 'partner_id': fields.many2one('res.partner', 'Partner'),
1958 '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."),
1959 'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
1962 'date': lambda *a: 'today'
1965 _sql_constraints = [
1966 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model (Credit Or Debit Must Be "0")!'),
1967 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model (Credit + Debit Must Be greater "0")!'),
1969 account_model_line()
1971 # ---------------------------------------------------------
1972 # Account Subscription
1973 # ---------------------------------------------------------
1976 class account_subscription(osv.osv):
1977 _name = "account.subscription"
1978 _description = "Account Subscription"
1980 'name': fields.char('Name', size=64, required=True),
1981 'ref': fields.char('Reference', size=16),
1982 'model_id': fields.many2one('account.model', 'Model', required=True),
1984 'date_start': fields.date('Start Date', required=True),
1985 'period_total': fields.integer('Number of Periods', required=True),
1986 'period_nbr': fields.integer('Period', required=True),
1987 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
1988 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
1990 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
1993 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
1994 'period_type': lambda *a: 'month',
1995 'period_total': lambda *a: 12,
1996 'period_nbr': lambda *a: 1,
1997 'state': lambda *a: 'draft',
1999 def state_draft(self, cr, uid, ids, context={}):
2000 self.write(cr, uid, ids, {'state':'draft'})
2003 def check(self, cr, uid, ids, context={}):
2005 for sub in self.browse(cr, uid, ids, context):
2007 for line in sub.lines_id:
2008 if not line.move_id.id:
2012 todone.append(sub.id)
2014 self.write(cr, uid, todone, {'state':'done'})
2017 def remove_line(self, cr, uid, ids, context={}):
2019 for sub in self.browse(cr, uid, ids, context):
2020 for line in sub.lines_id:
2021 if not line.move_id.id:
2022 toremove.append(line.id)
2024 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
2025 self.write(cr, uid, ids, {'state':'draft'})
2028 def compute(self, cr, uid, ids, context={}):
2029 for sub in self.browse(cr, uid, ids, context):
2031 for i in range(sub.period_total):
2032 self.pool.get('account.subscription.line').create(cr, uid, {
2034 'subscription_id': sub.id,
2036 if sub.period_type=='day':
2037 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(days=sub.period_nbr)).strftime('%Y-%m-%d')
2038 if sub.period_type=='month':
2039 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(months=sub.period_nbr)).strftime('%Y-%m-%d')
2040 if sub.period_type=='year':
2041 ds = (datetime.strptime(ds, '%Y-%m-%d') + relativedelta(years=sub.period_nbr)).strftime('%Y-%m-%d')
2042 self.write(cr, uid, ids, {'state':'running'})
2044 account_subscription()
2046 class account_subscription_line(osv.osv):
2047 _name = "account.subscription.line"
2048 _description = "Account Subscription Line"
2050 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
2051 'date': fields.date('Date', required=True),
2052 'move_id': fields.many2one('account.move', 'Entry'),
2056 def move_create(self, cr, uid, ids, context={}):
2058 for line in self.browse(cr, uid, ids, context):
2062 ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
2063 tocheck[line.subscription_id.id] = True
2064 self.write(cr, uid, [line.id], {'move_id':ids[0]})
2066 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
2069 account_subscription_line()
2071 # ---------------------------------------------------------------
2072 # Account Templates : Account, Tax, Tax Code and chart. + Wizard
2073 # ---------------------------------------------------------------
2075 class account_tax_template(osv.osv):
2076 _name = 'account.tax.template'
2077 account_tax_template()
2079 class account_account_template(osv.osv):
2081 _name = "account.account.template"
2082 _description ='Templates for Accounts'
2085 'name': fields.char('Name', size=128, required=True, select=True),
2086 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Forces all moves for this account to have this secondary currency."),
2087 'code': fields.char('Code', size=64),
2088 'type': fields.selection([
2089 ('receivable','Receivable'),
2090 ('payable','Payable'),
2092 ('consolidation','Consolidation'),
2094 ('closed','Closed'),
2095 ], 'Internal Type', required=True,help="This type is used to differentiate types with "\
2096 "special effects in Open ERP: view can not have entries, consolidation are accounts that "\
2097 "can have children accounts for multi-company consolidations, payable/receivable are for "\
2098 "partners accounts (for debit/credit computations), closed for depreciated accounts."),
2099 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
2100 help="These types are defined according to your country. The type contains more information "\
2101 "about the account and its specificities."),
2102 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
2103 'shortcut': fields.char('Shortcut', size=12),
2104 'note': fields.text('Note'),
2105 'parent_id': fields.many2one('account.account.template', 'Parent Account Template', ondelete='cascade'),
2106 'child_parent_ids':fields.one2many('account.account.template', 'parent_id', 'Children'),
2107 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel', 'account_id', 'tax_id', 'Default Taxes'),
2108 'nocreate': fields.boolean('Optional create', help="If checked, the new chart of accounts will not contain this by default."),
2112 'reconcile': lambda *a: False,
2113 'type' : lambda *a :'view',
2114 'nocreate': lambda *a: False,
2117 _check_recursion = check_cycle
2119 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
2123 def name_get(self, cr, uid, ids, context={}):
2126 reads = self.read(cr, uid, ids, ['name','code'], context)
2128 for record in reads:
2129 name = record['name']
2131 name = record['code']+' '+name
2132 res.append((record['id'],name ))
2135 account_account_template()
2137 class account_add_tmpl_wizard(osv.osv_memory):
2138 """Add one more account from the template.
2140 With the 'nocreate' option, some accounts may not be created. Use this to add them later."""
2141 _name = 'account.addtmpl.wizard'
2143 def _get_def_cparent(self, cr, uid, context):
2144 acc_obj=self.pool.get('account.account')
2145 tmpl_obj=self.pool.get('account.account.template')
2146 tids=tmpl_obj.read(cr, uid, [context['tmpl_ids']], ['parent_id'])
2147 if not tids or not tids[0]['parent_id']:
2149 ptids = tmpl_obj.read(cr, uid, [tids[0]['parent_id'][0]], ['code'])
2151 if not ptids or not ptids[0]['code']:
2152 raise osv.except_osv(_('Error !'), _('Cannot locate parent code for template account!'))
2153 res = acc_obj.search(cr, uid, [('code','=',ptids[0]['code'])])
2155 return res and res[0] or False
2158 'cparent_id':fields.many2one('account.account', 'Parent target', help="Creates an account with the selected template under this existing parent.", required=True),
2161 'cparent_id': _get_def_cparent,
2164 def action_create(self,cr,uid,ids,context=None):
2165 acc_obj=self.pool.get('account.account')
2166 tmpl_obj=self.pool.get('account.account.template')
2167 data= self.read(cr, uid, ids)
2168 company_id = acc_obj.read(cr, uid, [data[0]['cparent_id']], ['company_id'])[0]['company_id'][0]
2169 account_template = tmpl_obj.browse(cr, uid, context['tmpl_ids'])
2171 #for tax in account_template.tax_ids:
2172 # tax_ids.append(tax_template_ref[tax.id])
2174 'name': account_template.name,
2175 #'sign': account_template.sign,
2176 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2177 'code': account_template.code,
2178 'type': account_template.type,
2179 'user_type': account_template.user_type and account_template.user_type.id or False,
2180 'reconcile': account_template.reconcile,
2181 'shortcut': account_template.shortcut,
2182 'note': account_template.note,
2183 'parent_id': data[0]['cparent_id'],
2184 # 'tax_ids': [(6,0,tax_ids)], todo!!
2185 'company_id': company_id,
2187 new_account = acc_obj.create(cr, uid, vals)
2188 return {'type':'state', 'state': 'end' }
2190 def action_cancel(self, cr, uid, ids, context=None):
2191 return { 'type': 'state', 'state': 'end' }
2193 account_add_tmpl_wizard()
2195 class account_tax_code_template(osv.osv):
2197 _name = 'account.tax.code.template'
2198 _description = 'Tax Code Template'
2202 'name': fields.char('Tax Case Name', size=64, required=True),
2203 'code': fields.char('Case Code', size=64),
2204 'info': fields.text('Description'),
2205 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
2206 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
2207 'sign': fields.float('Sign for parent', required=True, help="Choose 1.00 to add the total to the parent account or -1.00 to subtract it"),
2208 '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"),
2212 'sign': lambda *args: 1.0,
2213 'notprintable': lambda *a: False,
2216 def name_get(self, cr, uid, ids, context=None):
2219 if isinstance(ids, (int, long)):
2221 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
2222 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
2225 _check_recursion = check_cycle
2227 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
2229 _order = 'code,name'
2230 account_tax_code_template()
2233 class account_chart_template(osv.osv):
2234 _name="account.chart.template"
2235 _description= "Templates for Account Chart"
2238 'name': fields.char('Name', size=64, required=True),
2239 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)], help=""),
2240 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
2241 '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'),
2242 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
2243 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
2244 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
2245 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
2246 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
2247 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
2248 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
2251 account_chart_template()
2253 class account_tax_template(osv.osv):
2255 _name = 'account.tax.template'
2256 _description = 'Templates for Taxes'
2259 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2260 'name': fields.char('Tax Name', size=64, required=True),
2261 '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."),
2262 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
2263 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
2264 '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."),
2265 '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."),
2266 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
2267 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
2268 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
2269 '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."),
2270 'python_compute':fields.text('Python Code'),
2271 'python_compute_inv':fields.text('Python Code (reverse)'),
2272 'python_applicable':fields.text('Python Code'),
2273 'tax_group': fields.selection([('vat','VAT'),('other','Other')], 'Tax Group', help="If a default tax if given in the partner it only override taxes from account (or product) of the same group."),
2276 # Fields used for the VAT declaration
2278 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
2279 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
2280 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2281 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2283 # Same fields for refund invoices
2285 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
2286 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
2287 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
2288 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
2289 '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."),
2290 'description': fields.char('Internal Name', size=32),
2291 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,)
2294 def name_get(self, cr, uid, ids, context={}):
2298 for record in self.read(cr, uid, ids, ['description','name'], context):
2299 name = record['description'] and record['description'] or record['name']
2300 res.append((record['id'],name ))
2303 def _default_company(self, cr, uid, context={}):
2304 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2306 return user.company_id.id
2307 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2310 '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''',
2311 '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''',
2312 'applicable_type': lambda *a: 'true',
2313 'type': lambda *a: 'percent',
2314 'amount': lambda *a: 0,
2315 'sequence': lambda *a: 1,
2316 'tax_group': lambda *a: 'vat',
2317 'ref_tax_sign': lambda *a: 1,
2318 'ref_base_sign': lambda *a: 1,
2319 'tax_sign': lambda *a: 1,
2320 'base_sign': lambda *a: 1,
2321 'include_base_amount': lambda *a: False,
2322 'type_tax_use': lambda *a: 'all',
2327 account_tax_template()
2329 # Fiscal Position Templates
2331 class account_fiscal_position_template(osv.osv):
2332 _name = 'account.fiscal.position.template'
2333 _description = 'Template for Fiscal Position'
2336 'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
2337 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2338 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2339 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2342 account_fiscal_position_template()
2344 class account_fiscal_position_tax_template(osv.osv):
2345 _name = 'account.fiscal.position.tax.template'
2346 _description = 'Fiscal Position Template Tax Mapping'
2347 _rec_name = 'position_id'
2350 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2351 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2352 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2355 account_fiscal_position_tax_template()
2357 class account_fiscal_position_account_template(osv.osv):
2358 _name = 'account.fiscal.position.account.template'
2359 _description = 'Fiscal Position Template Account Mapping'
2360 _rec_name = 'position_id'
2362 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2363 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2364 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2367 account_fiscal_position_account_template()
2369 # Multi charts of Accounts wizard
2371 class wizard_multi_charts_accounts(osv.osv_memory):
2373 Create a new account chart for a company.
2376 * an account chart template
2377 * a number of digits for formatting code of non-view accounts
2378 * a list of bank accounts owned by the company
2380 * generates all accounts from the template and assigns them to the right company
2381 * generates all taxes and tax codes, changing account assignations
2382 * generates all accounting properties and assigns them correctly
2384 _name='wizard.multi.charts.accounts'
2385 _inherit = 'res.config'
2388 'company_id':fields.many2one('res.company', 'Company', required=True),
2389 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2390 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts', required=True),
2391 'code_digits':fields.integer('# of Digits', required=True, help="No. of Digits to use for account code"),
2392 '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."),
2395 def _get_chart(self, cr, uid, context={}):
2396 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2401 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, [uid], c)[0].company_id.id,
2402 'chart_template_id': _get_chart,
2403 'code_digits': lambda *a:6,
2407 def execute(self, cr, uid, ids, context=None):
2408 obj_multi = self.browse(cr, uid, ids[0])
2409 obj_acc = self.pool.get('account.account')
2410 obj_acc_tax = self.pool.get('account.tax')
2411 obj_journal = self.pool.get('account.journal')
2412 obj_sequence = self.pool.get('ir.sequence')
2413 obj_acc_template = self.pool.get('account.account.template')
2414 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2415 obj_fiscal_position = self.pool.get('account.fiscal.position')
2416 data_pool = self.pool.get('ir.model.data')
2419 obj_acc_root = obj_multi.chart_template_id.account_root_id
2420 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2421 company_id = obj_multi.company_id.id
2424 acc_template_ref = {}
2425 tax_template_ref = {}
2426 tax_code_template_ref = {}
2429 #create all the tax code
2430 children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2431 children_tax_code_template.sort()
2432 for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2434 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2435 'code': tax_code_template.code,
2436 'info': tax_code_template.info,
2437 '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,
2438 'company_id': company_id,
2439 'sign': tax_code_template.sign,
2441 new_tax_code = self.pool.get('account.tax.code').create(cr, uid, vals)
2442 #recording the new tax code to do the mapping
2443 tax_code_template_ref[tax_code_template.id] = new_tax_code
2446 for tax in obj_multi.chart_template_id.tax_template_ids:
2450 'sequence': tax.sequence,
2451 'amount':tax.amount,
2453 'applicable_type': tax.applicable_type,
2454 'domain':tax.domain,
2455 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2456 'child_depend': tax.child_depend,
2457 'python_compute': tax.python_compute,
2458 'python_compute_inv': tax.python_compute_inv,
2459 'python_applicable': tax.python_applicable,
2460 'tax_group':tax.tax_group,
2461 '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,
2462 '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,
2463 'base_sign': tax.base_sign,
2464 'tax_sign': tax.tax_sign,
2465 '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,
2466 '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,
2467 'ref_base_sign': tax.ref_base_sign,
2468 'ref_tax_sign': tax.ref_tax_sign,
2469 'include_base_amount': tax.include_base_amount,
2470 'description':tax.description,
2471 'company_id': company_id,
2472 'type_tax_use': tax.type_tax_use
2474 new_tax = obj_acc_tax.create(cr, uid, vals_tax)
2475 #as the accounts have not been created yet, we have to wait before filling these fields
2476 todo_dict[new_tax] = {
2477 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2478 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2480 tax_template_ref[tax.id] = new_tax
2482 #deactivate the parent_store functionnality on account_account for rapidity purpose
2483 self.pool._init = True
2485 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id]),('nocreate','!=',True)])
2486 children_acc_template.sort()
2487 for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2489 for tax in account_template.tax_ids:
2490 tax_ids.append(tax_template_ref[tax.id])
2491 #create the account_account
2493 dig = obj_multi.code_digits
2494 code_main = account_template.code and len(account_template.code) or 0
2495 code_acc = account_template.code or ''
2496 if code_main>0 and code_main<=dig and account_template.type != 'view':
2497 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2499 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2500 #'sign': account_template.sign,
2501 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2503 'type': account_template.type,
2504 'user_type': account_template.user_type and account_template.user_type.id or False,
2505 'reconcile': account_template.reconcile,
2506 'shortcut': account_template.shortcut,
2507 'note': account_template.note,
2508 '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,
2509 'tax_ids': [(6,0,tax_ids)],
2510 'company_id': company_id,
2512 new_account = obj_acc.create(cr, uid, vals)
2513 acc_template_ref[account_template.id] = new_account
2514 #reactivate the parent_store functionnality on account_account
2515 self.pool._init = False
2516 self.pool.get('account.account')._parent_store_compute(cr)
2518 for key,value in todo_dict.items():
2519 if value['account_collected_id'] or value['account_paid_id']:
2520 obj_acc_tax.write(cr, uid, [key], {
2521 'account_collected_id': acc_template_ref[value['account_collected_id']],
2522 'account_paid_id': acc_template_ref[value['account_paid_id']],
2527 data_id = data_pool.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_view')])
2528 data = data_pool.browse(cr, uid, data_id[0])
2529 view_id = data.res_id
2531 seq_id = obj_sequence.search(cr, uid, [('name','=','Account Journal')])[0]
2533 if obj_multi.seq_journal:
2534 seq_id_sale = obj_sequence.search(cr, uid, [('name','=','Sale Journal')])[0]
2535 seq_id_purchase = obj_sequence.search(cr, uid, [('name','=','Purchase Journal')])[0]
2537 seq_id_sale = seq_id
2538 seq_id_purchase = seq_id
2540 vals_journal['view_id'] = view_id
2543 vals_journal['name'] = _('Sales Journal')
2544 vals_journal['type'] = 'sale'
2545 vals_journal['code'] = _('SAJ')
2546 vals_journal['sequence_id'] = seq_id_sale
2548 if obj_multi.chart_template_id.property_account_receivable:
2549 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2550 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2552 obj_journal.create(cr,uid,vals_journal)
2555 vals_journal['name'] = _('Purchase Journal')
2556 vals_journal['type'] = 'purchase'
2557 vals_journal['code'] = _('EXJ')
2558 vals_journal['sequence_id'] = seq_id_purchase
2560 if obj_multi.chart_template_id.property_account_payable:
2561 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2562 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2564 obj_journal.create(cr,uid,vals_journal)
2567 data_id = data_pool.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view')])
2568 data = data_pool.browse(cr, uid, data_id[0])
2569 view_id_cash = data.res_id
2570 #view_id_cash = self.pool.get('account.journal.view').search(cr, uid, [('name','=','Bank/Cash Journal View')])[0] #TOFIX: why put fix name
2572 data_id = data_pool.search(cr, uid, [('model','=','account.journal.view'), ('name','=','account_journal_bank_view_multi')])
2573 data = data_pool.browse(cr, uid, data_id[0])
2574 ref_acc_bank = data.res_id
2575 #ref_acc_bank = self.pool.get('account.journal.view').search(cr, uid, [('name','=','Bank/Cash Journal (Multi-Currency) View')])[0] #TOFIX: why put fix name
2576 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2579 for line in obj_multi.bank_accounts_id:
2580 #create the account_account for this bank journal
2581 tmp = self.pool.get('res.partner.bank').name_get(cr, uid, [line.acc_no.id])[0][1]
2582 dig = obj_multi.code_digits
2583 if ref_acc_bank.code:
2585 new_code = str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num)
2587 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)),'0')) + str(current_num)
2589 'name': line.acc_no.bank and line.acc_no.bank.name+' '+tmp or tmp,
2590 'currency_id': line.currency_id and line.currency_id.id or False,
2593 'user_type': account_template.user_type and account_template.user_type.id or False,
2595 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2596 'company_id': company_id,
2598 acc_cash_id = obj_acc.create(cr,uid,vals)
2600 if obj_multi.seq_journal:
2602 'name': _('Bank Journal ') + vals['name'],
2603 'code': 'account.journal',
2605 seq_id = obj_sequence.create(cr,uid,vals_seq)
2607 #create the bank journal
2608 vals_journal['name']= vals['name']
2609 vals_journal['code']= _('BNK') + str(current_num)
2610 vals_journal['sequence_id'] = seq_id
2611 vals_journal['type'] = 'cash'
2612 if line.currency_id:
2613 vals_journal['view_id'] = view_id_cur
2614 vals_journal['currency'] = line.currency_id.id
2616 vals_journal['view_id'] = view_id_cash
2617 vals_journal['default_credit_account_id'] = acc_cash_id
2618 vals_journal['default_debit_account_id'] = acc_cash_id
2619 obj_journal.create(cr,uid,vals_journal)
2623 #create the properties
2624 property_obj = self.pool.get('ir.property')
2625 fields_obj = self.pool.get('ir.model.fields')
2628 ('property_account_receivable','res.partner','account.account'),
2629 ('property_account_payable','res.partner','account.account'),
2630 ('property_account_expense_categ','product.category','account.account'),
2631 ('property_account_income_categ','product.category','account.account'),
2632 ('property_account_expense','product.template','account.account'),
2633 ('property_account_income','product.template','account.account')
2635 for record in todo_list:
2637 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2638 account = getattr(obj_multi.chart_template_id, record[0])
2639 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2642 'company_id': company_id,
2643 'fields_id': field[0],
2644 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2647 #the property exist: modify it
2648 property_obj.write(cr, uid, r, vals)
2650 #create the property
2651 property_obj.create(cr, uid, vals)
2653 fp_ids = obj_fiscal_position_template.search(cr, uid, [('chart_template_id', '=', obj_multi.chart_template_id.id)])
2656 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2659 'company_id' : company_id,
2660 'name' : position.name,
2662 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2664 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2665 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2667 for tax in position.tax_ids:
2669 'tax_src_id' : tax_template_ref[tax.tax_src_id.id],
2670 'tax_dest_id' : tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2671 'position_id' : new_fp,
2673 obj_tax_fp.create(cr, uid, vals_tax)
2675 for acc in position.account_ids:
2677 'account_src_id' : acc_template_ref[acc.account_src_id.id],
2678 'account_dest_id' : acc_template_ref[acc.account_dest_id.id],
2679 'position_id' : new_fp,
2681 obj_ac_fp.create(cr, uid, vals_acc)
2682 wizard_multi_charts_accounts()
2684 class account_bank_accounts_wizard(osv.osv_memory):
2685 _name='account.bank.accounts.wizard'
2688 'acc_name':fields.char('Account Name.', size=64, required=True),
2689 'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2690 'currency_id':fields.many2one('res.currency', 'Currency'),
2691 'account_type':fields.selection([('cash','Cash'),('check','Check'),('bank','Bank')], 'Type', size=32),
2694 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
2697 account_bank_accounts_wizard()
2699 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: