1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ##############################################################################
25 from osv import fields, osv
27 from tools.misc import currency
28 from tools.translate import _
31 from mx.DateTime import RelativeDateTime, now, DateTime, localtime
33 from tools import config
35 class account_payment_term(osv.osv):
36 _name = "account.payment.term"
37 _description = "Payment Term"
39 'name': fields.char('Payment Term', size=64, translate=True, required=True),
40 'active': fields.boolean('Active'),
41 'note': fields.text('Description', translate=True),
42 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
45 'active': lambda *a: 1,
49 def compute(self, cr, uid, id, value, date_ref=False, context={}):
51 date_ref = now().strftime('%Y-%m-%d')
52 pt = self.browse(cr, uid, id, context)
55 for line in pt.line_ids:
56 if line.value == 'fixed':
57 amt = round(line.value_amount, int(config['price_accuracy']))
58 elif line.value == 'procent':
59 amt = round(value * line.value_amount, int(config['price_accuracy']))
60 elif line.value == 'balance':
61 amt = round(amount, int(config['price_accuracy']))
63 next_date = mx.DateTime.strptime(date_ref, '%Y-%m-%d') + RelativeDateTime(days=line.days)
65 next_date += RelativeDateTime(day=line.days2)
67 next_date += RelativeDateTime(day=line.days2, months=1)
68 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
72 account_payment_term()
74 class account_payment_term_line(osv.osv):
75 _name = "account.payment.term.line"
76 _description = "Payment Term Line"
78 'name': fields.char('Line Name', size=32, required=True),
79 '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"),
80 'value': fields.selection([('procent', 'Percent'),
81 ('balance', 'Balance'),
82 ('fixed', 'Fixed Amount')], 'Value',
83 required=True, help="""Example: 14 days 2%, 30 days net
84 1. Line 1: percent 0.02 14 days
85 2. Line 2: balance 30 days"""),
87 'value_amount': fields.float('Value Amount', help="For Value percent enter % ratio between 0-1."),
88 'days': fields.integer('Number of Days', required=True, help="Number of days to add before computation of the day of month." \
89 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
90 '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)."),
91 'payment_id': fields.many2one('account.payment.term', 'Payment Term', required=True, select=True),
94 'value': lambda *a: 'balance',
95 'sequence': lambda *a: 5,
96 'days2': lambda *a: 0,
100 def _check_percent(self, cr, uid, ids, context={}):
101 obj = self.browse(cr, uid, ids[0])
102 if obj.value == 'procent' and ( obj.value_amount < 0.0 or obj.value_amount > 1.0):
107 (_check_percent, _('Percentages for Payment Term Line must be between 0 and 1, Example: 0.02 for 2% '), ['value_amount']),
110 account_payment_term_line()
113 class account_account_type(osv.osv):
114 _name = "account.account.type"
115 _description = "Account Type"
117 'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
118 'code': fields.char('Code', size=32, required=True),
119 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of account types."),
120 'partner_account': fields.boolean('Partner account'),
121 'close_method': fields.selection([('none', 'None'), ('balance', 'Balance'), ('detail', 'Detail'), ('unreconciled', 'Unreconciled')], 'Deferral Method', required=True),
122 '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.'),
125 'close_method': lambda *a: 'none',
126 'sequence': lambda *a: 5,
127 'sign': lambda *a: 1,
130 account_account_type()
132 def _code_get(self, cr, uid, context={}):
133 acc_type_obj = self.pool.get('account.account.type')
134 ids = acc_type_obj.search(cr, uid, [])
135 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context)
136 return [(r['code'], r['name']) for r in res]
138 #----------------------------------------------------------
140 #----------------------------------------------------------
142 class account_tax(osv.osv):
143 _name = 'account.tax'
146 class account_account(osv.osv):
147 _order = "parent_left"
148 _parent_order = "code"
149 _name = "account.account"
150 _description = "Account"
153 def search(self, cr, uid, args, offset=0, limit=None, order=None,
154 context=None, count=False):
159 while pos < len(args):
161 if args[pos][0] == 'code' and args[pos][1] in ('like', 'ilike') and args[pos][2]:
162 args[pos] = ('code', '=like', str(args[pos][2].replace('%', ''))+'%')
163 if args[pos][0] == 'journal_id':
167 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
168 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
169 args[pos] = ('type','not in',('consolidation','view'))
171 ids3 = map(lambda x: x.id, jour.type_control_ids)
172 ids1 = super(account_account, self).search(cr, uid, [('user_type', 'in', ids3)])
173 ids1 += map(lambda x: x.id, jour.account_control_ids)
174 args[pos] = ('id', 'in', ids1)
177 if context and context.has_key('consolidate_childs'): #add consolidated childs of accounts
178 ids = super(account_account, self).search(cr, uid, args, offset, limit,
179 order, context=context, count=count)
180 for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
181 ids.append(consolidate_child.id)
184 return super(account_account, self).search(cr, uid, args, offset, limit,
185 order, context=context, count=count)
187 def _get_children_and_consol(self, cr, uid, ids, context={}):
188 #this function search for all the children and all consolidated children (recursively) of the given account ids
189 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
191 for rec in self.browse(cr, uid, ids2, context=context):
192 for child in rec.child_consol_ids:
193 ids3.append(child.id)
195 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
198 def __compute(self, cr, uid, ids, field_names, arg, context={}, query=''):
199 #compute the balance/debit/credit accordingly to the value of field_name for the given account ids
201 'balance': "COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance ",
202 'debit': "COALESCE(SUM(l.debit), 0) as debit ",
203 'credit': "COALESCE(SUM(l.credit), 0) as credit "
205 #get all the necessary accounts
206 ids2 = self._get_children_and_consol(cr, uid, ids, context)
207 acc_set = ",".join(map(str, ids2))
208 #compute for each account the balance/debit/credit from the move lines
211 aml_query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
215 wheres.append(query.strip())
217 wheres.append(aml_query.strip())
218 query = " AND ".join(wheres)
220 cr.execute(("SELECT l.account_id as id, " +\
221 ' , '.join(map(lambda x: mapping[x], field_names)) +
223 "account_move_line l " \
225 "l.account_id IN (%s) " \
227 " GROUP BY l.account_id") % (acc_set, ))
229 for res in cr.dictfetchall():
230 accounts[res['id']] = res
233 # consolidate accounts with direct children
234 brs = list(self.browse(cr, uid, ids2, context=context))
239 for child in current.child_id:
240 if child.id not in sums:
243 brs.insert(0, brs.pop(brs.index(child)))
248 for fn in field_names:
249 sums.setdefault(current.id, {})[fn] = accounts.get(current.id, {}).get(fn, 0.0)
251 sums[current.id][fn] += sum(sums[child.id][fn] for child in current.child_id)
253 null_result = dict((fn, 0.0) for fn in field_names)
255 res[id] = sums.get(id, null_result)
258 def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
260 for rec in self.browse(cr, uid, ids, context):
261 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
264 def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
266 for record in self.browse(cr, uid, ids, context):
267 if record.child_parent_ids:
268 result[record.id] = [x.id for x in record.child_parent_ids]
270 result[record.id] = []
272 if record.child_consol_ids:
273 for acc in record.child_consol_ids:
274 if acc.id not in result[record.id]:
275 result[record.id].append(acc.id)
280 'name': fields.char('Name', size=128, required=True, select=True),
281 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Force all moves for this account to have this secondary currency."),
282 'code': fields.char('Code', size=64, required=True),
283 'type': fields.selection([
284 ('receivable', 'Receivable'),
285 ('payable', 'Payable'),
287 ('consolidation', 'Consolidation'),
289 ('closed', 'Closed'),
290 ], 'Internal Type', required=True,),
292 'user_type': fields.many2one('account.account.type', 'Account Type', required=True),
293 'parent_id': fields.many2one('account.account', 'Parent', ondelete='cascade'),
294 'child_parent_ids': fields.one2many('account.account','parent_id','Children'),
295 'child_consol_ids': fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
296 'child_id': fields.function(_get_child_ids, method=True, type='many2many', relation="account.account", string="Child Accounts"),
297 'balance': fields.function(__compute, digits=(16, int(config['price_accuracy'])), method=True, string='Balance', multi='balance'),
298 'credit': fields.function(__compute, digits=(16, int(config['price_accuracy'])), method=True, string='Credit', multi='balance'),
299 'debit': fields.function(__compute, digits=(16, int(config['price_accuracy'])), method=True, string='Debit', multi='balance'),
300 'reconcile': fields.boolean('Reconcile', help="Check this if the user is allowed to reconcile entries in this account."),
301 'shortcut': fields.char('Shortcut', size=12),
302 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
303 'account_id', 'tax_id', 'Default Taxes'),
304 'note': fields.text('Note'),
305 'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
306 'company_id': fields.many2one('res.company', 'Company', required=True),
307 'active': fields.boolean('Active', select=2),
309 'parent_left': fields.integer('Parent Left', select=1),
310 'parent_right': fields.integer('Parent Right', select=1),
311 'currency_mode': fields.selection([('current', 'At Date'), ('average', 'Average Rate')], 'Outgoing Currencies Rate',
313 'This will select how the current currency rate for outgoing transactions is computed. '\
314 'In most countries the legal method is "average" but only a few software systems are able to '\
315 'manage this. So if you import from another software system you may have to use the rate at date. ' \
316 'Incoming transactions always use the rate at date.', \
318 'check_history': fields.boolean('Display History',
319 help="Check this box if you want to print all entries when printing the General Ledger, "\
320 "otherwise it will only print its balance."),
323 def _default_company(self, cr, uid, context={}):
324 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
326 return user.company_id.id
327 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
330 'type': lambda *a : 'view',
331 'reconcile': lambda *a: False,
332 'company_id': _default_company,
333 'active': lambda *a: True,
334 'check_history': lambda *a: True,
335 'currency_mode': lambda *a: 'current'
338 def _check_recursion(self, cr, uid, ids):
339 obj_self = self.browse(cr, uid, ids[0])
340 p_id = obj_self.parent_id and obj_self.parent_id.id
341 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
344 cr.execute('select distinct child_id from account_account_consol_rel where parent_id in ('+','.join(map(str, ids))+')')
345 child_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
347 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
350 s_ids = self.search(cr, uid, [('parent_id', 'in', c_ids)])
351 if p_id and (p_id in s_ids):
358 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
361 ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !')
363 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
371 if name and str(name).startswith('partner:'):
372 part_id = int(name.split(':')[1])
373 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
374 args += [('id', 'in', (part.property_account_payable.id, part.property_account_receivable.id))]
376 if name and str(name).startswith('type:'):
377 type = name.split(':')[1]
378 args += [('type', '=', type)]
383 ids = self.search(cr, user, [('code', '=like', name+"%")]+args, limit=limit)
385 ids = self.search(cr, user, [('shortcut', '=', name)]+ args, limit=limit)
387 ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
389 ids = self.search(cr, user, args, context=context, limit=limit)
390 return self.name_get(cr, user, ids, context=context)
392 def name_get(self, cr, uid, ids, context={}):
395 reads = self.read(cr, uid, ids, ['name', 'code'], context)
398 name = record['name']
400 name = record['code'] + ' '+name
401 res.append((record['id'], name))
404 def copy(self, cr, uid, id, default={}, context={}, done_list=[], local=False):
405 account = self.browse(cr, uid, id, context=context)
409 default = default.copy()
410 default['code'] = (account['code'] or '') + '(copy)'
413 if account.id in done_list:
415 done_list.append(account.id)
417 for child in account.child_id:
418 child_ids = self.copy(cr, uid, child.id, default, context=context, done_list=done_list, local=True)
420 new_child_ids.append(child_ids)
421 default['child_parent_ids'] = [(6, 0, new_child_ids)]
423 default['child_parent_ids'] = False
424 return super(account_account, self).copy(cr, uid, id, default, context=context)
426 def _check_moves(self, cr, uid, ids, method, context):
427 line_obj = self.pool.get('account.move.line')
428 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
429 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
430 if method == 'write':
431 raise osv.except_osv(_('Error !'), _('You cannot deactivate an account that contains account moves.'))
432 elif method == 'unlink':
433 raise osv.except_osv(_('Error !'), _('You cannot remove an account which has account entries!. '))
436 def write(self, cr, uid, ids, vals, context=None):
439 if 'active' in vals and not vals['active']:
440 self._check_moves(cr, uid, ids, "write", context)
441 return super(account_account, self).write(cr, uid, ids, vals, context=context)
443 def unlink(self, cr, uid, ids, context={}):
444 self._check_moves(cr, uid, ids, "unlink", context)
445 return super(account_account, self).unlink(cr, uid, ids, context)
449 class account_journal_view(osv.osv):
450 _name = "account.journal.view"
451 _description = "Journal View"
453 'name': fields.char('Journal View', size=64, required=True),
454 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
457 account_journal_view()
460 class account_journal_column(osv.osv):
461 def _col_get(self, cr, user, context={}):
463 cols = self.pool.get('account.move.line')._columns
465 result.append( (col, cols[col].string) )
468 _name = "account.journal.column"
469 _description = "Journal Column"
471 'name': fields.char('Column Name', size=64, required=True),
472 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
473 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
474 'sequence': fields.integer('Sequence'),
475 'required': fields.boolean('Required'),
476 'readonly': fields.boolean('Readonly'),
479 account_journal_column()
481 class account_journal(osv.osv):
482 _name = "account.journal"
483 _description = "Journal"
485 'name': fields.char('Journal Name', size=64, required=True, translate=True),
486 'code': fields.char('Code', size=16),
487 'type': fields.selection([('sale', 'Sale'), ('purchase', 'Purchase'), ('cash', 'Cash'), ('general', 'General'), ('situation', 'Situation')], 'Type', size=32, required=True),
488 'refund_journal': fields.boolean('Refund Journal'),
490 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
491 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
493 'active': fields.boolean('Active'),
494 'view_id': fields.many2one('account.journal.view', 'View', required=True, help="Gives the view used when writing or browsing entries in this journal. The view tell 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."),
495 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]"),
496 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]"),
497 '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."),
498 'update_posted': fields.boolean('Allow Cancelling Entries'),
499 '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."),
500 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals", required=True),
501 'user_id': fields.many2one('res.users', 'User', help="The user responsible for this journal"),
502 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
503 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
504 '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.'),
505 'company_id': fields.related('default_credit_account_id','company_id',type='many2one', relation="res.company", string="Company"),
506 'invoice_sequence_id': fields.many2one('ir.sequence', 'Invoice Sequence', \
507 help="The sequence used for invoice numbers in this journal."),
511 'active': lambda *a: 1,
512 'user_id': lambda self,cr,uid,context: uid,
514 def create(self, cr, uid, vals, context={}):
515 journal_id = super(account_journal, self).create(cr, uid, vals, context)
516 # journal_name = self.browse(cr, uid, [journal_id])[0].code
517 # periods = self.pool.get('account.period')
518 # ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
519 # for period in periods.browse(cr, uid, ids):
520 # self.pool.get('account.journal.period').create(cr, uid, {
521 # 'name': (journal_name or '')+':'+(period.code or ''),
522 # 'journal_id': journal_id,
523 # 'period_id': period.id
527 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
534 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
536 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
537 return self.name_get(cr, user, ids, context=context)
540 class account_fiscalyear(osv.osv):
541 _name = "account.fiscalyear"
542 _description = "Fiscal Year"
544 'name': fields.char('Fiscal Year', size=64, required=True),
545 'code': fields.char('Code', size=6, required=True),
546 'company_id': fields.many2one('res.company', 'Company',
547 help="Keep empty if the fiscal year belongs to several companies."),
548 'date_start': fields.date('Start Date', required=True),
549 'date_stop': fields.date('End Date', required=True),
550 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
551 'state': fields.selection([('draft','Draft'), ('done','Done')], 'Status', readonly=True),
555 'state': lambda *a: 'draft',
557 _order = "date_start"
559 def _check_duration(self,cr,uid,ids):
560 obj_fy=self.browse(cr,uid,ids[0])
561 if obj_fy.date_stop < obj_fy.date_start:
566 (_check_duration, 'Error ! The duration of the Fiscal Year is invalid. ', ['date_stop'])
569 def create_period3(self,cr, uid, ids, context={}):
570 return self.create_period(cr, uid, ids, context, 3)
572 def create_period(self,cr, uid, ids, context={}, interval=1):
573 for fy in self.browse(cr, uid, ids, context):
574 ds = mx.DateTime.strptime(fy.date_start, '%Y-%m-%d')
575 while ds.strftime('%Y-%m-%d')<fy.date_stop:
576 de = ds + RelativeDateTime(months=interval, days=-1)
578 if de.strftime('%Y-%m-%d')>fy.date_stop:
579 de=mx.DateTime.strptime(fy.date_stop, '%Y-%m-%d')
581 self.pool.get('account.period').create(cr, uid, {
582 'name': ds.strftime('%m/%Y'),
583 'code': ds.strftime('%m/%Y'),
584 'date_start': ds.strftime('%Y-%m-%d'),
585 'date_stop': de.strftime('%Y-%m-%d'),
586 'fiscalyear_id': fy.id,
588 ds = ds + RelativeDateTime(months=interval)
591 def find(self, cr, uid, dt=None, exception=True, context={}):
593 dt = time.strftime('%Y-%m-%d')
594 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
597 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
603 class account_period(osv.osv):
604 _name = "account.period"
605 _description = "Account period"
607 'name': fields.char('Period Name', size=64, required=True),
608 'code': fields.char('Code', size=12),
609 'special': fields.boolean('Opening/Closing Period', size=12,
610 help="These periods can overlap."),
611 'date_start': fields.date('Start of Period', required=True, states={'done':[('readonly',True)]}),
612 'date_stop': fields.date('End of Period', required=True, states={'done':[('readonly',True)]}),
613 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
614 'state': fields.selection([('draft','Draft'), ('done','Done')], 'Status', readonly=True)
617 'state': lambda *a: 'draft',
619 _order = "date_start"
621 def _check_duration(self,cr,uid,ids,context={}):
622 obj_period=self.browse(cr,uid,ids[0])
623 if obj_period.date_stop < obj_period.date_start:
627 def _check_year_limit(self,cr,uid,ids,context={}):
628 for obj_period in self.browse(cr,uid,ids):
629 if obj_period.special:
632 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or \
633 obj_period.fiscalyear_id.date_stop < obj_period.date_start or \
634 obj_period.fiscalyear_id.date_start > obj_period.date_start or \
635 obj_period.fiscalyear_id.date_start > obj_period.date_stop:
638 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
639 for period in self.browse(cr, uid, pids):
640 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
645 (_check_duration, 'Error ! The duration of the Period(s) is/are invalid. ', ['date_stop']),
646 (_check_year_limit, 'Invalid period ! Some periods overlap or the date period is not in the scope of the fiscal year. ', ['date_stop'])
649 def next(self, cr, uid, period, step, context={}):
650 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
655 def find(self, cr, uid, dt=None, context={}):
657 dt = time.strftime('%Y-%m-%d')
658 #CHECKME: shouldn't we check the state of the period?
659 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
661 raise osv.except_osv(_('Error !'), _('No period defined for this date !\nPlease create a fiscal year.'))
664 def action_draft(self, cr, uid, ids, *args):
665 users_roles = self.pool.get('res.users').browse(cr, uid, uid).roles_id
666 for role in users_roles:
667 if role.name=='Period':
670 cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
671 cr.execute('update account_period set state=%s where id=%s', (mode, id))
676 class account_journal_period(osv.osv):
677 _name = "account.journal.period"
678 _description = "Journal - Period"
680 def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
681 result = {}.fromkeys(ids, 'STOCK_NEW')
682 for r in self.read(cr, uid, ids, ['state']):
684 'draft': 'STOCK_NEW',
685 'printed': 'STOCK_PRINT_PREVIEW',
686 'done': 'STOCK_DIALOG_AUTHENTICATION',
687 }.get(r['state'], 'STOCK_NEW')
691 'name': fields.char('Journal-Period Name', size=64, required=True),
692 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
693 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
694 'icon': fields.function(_icon_get, method=True, string='Icon', type='char', size=32),
695 'active': fields.boolean('Active', required=True),
696 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'Status', required=True, readonly=True),
697 'fiscalyear_id': fields.related('period_id', 'fiscalyear_id', string='Fiscal Year', type='many2one', relation='account.fiscalyear'),
700 def _check(self, cr, uid, ids, context={}):
701 for obj in self.browse(cr, uid, ids, context):
702 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))
705 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
708 def write(self, cr, uid, ids, vals, context={}):
709 self._check(cr, uid, ids, context)
710 return super(account_journal_period, self).write(cr, uid, ids, vals, context)
712 def create(self, cr, uid, vals, context={}):
713 period_id=vals.get('period_id',False)
715 period = self.pool.get('account.period').browse(cr, uid,period_id)
716 vals['state']=period.state
717 return super(account_journal_period, self).create(cr, uid, vals, context)
719 def unlink(self, cr, uid, ids, context={}):
720 self._check(cr, uid, ids, context)
721 return super(account_journal_period, self).unlink(cr, uid, ids, context)
724 'state': lambda *a: 'draft',
725 'active': lambda *a: True,
729 account_journal_period()
731 class account_fiscalyear(osv.osv):
732 _inherit = "account.fiscalyear"
733 _description = "Fiscal Year"
735 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
739 #----------------------------------------------------------
741 #----------------------------------------------------------
742 class account_move(osv.osv):
743 _name = "account.move"
744 _description = "Account Entry"
747 def name_get(self, cursor, user, ids, context=None):
751 data_move = self.pool.get('account.move').browse(cursor,user,ids)
752 for move in data_move:
753 if move.state=='draft':
754 name = '*' + str(move.id)
757 res.append((move.id, name))
761 def _get_period(self, cr, uid, context):
762 periods = self.pool.get('account.period').find(cr, uid)
768 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
769 if not ids: return {}
770 cr.execute('select move_id,sum(debit) from account_move_line where move_id in ('+','.join(map(str,ids))+') group by move_id')
771 result = dict(cr.fetchall())
773 result.setdefault(id, 0.0)
776 def _search_amount(self, cr, uid, obj, name, args, context):
778 cr.execute('select move_id,sum(debit) from account_move_line group by move_id')
779 result = dict(cr.fetchall())
783 res = [('id', 'in', [k for k,v in result.iteritems() if v >= item[2]])]
785 res = [('id', 'in', [k for k,v in result.iteritems() if v <= item[2]])]
789 return [('id', '>', '0')]
794 'name': fields.char('Number', size=64, required=True),
795 'ref': fields.char('Ref', size=64),
796 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
797 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
798 'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'Status', required=True, readonly=True),
799 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
800 'to_check': fields.boolean('To Be Verified'),
801 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner"),
802 'amount': fields.function(_amount_compute, method=True, string='Amount', digits=(16,int(config['price_accuracy'])), type='float', fnct_search=_search_amount),
803 'date': fields.date('Date', required=True),
804 'type': fields.selection([
805 ('pay_voucher','Cash Payment'),
806 ('bank_pay_voucher','Bank Payment'),
807 ('rec_voucher','Cash Receipt'),
808 ('bank_rec_voucher','Bank Receipt'),
809 ('cont_voucher','Contra'),
810 ('journal_sale_vou','Journal Sale'),
811 ('journal_pur_voucher','Journal Purchase'),
812 ('journal_voucher','Journal Voucher'),
813 ],'Type', readonly=True, select=True, states={'draft':[('readonly',False)]}),
816 'name': lambda *a: '/',
817 'state': lambda *a: 'draft',
818 'period_id': _get_period,
819 'type' : lambda *a : 'journal_voucher',
820 'date': lambda *a:time.strftime('%Y-%m-%d'),
823 def _check_centralisation(self, cursor, user, ids):
824 for move in self.browse(cursor, user, ids):
825 if move.journal_id.centralisation:
826 move_ids = self.search(cursor, user, [
827 ('period_id', '=', move.period_id.id),
828 ('journal_id', '=', move.journal_id.id),
830 if len(move_ids) > 1:
834 def _check_period_journal(self, cursor, user, ids):
835 for move in self.browse(cursor, user, ids):
836 for line in move.line_id:
837 if line.period_id.id != move.period_id.id:
839 if line.journal_id.id != move.journal_id.id:
844 (_check_centralisation,
845 'You cannot create more than one move per period on centralized journal',
847 (_check_period_journal,
848 'You cannot create entries on different periods/journals in the same move',
851 def post(self, cr, uid, ids, context=None):
852 if self.validate(cr, uid, ids, context) and len(ids):
853 for move in self.browse(cr, uid, ids):
856 journal = move.journal_id
857 if journal.sequence_id:
858 c = {'fiscalyear_id': move.period_id.fiscalyear_id.id}
859 new_name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id, context=c)
861 raise osv.except_osv(_('Error'), _('No sequence defined in the journal !'))
863 self.write(cr, uid, [move.id], {'name':new_name})
865 cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('posted',))
867 raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non-balanced entry !'))
870 def button_validate(self, cursor, user, ids, context=None):
871 return self.post(cursor, user, ids, context=context)
873 def button_cancel(self, cr, uid, ids, context={}):
874 for line in self.browse(cr, uid, ids, context):
875 if not line.journal_id.update_posted:
876 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.'))
878 cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('draft',))
881 def write(self, cr, uid, ids, vals, context={}):
883 c['novalidate'] = True
884 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
885 self.validate(cr, uid, ids, context)
889 # TODO: Check if period is closed !
891 def create(self, cr, uid, vals, context={}):
892 if 'line_id' in vals:
893 if 'journal_id' in vals:
894 for l in vals['line_id']:
896 l[2]['journal_id'] = vals['journal_id']
897 context['journal_id'] = vals['journal_id']
898 if 'period_id' in vals:
899 for l in vals['line_id']:
901 l[2]['period_id'] = vals['period_id']
902 context['period_id'] = vals['period_id']
904 default_period = self._get_period(cr, uid, context)
905 for l in vals['line_id']:
907 l[2]['period_id'] = default_period
908 context['period_id'] = default_period
910 accnt_journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'])
911 if 'line_id' in vals:
913 c['novalidate'] = True
914 result = super(account_move, self).create(cr, uid, vals, c)
915 self.validate(cr, uid, [result], context)
917 result = super(account_move, self).create(cr, uid, vals, context)
920 def copy(self, cr, uid, id, default=None, context=None):
923 default = default.copy()
924 default.update({'state':'draft', 'name':'/',})
925 return super(account_move, self).copy(cr, uid, id, default, context)
927 def unlink(self, cr, uid, ids, context={}, check=True):
929 for move in self.browse(cr, uid, ids, context):
930 if move['state'] != 'draft':
931 raise osv.except_osv(_('UserError'),
932 _('You can not delete posted movement: "%s"!') % \
934 line_ids = map(lambda x: x.id, move.line_id)
935 context['journal_id'] = move.journal_id.id
936 context['period_id'] = move.period_id.id
937 self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
938 self.pool.get('account.move.line').unlink(cr, uid, line_ids, context=context)
939 toremove.append(move.id)
940 result = super(account_move, self).unlink(cr, uid, toremove, context)
943 def _compute_balance(self, cr, uid, id, context={}):
944 move = self.browse(cr, uid, [id])[0]
946 for line in move.line_id:
947 amount+= (line.debit - line.credit)
950 def _centralise(self, cr, uid, move, mode, context=None):
955 account_id = move.journal_id.default_debit_account_id.id
958 raise osv.except_osv(_('UserError'),
959 _('There is no default default debit account defined \n' \
960 'on journal "%s"') % move.journal_id.name)
962 account_id = move.journal_id.default_credit_account_id.id
965 raise osv.except_osv(_('UserError'),
966 _('There is no default default credit account defined \n' \
967 'on journal "%s"') % move.journal_id.name)
969 # find the first line of this move with the current mode
970 # or create it if it doesn't exist
971 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
976 context.update({'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
977 line_id = self.pool.get('account.move.line').create(cr, uid, {
978 'name': _(mode.capitalize()+' Centralisation'),
979 'centralisation': mode,
980 'account_id': account_id,
982 'journal_id': move.journal_id.id,
983 'period_id': move.period_id.id,
984 'date': move.period_id.date_stop,
989 # find the first line of this move with the other mode
990 # so that we can exclude it from our calculation
991 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
998 cr.execute('select sum('+mode+') from account_move_line where move_id=%s and id<>%s', (move.id, line_id2))
999 result = cr.fetchone()[0] or 0.0
1000 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
1004 # Validate a balanced move. If it is a centralised journal, create a move.
1006 def validate(self, cr, uid, ids, context={}):
1007 if context and ('__last_update' in context):
1008 del context['__last_update']
1010 for move in self.browse(cr, uid, ids, context):
1011 #unlink analytic lines on move_lines
1012 for obj_line in move.line_id:
1013 for obj in obj_line.analytic_lines:
1014 self.pool.get('account.analytic.line').unlink(cr,uid,obj.id)
1016 journal = move.journal_id
1021 for line in move.line_id:
1022 amount += line.debit - line.credit
1023 line_ids.append(line.id)
1024 if line.state=='draft':
1025 line_draft_ids.append(line.id)
1028 company_id = line.account_id.company_id.id
1029 if not company_id == line.account_id.company_id.id:
1030 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
1032 if line.account_id.currency_id:
1033 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):
1034 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)))
1036 if abs(amount) < 0.0001:
1037 if not len(line_draft_ids):
1039 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1040 'journal_id': move.journal_id.id,
1041 'period_id': move.period_id.id,
1043 }, context, check=False)
1047 if journal.type not in ('purchase','sale'):
1050 for line in move.line_id:
1052 key = (line.account_id.id, line.tax_code_id.id)
1054 code = account2[key][0]
1055 amount = account2[key][1] * (line.debit + line.credit)
1056 elif line.account_id.id in account:
1057 code = account[line.account_id.id][0]
1058 amount = account[line.account_id.id][1] * (line.debit + line.credit)
1059 if (code or amount) and not (line.tax_code_id or line.tax_amount):
1060 self.pool.get('account.move.line').write(cr, uid, [line.id], {
1061 'tax_code_id': code,
1062 'tax_amount': amount
1063 }, context, check=False)
1068 if journal.centralisation:
1069 self._centralise(cr, uid, move, 'debit', context=context)
1070 self._centralise(cr, uid, move, 'credit', context=context)
1071 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1073 }, context, check=False)
1076 self.pool.get('account.move.line').write(cr, uid, line_ids, {
1077 'journal_id': move.journal_id.id,
1078 'period_id': move.period_id.id,
1079 #'tax_code_id': False,
1080 #'tax_amount': False,
1082 }, context, check=False)
1086 for tmp in move.line_id:
1087 list_ids.append(tmp.id)
1088 self.pool.get('account.move.line').create_analytic_lines(cr, uid, list_ids, context)
1092 class account_move_reconcile(osv.osv):
1093 _name = "account.move.reconcile"
1094 _description = "Account Reconciliation"
1096 'name': fields.char('Name', size=64, required=True),
1097 'type': fields.char('Type', size=16, required=True),
1098 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'),
1099 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1100 'create_date': fields.date('Creation date', readonly=True),
1103 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1105 def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1107 for rec in self.browse(cr, uid, ids, context):
1108 for line in rec.line_partial_ids:
1109 total += (line.debit or 0.0) - (line.credit or 0.0)
1111 self.pool.get('account.move.line').write(cr, uid,
1112 map(lambda x: x.id, rec.line_partial_ids),
1113 {'reconcile_id': rec.id }
1117 def name_get(self, cr, uid, ids, context=None):
1121 for r in self.browse(cr, uid, ids, context):
1122 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1124 name = '%s (%.2f)' % (r.name, total)
1125 result.append((r.id,name))
1127 result.append((r.id,r.name))
1131 account_move_reconcile()
1133 #----------------------------------------------------------
1135 #----------------------------------------------------------
1138 child_depend: la taxe depend des taxes filles
1140 class account_tax_code(osv.osv):
1142 A code for the tax object.
1144 This code is used for some tax declarations.
1146 def _sum(self, cr, uid, ids, name, args, context, where =''):
1147 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
1148 acc_set = ",".join(map(str, ids2))
1149 if context.get('based_on', 'invoices') == 'payments':
1150 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1151 FROM account_move_line AS line, \
1152 account_move AS move \
1153 LEFT JOIN account_invoice invoice ON \
1154 (invoice.move_id = move.id) \
1155 WHERE line.tax_code_id in ('+acc_set+') '+where+' \
1156 AND move.id = line.move_id \
1157 AND ((invoice.state = \'paid\') \
1158 OR (invoice.id IS NULL)) \
1159 GROUP BY line.tax_code_id')
1161 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1162 FROM account_move_line AS line \
1163 WHERE line.tax_code_id in ('+acc_set+') '+where+' \
1164 GROUP BY line.tax_code_id')
1165 res=dict(cr.fetchall())
1166 for record in self.browse(cr, uid, ids, context):
1167 def _rec_get(record):
1168 amount = res.get(record.id, 0.0)
1169 for rec in record.child_ids:
1170 amount += _rec_get(rec) * rec.sign
1172 res[record.id] = round(_rec_get(record), int(config['price_accuracy']))
1175 def _sum_year(self, cr, uid, ids, name, args, context):
1176 if 'fiscalyear_id' in context and context['fiscalyear_id']:
1177 fiscalyear_id = context['fiscalyear_id']
1179 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1182 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1184 where = ' and period_id in (' + (','.join(pids))+')'
1185 return self._sum(cr, uid, ids, name, args, context,
1188 def _sum_period(self, cr, uid, ids, name, args, context):
1189 if 'period_id' in context and context['period_id']:
1190 period_id = context['period_id']
1192 period_id = self.pool.get('account.period').find(cr, uid)
1193 if not len(period_id):
1194 return dict.fromkeys(ids, 0.0)
1195 period_id = period_id[0]
1196 return self._sum(cr, uid, ids, name, args, context,
1197 where=' and line.period_id='+str(period_id))
1199 _name = 'account.tax.code'
1200 _description = 'Tax Code'
1203 'name': fields.char('Tax Case Name', size=64, required=True),
1204 'code': fields.char('Case Code', size=64),
1205 'info': fields.text('Description'),
1206 'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1207 'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1208 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1209 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Child Codes'),
1210 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1211 'company_id': fields.many2one('res.company', 'Company', required=True),
1212 'sign': fields.float('Sign for parent', required=True),
1213 '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"),
1217 def name_get(self, cr, uid, ids, context=None):
1220 if isinstance(ids, (int, long)):
1222 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1223 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1226 def _default_company(self, cr, uid, context={}):
1227 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1229 return user.company_id.id
1230 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1232 'company_id': _default_company,
1233 'sign': lambda *args: 1.0,
1234 'notprintable': lambda *a: False,
1236 def _check_recursion(self, cr, uid, ids):
1239 cr.execute('select distinct parent_id from account_tax_code where id in ('+','.join(map(str,ids))+')')
1240 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1247 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1249 _order = 'code,name'
1252 class account_tax(osv.osv):
1256 Type: percent, fixed, none, code
1257 PERCENT: tax = price * amount
1258 FIXED: tax = price + amount
1260 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1261 return result in the context
1262 Ex: result=round(price_unit*0.21,4)
1264 _name = 'account.tax'
1265 _description = 'Tax'
1267 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be displayed on reports"),
1268 '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."),
1269 'amount': fields.float('Amount', required=True, digits=(14,4), help="For Tax Type percent enter % ratio between 0-1."),
1270 'active': fields.boolean('Active'),
1271 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'),('balance','Balance')], 'Tax Type', required=True,
1272 help="The computation method for the tax amount."),
1273 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True,
1274 help="If not applicable (computed through a Python code), the tax won't appear on the invoice."),
1275 '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."),
1276 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1277 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1278 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1279 'child_ids':fields.one2many('account.tax', 'parent_id', 'Child Tax Accounts'),
1280 '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."),
1281 'python_compute':fields.text('Python Code'),
1282 'python_compute_inv':fields.text('Python Code (reverse)'),
1283 'python_applicable':fields.text('Python Code'),
1284 '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."),
1287 # Fields used for the VAT declaration
1289 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1290 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1291 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1292 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1294 # Same fields for refund invoices
1296 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1297 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1298 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1299 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1300 'include_base_amount': fields.boolean('Include in base amount', help="Indicate if the amount of tax must be included in the base amount for the computation of the next taxes"),
1301 'company_id': fields.many2one('res.company', 'Company', required=True),
1302 'description': fields.char('Tax Code',size=32),
1303 'price_include': fields.boolean('Tax Included in Price', help="Check this if the price you use on the product and invoices includes this tax."),
1304 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1307 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1308 context=None, count=False):
1309 if context and context.has_key('type'):
1310 if context['type'] in ('out_invoice','out_refund'):
1311 args.append(('type_tax_use','in',['sale','all']))
1312 elif context['type'] in ('in_invoice','in_refund'):
1313 args.append(('type_tax_use','in',['purchase','all']))
1314 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1316 def name_get(self, cr, uid, ids, context={}):
1320 for record in self.read(cr, uid, ids, ['description','name'], context):
1321 name = record['description'] and record['description'] or record['name']
1322 res.append((record['id'],name ))
1325 def _default_company(self, cr, uid, context={}):
1326 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1328 return user.company_id.id
1329 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1331 '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''',
1332 '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''',
1333 'applicable_type': lambda *a: 'true',
1334 'type': lambda *a: 'percent',
1335 'amount': lambda *a: 0,
1336 'price_include': lambda *a: 0,
1337 'active': lambda *a: 1,
1338 'type_tax_use': lambda *a: 'all',
1339 'sequence': lambda *a: 1,
1340 'tax_group': lambda *a: 'vat',
1341 'ref_tax_sign': lambda *a: 1,
1342 'ref_base_sign': lambda *a: 1,
1343 'tax_sign': lambda *a: 1,
1344 'base_sign': lambda *a: 1,
1345 'include_base_amount': lambda *a: False,
1346 'company_id': _default_company,
1350 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1353 if tax.applicable_type=='code':
1354 localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id), 'product':product, 'partner':partner}
1355 exec tax.python_applicable in localdict
1356 if localdict.get('result', False):
1362 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None, quantity=0):
1363 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1366 cur_price_unit=price_unit
1368 # we compute the amount for the current tax object and append it to the result
1370 data = {'id':tax.id,
1371 'name':tax.description and tax.description + " - " + tax.name or tax.name,
1372 'account_collected_id':tax.account_collected_id.id,
1373 'account_paid_id':tax.account_paid_id.id,
1374 'base_code_id': tax.base_code_id.id,
1375 'ref_base_code_id': tax.ref_base_code_id.id,
1376 'sequence': tax.sequence,
1377 'base_sign': tax.base_sign,
1378 'tax_sign': tax.tax_sign,
1379 'ref_base_sign': tax.ref_base_sign,
1380 'ref_tax_sign': tax.ref_tax_sign,
1381 'price_unit': cur_price_unit,
1382 'tax_code_id': tax.tax_code_id.id,
1383 'ref_tax_code_id': tax.ref_tax_code_id.id,
1386 if tax.type=='percent':
1387 amount = cur_price_unit * tax.amount
1388 data['amount'] = amount
1390 elif tax.type=='fixed':
1391 data['amount'] = tax.amount
1392 data['tax_amount']=quantity
1393 # data['amount'] = quantity
1394 elif tax.type=='code':
1395 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1396 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1397 exec tax.python_compute in localdict
1398 amount = localdict['result']
1399 data['amount'] = amount
1400 elif tax.type=='balance':
1401 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1402 data['balance'] = cur_price_unit
1404 amount2 = data['amount']
1405 if len(tax.child_ids):
1406 if tax.child_depend:
1409 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner, quantity)
1410 res.extend(child_tax)
1411 if tax.child_depend:
1413 for name in ('base','ref_base'):
1414 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1415 r[name+'_code_id'] = latest[name+'_code_id']
1416 r[name+'_sign'] = latest[name+'_sign']
1417 r['price_unit'] = latest['price_unit']
1418 latest[name+'_code_id'] = False
1419 for name in ('tax','ref_tax'):
1420 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1421 r[name+'_code_id'] = latest[name+'_code_id']
1422 r[name+'_sign'] = latest[name+'_sign']
1423 r['amount'] = data['amount']
1424 latest[name+'_code_id'] = False
1425 if tax.include_base_amount:
1426 cur_price_unit+=amount2
1429 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1432 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1436 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1437 one tax for each tax id in IDS and their childs
1439 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner, quantity)
1442 if r.get('balance',False):
1443 r['amount'] = round(r['balance'] * quantity, int(config['price_accuracy'])) - total
1445 r['amount'] = round(r['amount'] * quantity, int(config['price_accuracy']))
1446 total += r['amount']
1450 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1451 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1455 cur_price_unit = price_unit
1457 tax_parent_tot = 0.0
1459 if (tax.type=='percent') and not tax.include_base_amount:
1460 tax_parent_tot += tax.amount
1463 if (tax.type=='fixed') and not tax.include_base_amount:
1464 cur_price_unit -= tax.amount
1467 if tax.type=='percent':
1468 if tax.include_base_amount:
1469 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1471 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1473 elif tax.type=='fixed':
1476 elif tax.type=='code':
1477 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1478 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1479 exec tax.python_compute_inv in localdict
1480 amount = localdict['result']
1481 elif tax.type=='balance':
1482 amount = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1483 # data['balance'] = cur_price_unit
1486 if tax.include_base_amount:
1487 cur_price_unit -= amount
1496 'account_collected_id': tax.account_collected_id.id,
1497 'account_paid_id': tax.account_paid_id.id,
1498 'base_code_id': tax.base_code_id.id,
1499 'ref_base_code_id': tax.ref_base_code_id.id,
1500 'sequence': tax.sequence,
1501 'base_sign': tax.base_sign,
1502 'tax_sign': tax.tax_sign,
1503 'ref_base_sign': tax.ref_base_sign,
1504 'ref_tax_sign': tax.ref_tax_sign,
1505 'price_unit': cur_price_unit,
1506 'tax_code_id': tax.tax_code_id.id,
1507 'ref_tax_code_id': tax.ref_tax_code_id.id,
1509 if len(tax.child_ids):
1510 if tax.child_depend:
1514 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1515 res.extend(parent_tax)
1520 total += r['amount']
1522 r['price_unit'] -= total
1526 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1528 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1529 Price Unit is a VAT included price
1533 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1534 one tax for each tax id in IDS and their childs
1536 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1539 if r.get('balance',False):
1540 r['amount'] = round(r['balance'] * quantity, int(config['price_accuracy'])) - total
1542 r['amount'] = round(r['amount'] * quantity, int(config['price_accuracy']))
1543 total += r['amount']
1547 # ---------------------------------------------------------
1548 # Account Entries Models
1549 # ---------------------------------------------------------
1551 class account_model(osv.osv):
1552 _name = "account.model"
1553 _description = "Account Model"
1555 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
1556 'ref': fields.char('Ref', size=64),
1557 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
1558 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
1559 'legend' :fields.text('Legend',readonly=True,size=100),
1563 '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'),
1565 def generate(self, cr, uid, ids, datas={}, context={}):
1567 for model in self.browse(cr, uid, ids, context):
1568 context.update({'date':datas['date']})
1569 period_id = self.pool.get('account.period').find(cr, uid, dt=context.get('date',False))
1571 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
1572 period_id = period_id[0]
1573 move_id = self.pool.get('account.move').create(cr, uid, {
1575 'period_id': period_id,
1576 'journal_id': model.journal_id.id,
1577 'date': context.get('date',time.strftime('%Y-%m-%d'))
1579 move_ids.append(move_id)
1580 for line in model.lines_id:
1583 'journal_id': model.journal_id.id,
1584 'period_id': period_id
1588 'quantity': line.quantity,
1589 'debit': line.debit,
1590 'credit': line.credit,
1591 'account_id': line.account_id.id,
1594 'partner_id': line.partner_id.id,
1595 'date': context.get('date',time.strftime('%Y-%m-%d')),
1596 'date_maturity': time.strftime('%Y-%m-%d')
1599 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
1600 self.pool.get('account.move.line').create(cr, uid, val, context=c)
1604 class account_model_line(osv.osv):
1605 _name = "account.model.line"
1606 _description = "Account Model Entries"
1608 'name': fields.char('Name', size=64, required=True),
1609 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from lower sequences to higher ones"),
1610 'quantity': fields.float('Quantity', digits=(16, int(config['price_accuracy'])), help="The optional quantity on entries"),
1611 'debit': fields.float('Debit', digits=(16, int(config['price_accuracy']))),
1612 'credit': fields.float('Credit', digits=(16, int(config['price_accuracy']))),
1614 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
1616 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
1618 'ref': fields.char('Ref.', size=16),
1620 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optional other currency."),
1621 'currency_id': fields.many2one('res.currency', 'Currency'),
1623 'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
1624 '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 chosse between the date of the creation action or the the date of the creation of the entries plus the partner payment terms."),
1625 'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
1628 'date': lambda *a: 'today'
1631 _sql_constraints = [
1632 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model !'),
1633 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model !'),
1635 account_model_line()
1637 # ---------------------------------------------------------
1638 # Account Subscription
1639 # ---------------------------------------------------------
1642 class account_subscription(osv.osv):
1643 _name = "account.subscription"
1644 _description = "Account Subscription"
1646 'name': fields.char('Name', size=64, required=True),
1647 'ref': fields.char('Ref', size=16),
1648 'model_id': fields.many2one('account.model', 'Model', required=True),
1650 'date_start': fields.date('Start Date', required=True),
1651 'period_total': fields.integer('Number of Periods', required=True),
1652 'period_nbr': fields.integer('Period', required=True),
1653 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
1654 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', required=True, readonly=True),
1656 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
1659 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
1660 'period_type': lambda *a: 'month',
1661 'period_total': lambda *a: 12,
1662 'period_nbr': lambda *a: 1,
1663 'state': lambda *a: 'draft',
1665 def state_draft(self, cr, uid, ids, context={}):
1666 self.write(cr, uid, ids, {'state':'draft'})
1669 def check(self, cr, uid, ids, context={}):
1671 for sub in self.browse(cr, uid, ids, context):
1673 for line in sub.lines_id:
1674 if not line.move_id.id:
1678 todone.append(sub.id)
1680 self.write(cr, uid, todone, {'state':'done'})
1683 def remove_line(self, cr, uid, ids, context={}):
1685 for sub in self.browse(cr, uid, ids, context):
1686 for line in sub.lines_id:
1687 if not line.move_id.id:
1688 toremove.append(line.id)
1690 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
1691 self.write(cr, uid, ids, {'state':'draft'})
1694 def compute(self, cr, uid, ids, context={}):
1695 for sub in self.browse(cr, uid, ids, context):
1697 for i in range(sub.period_total):
1698 self.pool.get('account.subscription.line').create(cr, uid, {
1700 'subscription_id': sub.id,
1702 if sub.period_type=='day':
1703 ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(days=sub.period_nbr)).strftime('%Y-%m-%d')
1704 if sub.period_type=='month':
1705 ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(months=sub.period_nbr)).strftime('%Y-%m-%d')
1706 if sub.period_type=='year':
1707 ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(years=sub.period_nbr)).strftime('%Y-%m-%d')
1708 self.write(cr, uid, ids, {'state':'running'})
1710 account_subscription()
1712 class account_subscription_line(osv.osv):
1713 _name = "account.subscription.line"
1714 _description = "Account Subscription Line"
1716 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
1717 'date': fields.date('Date', required=True),
1718 'move_id': fields.many2one('account.move', 'Entry'),
1722 def move_create(self, cr, uid, ids, context={}):
1724 for line in self.browse(cr, uid, ids, context):
1728 ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
1729 tocheck[line.subscription_id.id] = True
1730 self.write(cr, uid, [line.id], {'move_id':ids[0]})
1732 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
1735 account_subscription_line()
1738 class account_config_wizard(osv.osv_memory):
1739 _name = 'account.config.wizard'
1741 def _get_charts(self, cr, uid, context):
1742 module_obj=self.pool.get('ir.module.module')
1743 ids=module_obj.search(cr, uid, [('category_id', '=', 'Account Charts'), ('state', '<>', 'installed')])
1744 res=[(m.id, m.shortdesc) for m in module_obj.browse(cr, uid, ids)]
1745 res.append((-1, 'None'))
1746 res.sort(key=lambda x: x[1])
1750 'name':fields.char('Name', required=True, size=64, help="Name of the fiscal year as displayed on screens."),
1751 'code':fields.char('Code', required=True, size=64, help="Name of the fiscal year as displayed in reports."),
1752 'date1': fields.date('Start Date', required=True),
1753 'date2': fields.date('End Date', required=True),
1754 'period':fields.selection([('month','Month'),('3months','3 Months')], 'Periods', required=True),
1755 'charts' : fields.selection(_get_charts, 'Charts of Account',required=True)
1758 'code': lambda *a: time.strftime('%Y'),
1759 'name': lambda *a: time.strftime('%Y'),
1760 'date1': lambda *a: time.strftime('%Y-01-01'),
1761 'date2': lambda *a: time.strftime('%Y-12-31'),
1762 'period':lambda *a:'month',
1764 def action_cancel(self,cr,uid,ids,conect=None):
1766 'view_type': 'form',
1767 "view_mode": 'form',
1768 'res_model': 'ir.actions.configuration.wizard',
1769 'type': 'ir.actions.act_window',
1773 def install_account_chart(self, cr, uid, ids, context=None):
1774 for res in self.read(cr,uid,ids):
1775 chart_id = res['charts']
1777 mod_obj = self.pool.get('ir.module.module')
1778 mod_obj.button_install(cr, uid, [chart_id], context=context)
1780 db, pool = pooler.restart_pool(cr.dbname, update_module=True)
1782 def action_create(self, cr, uid,ids, context=None):
1783 for res in self.read(cr,uid,ids):
1784 if 'date1' in res and 'date2' in res:
1785 res_obj = self.pool.get('account.fiscalyear')
1786 start_date=res['date1']
1787 end_date=res['date2']
1788 name=res['name']#DateTime.strptime(start_date, '%Y-%m-%d').strftime('%m.%Y') + '-' + DateTime.strptime(end_date, '%Y-%m-%d').strftime('%m.%Y')
1792 'date_start':start_date,
1793 'date_stop':end_date,
1795 new_id=res_obj.create(cr, uid, vals, context=context)
1796 if res['period']=='month':
1797 res_obj.create_period(cr,uid,[new_id])
1798 elif res['period']=='3months':
1799 res_obj.create_period3(cr,uid,[new_id])
1800 self.install_account_chart(cr,uid,ids)
1802 'view_type': 'form',
1803 "view_mode": 'form',
1804 'res_model': 'ir.actions.configuration.wizard',
1805 'type': 'ir.actions.act_window',
1811 account_config_wizard()
1814 # ---------------------------------------------------------------
1815 # Account Templates : Account, Tax, Tax Code and chart. + Wizard
1816 # ---------------------------------------------------------------
1818 class account_tax_template(osv.osv):
1819 _name = 'account.tax.template'
1820 account_tax_template()
1822 class account_account_template(osv.osv):
1824 _name = "account.account.template"
1825 _description ='Templates for Accounts'
1828 'name': fields.char('Name', size=128, required=True, select=True),
1829 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Force all moves for this account to have this secondary currency."),
1830 'code': fields.char('Code', size=64),
1831 'type': fields.selection([
1832 ('receivable','Receivable'),
1833 ('payable','Payable'),
1835 ('consolidation','Consolidation'),
1837 ('closed','Closed'),
1838 ], 'Internal Type', required=True,help="This type is used to differenciate types with "\
1839 "special effects in Open ERP: view can not have entries, consolidation are accounts that "\
1840 "can have children accounts for multi-company consolidations, payable/receivable are for "\
1841 "partners accounts (for debit/credit computations), closed for deprecated accounts."),
1842 'user_type': fields.many2one('account.account.type', 'Account Type', required=True,
1843 help="These types are defined according to your country. The type contain more information "\
1844 "about the account and it's specificities."),
1845 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if you want the user to reconcile entries in this account."),
1846 'shortcut': fields.char('Shortcut', size=12),
1847 'note': fields.text('Note'),
1848 'parent_id': fields.many2one('account.account.template','Parent Account Template', ondelete='cascade'),
1849 'child_parent_ids':fields.one2many('account.account.template','parent_id','Children'),
1850 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel','account_id','tax_id', 'Default Taxes'),
1854 'reconcile': lambda *a: False,
1855 'type' : lambda *a :'view',
1858 def _check_recursion(self, cr, uid, ids):
1861 cr.execute('select parent_id from account_account_template where id in ('+','.join(map(str,ids))+')')
1862 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1869 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
1873 def name_get(self, cr, uid, ids, context={}):
1876 reads = self.read(cr, uid, ids, ['name','code'], context)
1878 for record in reads:
1879 name = record['name']
1881 name = record['code']+' '+name
1882 res.append((record['id'],name ))
1885 account_account_template()
1887 class account_tax_code_template(osv.osv):
1889 _name = 'account.tax.code.template'
1890 _description = 'Tax Code Template'
1894 'name': fields.char('Tax Case Name', size=64, required=True),
1895 'code': fields.char('Case Code', size=64),
1896 'info': fields.text('Description'),
1897 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
1898 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Child Codes'),
1899 'sign': fields.float('Sign for parent', required=True),
1900 '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"),
1904 'sign': lambda *args: 1.0,
1905 'notprintable': lambda *a: False,
1908 def name_get(self, cr, uid, ids, context=None):
1911 if isinstance(ids, (int, long)):
1913 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1914 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1917 def _check_recursion(self, cr, uid, ids):
1920 cr.execute('select distinct parent_id from account_tax_code_template where id in ('+','.join(map(str,ids))+')')
1921 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1928 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
1930 _order = 'code,name'
1931 account_tax_code_template()
1934 class account_chart_template(osv.osv):
1935 _name="account.chart.template"
1936 _description= "Templates for Account Chart"
1939 'name': fields.char('Name', size=64, required=True),
1940 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
1941 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
1942 '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'),
1943 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
1944 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
1945 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
1946 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
1947 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
1948 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
1949 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
1952 account_chart_template()
1954 class account_tax_template(osv.osv):
1956 _name = 'account.tax.template'
1957 _description = 'Templates for Taxes'
1960 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
1961 'name': fields.char('Tax Name', size=64, required=True),
1962 '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."),
1963 'amount': fields.float('Amount', required=True, digits=(14,4)),
1964 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
1965 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True),
1966 '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."),
1967 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
1968 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
1969 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
1970 'child_depend':fields.boolean('Tax on Children', help="Indicate if the tax computation is based on the value computed for the computation of child taxes or based on the total amount."),
1971 'python_compute':fields.text('Python Code'),
1972 'python_compute_inv':fields.text('Python Code (reverse)'),
1973 'python_applicable':fields.text('Python Code'),
1974 '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."),
1977 # Fields used for the VAT declaration
1979 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
1980 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
1981 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1982 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1984 # Same fields for refund invoices
1986 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
1987 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1988 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1989 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1990 '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."),
1991 'description': fields.char('Internal Name', size=32),
1992 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Use In', required=True,)
1995 def name_get(self, cr, uid, ids, context={}):
1999 for record in self.read(cr, uid, ids, ['description','name'], context):
2000 name = record['description'] and record['description'] or record['name']
2001 res.append((record['id'],name ))
2004 def _default_company(self, cr, uid, context={}):
2005 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
2007 return user.company_id.id
2008 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
2011 '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''',
2012 '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''',
2013 'applicable_type': lambda *a: 'true',
2014 'type': lambda *a: 'percent',
2015 'amount': lambda *a: 0,
2016 'sequence': lambda *a: 1,
2017 'tax_group': lambda *a: 'vat',
2018 'ref_tax_sign': lambda *a: 1,
2019 'ref_base_sign': lambda *a: 1,
2020 'tax_sign': lambda *a: 1,
2021 'base_sign': lambda *a: 1,
2022 'include_base_amount': lambda *a: False,
2023 'type_tax_use': lambda *a: 'all',
2028 account_tax_template()
2030 # Fiscal Position Templates
2032 class account_fiscal_position_template(osv.osv):
2033 _name = 'account.fiscal.position.template'
2034 _description = 'Template for Fiscal Position'
2037 'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
2038 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
2039 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Account Mapping'),
2040 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Tax Mapping')
2043 account_fiscal_position_template()
2045 class account_fiscal_position_tax_template(osv.osv):
2046 _name = 'account.fiscal.position.tax.template'
2047 _description = 'Fiscal Position Template Tax Mapping'
2048 _rec_name = 'position_id'
2051 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2052 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
2053 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
2056 account_fiscal_position_tax_template()
2058 class account_fiscal_position_account_template(osv.osv):
2059 _name = 'account.fiscal.position.account.template'
2060 _description = 'Fiscal Position Template Account Mapping'
2061 _rec_name = 'position_id'
2063 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
2064 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
2065 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
2068 account_fiscal_position_account_template()
2070 # Multi charts of Accounts wizard
2072 class wizard_multi_charts_accounts(osv.osv_memory):
2074 Create a new account chart for a company.
2077 * an account chart template
2078 * a number of digits for formatting code of non-view accounts
2079 * a list of bank accounts owned by the company
2081 * generates all accounts from the template and assigns them to the right company
2082 * generates all taxes and tax codes, changing account assignations
2083 * generates all accounting properties and assigns them correctly
2085 _name='wizard.multi.charts.accounts'
2088 'company_id':fields.many2one('res.company','Company',required=True),
2089 'chart_template_id': fields.many2one('account.chart.template','Chart Template',required=True),
2090 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts',required=True),
2091 'code_digits':fields.integer('# of Digits',required=True,help="No. of Digits to use for account code"),
2092 '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."),
2095 def _get_chart(self, cr, uid, context={}):
2096 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2101 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr,uid,[uid],c)[0].company_id.id,
2102 'chart_template_id': _get_chart,
2103 'code_digits': lambda *a:6,
2106 def action_create(self, cr, uid, ids, context=None):
2107 obj_multi = self.browse(cr,uid,ids[0])
2108 obj_acc = self.pool.get('account.account')
2109 obj_acc_tax = self.pool.get('account.tax')
2110 obj_journal = self.pool.get('account.journal')
2111 obj_sequence = self.pool.get('ir.sequence')
2112 obj_acc_template = self.pool.get('account.account.template')
2113 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2114 obj_fiscal_position = self.pool.get('account.fiscal.position')
2117 obj_acc_root = obj_multi.chart_template_id.account_root_id
2118 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2119 company_id = obj_multi.company_id.id
2122 acc_template_ref = {}
2123 tax_template_ref = {}
2124 tax_code_template_ref = {}
2127 #create all the tax code
2128 children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2129 for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2131 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2132 'code': tax_code_template.code,
2133 'info': tax_code_template.info,
2134 '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,
2135 'company_id': company_id,
2136 'sign': tax_code_template.sign,
2138 new_tax_code = self.pool.get('account.tax.code').create(cr,uid,vals)
2139 #recording the new tax code to do the mapping
2140 tax_code_template_ref[tax_code_template.id] = new_tax_code
2143 for tax in obj_multi.chart_template_id.tax_template_ids:
2147 'sequence': tax.sequence,
2148 'amount':tax.amount,
2150 'applicable_type': tax.applicable_type,
2151 'domain':tax.domain,
2152 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2153 'child_depend': tax.child_depend,
2154 'python_compute': tax.python_compute,
2155 'python_compute_inv': tax.python_compute_inv,
2156 'python_applicable': tax.python_applicable,
2157 'tax_group':tax.tax_group,
2158 '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,
2159 '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,
2160 'base_sign': tax.base_sign,
2161 'tax_sign': tax.tax_sign,
2162 '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,
2163 '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,
2164 'ref_base_sign': tax.ref_base_sign,
2165 'ref_tax_sign': tax.ref_tax_sign,
2166 'include_base_amount': tax.include_base_amount,
2167 'description':tax.description,
2168 'company_id': company_id,
2169 'type_tax_use': tax.type_tax_use
2171 new_tax = obj_acc_tax.create(cr,uid,vals_tax)
2172 #as the accounts have not been created yet, we have to wait before filling these fields
2173 todo_dict[new_tax] = {
2174 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2175 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2177 tax_template_ref[tax.id] = new_tax
2179 #deactivate the parent_store functionnality on account_account for rapidity purpose
2180 self.pool._init = True
2182 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id])])
2183 children_acc_template.sort()
2184 for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2186 for tax in account_template.tax_ids:
2187 tax_ids.append(tax_template_ref[tax.id])
2188 #create the account_account
2190 dig = obj_multi.code_digits
2191 code_main = account_template.code and len(account_template.code) or 0
2192 code_acc = account_template.code or ''
2193 if code_main>0 and code_main<=dig and account_template.type != 'view':
2194 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2196 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2197 #'sign': account_template.sign,
2198 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2200 'type': account_template.type,
2201 'user_type': account_template.user_type and account_template.user_type.id or False,
2202 'reconcile': account_template.reconcile,
2203 'shortcut': account_template.shortcut,
2204 'note': account_template.note,
2205 '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,
2206 'tax_ids': [(6,0,tax_ids)],
2207 'company_id': company_id,
2209 new_account = obj_acc.create(cr,uid,vals)
2210 acc_template_ref[account_template.id] = new_account
2211 #reactivate the parent_store functionnality on account_account
2212 self.pool._init = False
2213 self.pool.get('account.account')._parent_store_compute(cr)
2215 for key,value in todo_dict.items():
2216 if value['account_collected_id'] or value['account_paid_id']:
2217 obj_acc_tax.write(cr, uid, [key], vals={
2218 'account_collected_id': acc_template_ref[value['account_collected_id']],
2219 'account_paid_id': acc_template_ref[value['account_paid_id']],
2224 view_id = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Journal View')])[0]
2225 seq_id = obj_sequence.search(cr,uid,[('name','=','Account Journal')])[0]
2227 if obj_multi.seq_journal:
2228 seq_id_sale = obj_sequence.search(cr,uid,[('name','=','Sale Journal')])[0]
2229 seq_id_purchase = obj_sequence.search(cr,uid,[('name','=','Purchase Journal')])[0]
2231 seq_id_sale = seq_id
2232 seq_id_purchase = seq_id
2234 vals_journal['view_id'] = view_id
2237 vals_journal['name'] = _('Sales Journal')
2238 vals_journal['type'] = 'sale'
2239 vals_journal['code'] = _('SAJ')
2240 vals_journal['sequence_id'] = seq_id_sale
2242 if obj_multi.chart_template_id.property_account_receivable:
2243 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2244 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2246 obj_journal.create(cr,uid,vals_journal)
2249 vals_journal['name'] = _('Purchase Journal')
2250 vals_journal['type'] = 'purchase'
2251 vals_journal['code'] = _('EXJ')
2252 vals_journal['sequence_id'] = seq_id_purchase
2254 if obj_multi.chart_template_id.property_account_payable:
2255 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2256 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2258 obj_journal.create(cr,uid,vals_journal)
2261 view_id_cash = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Cash Journal View')])[0]
2262 view_id_cur = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Multi-Currency Cash Journal View')])[0]
2263 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2266 for line in obj_multi.bank_accounts_id:
2267 #create the account_account for this bank journal
2268 tmp = self.pool.get('res.partner.bank').name_get(cr, uid, [line.acc_no.id])[0][1]
2269 dig = obj_multi.code_digits
2270 if ref_acc_bank.code:
2272 new_code = str(int(ref_acc_bank.code.ljust(dig,'0')) + current_num)
2274 new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)),'0')) + str(current_num)
2276 'name': line.acc_no.bank and line.acc_no.bank.name+' '+tmp or tmp,
2277 'currency_id': line.currency_id and line.currency_id.id or False,
2280 'user_type': account_template.user_type and account_template.user_type.id or False,
2282 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2283 'company_id': company_id,
2285 acc_cash_id = obj_acc.create(cr,uid,vals)
2287 if obj_multi.seq_journal:
2289 'name': _('Bank Journal ') + vals['name'],
2290 'code': 'account.journal',
2292 seq_id = obj_sequence.create(cr,uid,vals_seq)
2294 #create the bank journal
2295 vals_journal['name']= vals['name']
2296 vals_journal['code']= _('BNK') + str(current_num)
2297 vals_journal['sequence_id'] = seq_id
2298 vals_journal['type'] = 'cash'
2299 if line.currency_id:
2300 vals_journal['view_id'] = view_id_cur
2301 vals_journal['currency'] = line.currency_id.id
2303 vals_journal['view_id'] = view_id_cash
2304 vals_journal['default_credit_account_id'] = acc_cash_id
2305 vals_journal['default_debit_account_id'] = acc_cash_id
2306 obj_journal.create(cr,uid,vals_journal)
2310 #create the properties
2311 property_obj = self.pool.get('ir.property')
2312 fields_obj = self.pool.get('ir.model.fields')
2315 ('property_account_receivable','res.partner','account.account'),
2316 ('property_account_payable','res.partner','account.account'),
2317 ('property_account_expense_categ','product.category','account.account'),
2318 ('property_account_income_categ','product.category','account.account'),
2319 ('property_account_expense','product.template','account.account'),
2320 ('property_account_income','product.template','account.account')
2322 for record in todo_list:
2324 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2325 account = getattr(obj_multi.chart_template_id, record[0])
2326 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2329 'company_id': company_id,
2330 'fields_id': field[0],
2331 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2334 #the property exist: modify it
2335 property_obj.write(cr, uid, r, vals)
2337 #create the property
2338 property_obj.create(cr, uid, vals)
2340 fp_ids = obj_fiscal_position_template.search(cr, uid,[('chart_template_id', '=', obj_multi.chart_template_id.id)])
2343 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2346 'company_id' : company_id,
2347 'name' : position.name,
2349 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2351 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2352 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2354 for tax in position.tax_ids:
2356 'tax_src_id' : tax_template_ref[tax.tax_src_id.id],
2357 'tax_dest_id' : tax.tax_dest_id and tax_template_ref[tax.tax_dest_id.id] or False,
2358 'position_id' : new_fp,
2360 obj_tax_fp.create(cr, uid, vals_tax)
2362 for acc in position.account_ids:
2364 'account_src_id' : acc_template_ref[acc.account_src_id.id],
2365 'account_dest_id' : acc_template_ref[acc.account_dest_id.id],
2366 'position_id' : new_fp,
2368 obj_ac_fp.create(cr, uid, vals_acc)
2371 'view_type': 'form',
2372 "view_mode": 'form',
2373 'res_model': 'ir.actions.configuration.wizard',
2374 'type': 'ir.actions.act_window',
2377 def action_cancel(self,cr,uid,ids,conect=None):
2379 'view_type': 'form',
2380 "view_mode": 'form',
2381 'res_model': 'ir.actions.configuration.wizard',
2382 'type': 'ir.actions.act_window',
2387 wizard_multi_charts_accounts()
2389 class account_bank_accounts_wizard(osv.osv_memory):
2390 _name='account.bank.accounts.wizard'
2393 'acc_no':fields.many2one('res.partner.bank','Account No.',required=True),
2394 'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2395 'currency_id':fields.many2one('res.currency', 'Currency'),
2398 account_bank_accounts_wizard()
2400 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: