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
34 class account_payment_term(osv.osv):
35 _name = "account.payment.term"
36 _description = "Payment Term"
38 'name': fields.char('Payment Term', size=64, translate=True, required=True),
39 'active': fields.boolean('Active'),
40 'note': fields.text('Description', translate=True),
41 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
44 'active': lambda *a: 1,
48 def compute(self, cr, uid, id, value, date_ref=False, context={}):
50 date_ref = now().strftime('%Y-%m-%d')
51 pt = self.browse(cr, uid, id, context)
54 for line in pt.line_ids:
55 if line.value=='fixed':
56 amt = round(line.value_amount, 2)
57 elif line.value=='procent':
58 amt = round(value * line.value_amount, 2)
59 elif line.value=='balance':
60 amt = round(amount, 2)
62 next_date = mx.DateTime.strptime(date_ref, '%Y-%m-%d') + RelativeDateTime(days=line.days)
64 next_date += RelativeDateTime(day=line.days2)
66 next_date += RelativeDateTime(day=line.days2, months=1)
67 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
71 account_payment_term()
73 class account_payment_term_line(osv.osv):
74 _name = "account.payment.term.line"
75 _description = "Payment Term Line"
77 'name': fields.char('Line Name', size=32,required=True),
78 '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"),
79 'value': fields.selection([('procent','Percent'),('balance','Balance'),('fixed','Fixed Amount')], 'Value',required=True),
80 'value_amount': fields.float('Value Amount'),
81 'days': fields.integer('Number of Days',required=True, help="Number of days to add before computation of the day of month." \
82 "If Date=15/01, Number of Days=22, Day of Month=-1, then the due date is 28/02."),
83 '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)."),
84 'payment_id': fields.many2one('account.payment.term','Payment Term', required=True, select=True),
87 'value': lambda *a: 'balance',
88 'sequence': lambda *a: 5,
89 'days2': lambda *a: 0,
92 account_payment_term_line()
95 class account_account_type(osv.osv):
96 _name = "account.account.type"
97 _description = "Account Type"
99 'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
100 'code': fields.char('Code', size=32, required=True),
101 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of account types."),
102 'partner_account': fields.boolean('Partner account'),
103 'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True),
104 'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign on Reports', required=True, help='Allows to change the displayed amount of the balance in the reports, in order to see positive results instead of negative ones in expenses accounts.'),
107 'close_method': lambda *a: 'none',
108 'sequence': lambda *a: 5,
109 'sign': lambda *a: 1,
112 account_account_type()
114 def _code_get(self, cr, uid, context={}):
115 acc_type_obj = self.pool.get('account.account.type')
116 ids = acc_type_obj.search(cr, uid, [])
117 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context)
118 return [(r['code'], r['name']) for r in res]
120 #----------------------------------------------------------
122 #----------------------------------------------------------
124 class account_tax(osv.osv):
125 _name = 'account.tax'
128 class account_account(osv.osv):
129 _order = "parent_left"
130 _parent_order = "code"
131 _name = "account.account"
132 _description = "Account"
134 _parent_order = 'length(code),code'
136 def search(self, cr, uid, args, offset=0, limit=None, order=None,
137 context=None, count=False):
144 if args[pos][0]=='code' and args[pos][1] in ('like','ilike') and args[pos][2]:
145 args[pos] = ('code', '=like', str(args[pos][2].replace('%',''))+'%')
146 if args[pos][0]=='journal_id':
150 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
151 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
154 ids3 = map(lambda x: x.code, jour.type_control_ids)
155 ids1 = super(account_account,self).search(cr, uid, [('type','in',ids3)])
156 ids1 += map(lambda x: x.id, jour.account_control_ids)
157 args[pos] = ('id','in',ids1)
160 if context and context.has_key('consolidate_childs'): #add consolidated childs of accounts
161 ids = super(account_account,self).search(cr, uid, args, offset, limit,
162 order, context=context, count=count)
163 for consolidate_child in self.browse(cr, uid, context['account_id']).child_consol_ids:
164 ids.append(consolidate_child.id)
167 return super(account_account,self).search(cr, uid, args, offset, limit,
168 order, context=context, count=count)
170 def _get_children_and_consol(self, cr, uid, ids, context={}):
171 #this function search for all the children and all consolidated children (recursively) of the given account ids
172 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)], context=context)
174 for rec in self.browse(cr, uid, ids2, context=context):
175 for child in rec.child_consol_ids:
176 ids3.append(child.id)
178 ids3 = self._get_children_and_consol(cr, uid, ids3, context)
181 def __compute(self, cr, uid, ids, field_names, arg, context={}, query=''):
182 #compute the balance/debit/credit accordingly to the value of field_name for the given account ids
184 'balance': "COALESCE(SUM(l.debit),0) - COALESCE(SUM(l.credit), 0) as balance ",
185 'debit': "COALESCE(SUM(l.debit), 0) as debit ",
186 'credit': "COALESCE(SUM(l.credit), 0) as credit "
188 #get all the necessary accounts
189 ids2 = self._get_children_and_consol(cr, uid, ids, context)
190 acc_set = ",".join(map(str, ids2))
191 #compute for each account the balance/debit/credit from the move lines
194 query = self.pool.get('account.move.line')._query_get(cr, uid,
196 cr.execute(("SELECT l.account_id as id, " +\
197 ' , '.join(map(lambda x: mapping[x], field_names)) +
199 "account_move_line l " \
201 "l.account_id IN (%s) " \
202 "AND " + query + " " \
203 "GROUP BY l.account_id") % (acc_set, ))
205 for res in cr.dictfetchall():
206 accounts[res['id']] = res
208 #for the asked accounts, get from the dictionnary 'accounts' the value of it
211 res[id] = self._get_account_values(cr, uid, id, accounts, field_names, context)
214 def _get_account_values(self, cr, uid, id, accounts, field_names, context={}):
215 res = {}.fromkeys(field_names, 0.0)
216 browse_rec = self.browse(cr, uid, id)
217 if browse_rec.type == 'consolidation':
218 ids2 = self.read(cr, uid, [browse_rec.id], ['child_consol_ids'], context)[0]['child_consol_ids']
219 for t in self.search(cr, uid, [('parent_id', 'child_of', [browse_rec.id])]):
220 if t not in ids2 and t != browse_rec.id:
223 tmp = self._get_account_values(cr, uid, i, accounts, field_names, context)
224 for a in field_names:
227 ids2 = self.search(cr, uid, [('parent_id', 'child_of', [browse_rec.id])])
229 for a in field_names:
230 res[a] += accounts.get(i, {}).get(a, 0.0)
233 def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
235 for rec in self.browse(cr, uid, ids, context):
236 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
239 def _get_child_ids(self, cr, uid, ids, field_name, arg, context={}):
241 for record in self.browse(cr, uid, ids, context):
242 if record.child_parent_ids:
243 result[record.id]=[x.id for x in record.child_parent_ids]
247 if record.child_consol_ids:
248 for acc in record.child_consol_ids:
249 result[record.id].append(acc.id)
254 'name': fields.char('Name', size=128, required=True, select=True),
255 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Force all moves for this account to have this secondary currency."),
256 'code': fields.char('Code', size=64, required=True),
257 'type': fields.selection([
258 ('receivable','Receivable'),
259 ('payable','Payable'),
261 ('consolidation','Consolidation'),
264 ], 'Internal Type', required=True,),
266 'user_type': fields.many2one('account.account.type', 'Account Type', required=True),
267 'parent_id': fields.many2one('account.account','Parent', ondelete='cascade'),
268 'child_parent_ids':fields.one2many('account.account','parent_id','Children'),
269 'child_consol_ids':fields.many2many('account.account', 'account_account_consol_rel', 'child_id', 'parent_id', 'Consolidated Children'),
270 'child_id': fields.function(_get_child_ids, method=True, type='many2many',relation="account.account",string="Children Accounts"),
271 'balance': fields.function(__compute, digits=(16,2), method=True, string='Balance', multi='balance'),
272 'credit': fields.function(__compute, digits=(16,2), method=True, string='Credit', multi='balance'),
273 'debit': fields.function(__compute, digits=(16,2), method=True, string='Debit', multi='balance'),
274 'reconcile': fields.boolean('Reconcile', help="Check this account if the user can make a reconciliation of the entries in this account."),
275 'shortcut': fields.char('Shortcut', size=12),
276 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
277 'account_id','tax_id', 'Default Taxes'),
278 'note': fields.text('Note'),
279 'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
280 'company_id': fields.many2one('res.company', 'Company', required=True),
281 'active': fields.boolean('Active', select=2),
283 'parent_left': fields.integer('Parent Left', select=1),
284 'parent_right': fields.integer('Parent Right', select=1),
285 'currency_mode': fields.selection([('current','At Date'),('average','Average Rate')], 'Outgoing Currencies Rate',
287 'This will select how is computed the current currency rate for outgoing transactions. '\
288 'In most countries the legal method is "average" but only a few softwares are able to '\
289 'manage this. So if you import from another software, you may have to use the rate at date. ' \
290 'Incoming transactions, always use the rate at date.', \
292 'check_history': fields.boolean('Display History',
293 help="Check this box if you want to print all entries when printing the General Ledger, "\
294 "otherwise it will only print its balance."),
297 def _default_company(self, cr, uid, context={}):
298 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
300 return user.company_id.id
301 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
304 'type' : lambda *a :'view',
305 'reconcile': lambda *a: False,
306 'company_id': _default_company,
307 'active': lambda *a: True,
308 'check_history': lambda *a: True,
309 'currency_mode': lambda *a: 'current'
312 def _check_recursion(self, cr, uid, ids):
313 obj_self=self.browse(cr,uid,ids[0])
314 p_id=obj_self.parent_id and obj_self.parent_id.id
315 if (obj_self in obj_self.child_consol_ids) or (p_id and (p_id is obj_self.id)):
318 cr.execute('select distinct child_id from account_account_consol_rel where parent_id in ('+','.join(map(str,ids))+')')
319 child_ids = filter(None, map(lambda x:x[0], cr.fetchall()))
321 if (p_id and (p_id in c_ids)) or (obj_self.id in c_ids):
324 s_ids=self.search(cr,uid,[('parent_id','in',c_ids)])
325 if p_id and (p_id in s_ids):
332 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
334 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
342 if name and str(name).startswith('partner:'):
343 part_id = int(name.split(':')[1])
344 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
345 args += [('id','in', (part.property_account_payable.id, part.property_account_receivable.id))]
347 if name and str(name).startswith('type:'):
348 type = name.split(':')[1]
349 args += [('type','=', type)]
354 ids = self.search(cr, user, [('code','=like',name+"%")]+ args, limit=limit)
356 ids = self.search(cr, user, [('shortcut','=',name)]+ args, limit=limit)
358 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
360 ids = self.search(cr, user, args, context=context, limit=limit)
361 return self.name_get(cr, user, ids, context=context)
363 def name_get(self, cr, uid, ids, context={}):
366 reads = self.read(cr, uid, ids, ['name','code'], context)
369 name = record['name']
371 name = record['code']+' '+name
372 res.append((record['id'],name ))
375 def copy(self, cr, uid, id, default={}, context={},done_list=[]):
376 account = self.browse(cr, uid, id, context=context)
380 default=default.copy()
381 default['parent_id'] = False
382 if account.id in done_list:
384 done_list.append(account.id)
386 for child in account.child_id:
387 child_ids=self.copy(cr, uid, child.id, default, context=context,done_list=done_list)
389 new_child_ids.append(child_ids)
390 default['child_parent_ids'] = [(6, 0, new_child_ids)]
392 default['child_parent_ids'] = False
393 return super(account_account, self).copy(cr, uid, id, default, context=context)
395 def write(self, cr, uid, ids, vals, context=None):
398 if 'active' in vals and not vals['active']:
399 line_obj = self.pool.get('account.move.line')
400 account_ids = self.search(cr, uid, [('id', 'child_of', ids)])
401 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
402 raise osv.except_osv(_('Error !'), _('You can not deactivate an account that contains account moves.'))
403 return super(account_account, self).write(cr, uid, ids, vals, context=context)
406 class account_journal_view(osv.osv):
407 _name = "account.journal.view"
408 _description = "Journal View"
410 'name': fields.char('Journal View', size=64, required=True),
411 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
414 account_journal_view()
417 class account_journal_column(osv.osv):
418 def _col_get(self, cr, user, context={}):
420 cols = self.pool.get('account.move.line')._columns
422 result.append( (col, cols[col].string) )
425 _name = "account.journal.column"
426 _description = "Journal Column"
428 'name': fields.char('Column Name', size=64, required=True),
429 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
430 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
431 'sequence': fields.integer('Sequence'),
432 'required': fields.boolean('Required'),
433 'readonly': fields.boolean('Readonly'),
436 account_journal_column()
438 class account_journal(osv.osv):
439 _name = "account.journal"
440 _description = "Journal"
442 'name': fields.char('Journal Name', size=64, required=True, translate=True),
443 'code': fields.char('Code', size=16),
444 'type': fields.selection([('sale','Sale'), ('purchase','Purchase'), ('cash','Cash'), ('general','General'), ('situation','Situation')], 'Type', size=32, required=True),
445 'refund_journal': fields.boolean('Refund Journal'),
447 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
448 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
450 'active': fields.boolean('Active'),
451 '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."),
452 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account', domain="[('type','!=','view')]"),
453 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account', domain="[('type','!=','view')]"),
454 'centralisation': fields.boolean('Centralised counterpart', help="Check this box if you want that each entry doesn't create a counterpart but share the same counterpart for each entry of this journal. This is used in fiscal year closing."),
455 'update_posted': fields.boolean('Allow Cancelling Entries'),
456 'group_invoice_lines': fields.boolean('Group invoice lines', help="If this box is cheked, the system will try to group the accouting lines when generating them from invoices."),
457 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals", required=True),
458 'user_id': fields.many2one('res.users', 'User', help="The responsible user of this journal"),
459 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
460 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
461 'entry_posted': fields.boolean('Skip \'Draft\' State for Created Entries', help='Check this box if you don\'t want that new account moves pass through the \'draft\' state and goes direclty to the \'posted state\' without any manual validation.'),
462 'company_id': fields.related('default_credit_account_id','company_id',type='many2one', relation="res.company", string="Company"),
463 'fy_seq_id': fields.one2many('fiscalyear.seq', 'journal_id', 'Sequences'),
467 'active': lambda *a: 1,
468 'user_id': lambda self,cr,uid,context: uid,
470 def create(self, cr, uid, vals, context={}):
471 journal_id = super(osv.osv, self).create(cr, uid, vals, context)
472 # journal_name = self.browse(cr, uid, [journal_id])[0].code
473 # periods = self.pool.get('account.period')
474 # ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
475 # for period in periods.browse(cr, uid, ids):
476 # self.pool.get('account.journal.period').create(cr, uid, {
477 # 'name': (journal_name or '')+':'+(period.code or ''),
478 # 'journal_id': journal_id,
479 # 'period_id': period.id
482 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
489 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
491 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
492 return self.name_get(cr, user, ids, context=context)
495 class account_fiscalyear(osv.osv):
496 _name = "account.fiscalyear"
497 _description = "Fiscal Year"
499 'name': fields.char('Fiscal Year', size=64, required=True),
500 'code': fields.char('Code', size=6, required=True),
501 'company_id': fields.many2one('res.company', 'Company',
502 help="Keep empty if the fiscal year belongs to several companies."),
503 'date_start': fields.date('Start date', required=True),
504 'date_stop': fields.date('End date', required=True),
505 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
506 'state': fields.selection([('draft','Draft'), ('done','Done')], 'Status', redonly=True),
510 'state': lambda *a: 'draft',
512 _order = "date_start"
514 def _check_duration(self,cr,uid,ids):
515 obj_fy=self.browse(cr,uid,ids[0])
516 if obj_fy.date_stop < obj_fy.date_start:
521 (_check_duration, 'Error ! The date duration of the Fiscal Year is invalid. ', ['date_stop'])
524 def create_period3(self,cr, uid, ids, context={}):
525 return self.create_period(cr, uid, ids, context, 3)
527 def create_period(self,cr, uid, ids, context={}, interval=1):
528 for fy in self.browse(cr, uid, ids, context):
530 ds = mx.DateTime.strptime(fy.date_start, '%Y-%m-%d')
531 while ds.strftime('%Y-%m-%d')<fy.date_stop:
532 de = ds + RelativeDateTime(months=interval, days=-1)
534 if de.strftime('%Y-%m-%d')>fy.date_stop:
535 de=mx.DateTime.strptime(fy.date_stop, '%Y-%m-%d')
537 self.pool.get('account.period').create(cr, uid, {
538 'name': ds.strftime('%m/%Y'),
539 'code': ds.strftime('%m/%Y'),
540 'date_start': ds.strftime('%Y-%m-%d'),
541 'date_stop': de.strftime('%Y-%m-%d'),
542 'fiscalyear_id': fy.id,
544 ds = ds + RelativeDateTime(months=interval)
547 def find(self, cr, uid, dt=None, exception=True, context={}):
549 dt = time.strftime('%Y-%m-%d')
550 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
553 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
559 class account_period(osv.osv):
560 _name = "account.period"
561 _description = "Account period"
563 'name': fields.char('Period Name', size=64, required=True),
564 'code': fields.char('Code', size=12),
565 'special': fields.boolean('Opening/Closing Period', size=12,
566 help="These periods can overlap."),
567 'date_start': fields.date('Start of period', required=True, states={'done':[('readonly',True)]}),
568 'date_stop': fields.date('End of period', required=True, states={'done':[('readonly',True)]}),
569 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
570 'state': fields.selection([('draft','Draft'), ('done','Done')], 'Status', readonly=True)
573 'state': lambda *a: 'draft',
575 _order = "date_start"
577 def _check_duration(self,cr,uid,ids,context={}):
578 obj_period=self.browse(cr,uid,ids[0])
579 if obj_period.date_stop < obj_period.date_start:
583 def _check_year_limit(self,cr,uid,ids,context={}):
584 for obj_period in self.browse(cr,uid,ids):
585 if obj_period.special:
587 if obj_period.fiscalyear_id.date_stop < obj_period.date_stop or obj_period.fiscalyear_id.date_stop < obj_period.date_start or obj_period.fiscalyear_id.date_start > obj_period.date_start or obj_period.fiscalyear_id.date_start > obj_period.date_stop:
590 pids = self.search(cr, uid, [('date_stop','>=',obj_period.date_start),('date_start','<=',obj_period.date_stop),('special','=',False),('id','<>',obj_period.id)])
591 for period in self.browse(cr, uid, pids):
592 if period.fiscalyear_id.company_id.id==obj_period.fiscalyear_id.company_id.id:
597 (_check_duration, 'Error ! The date duration of the Period(s) is invalid. ', ['date_stop']),
598 (_check_year_limit, 'Invalid period ! Some periods overlap or the date duration is not in the limit of the fiscal year. ', ['date_stop'])
601 def next(self, cr, uid, period, step, context={}):
602 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
607 def find(self, cr, uid, dt=None, context={}):
609 dt = time.strftime('%Y-%m-%d')
610 #CHECKME: shouldn't we check the state of the period?
611 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
613 raise osv.except_osv(_('Error !'), _('No period defined for this date !\nPlease create a fiscal year.'))
616 def action_draft(self, cr, uid, ids, *args):
617 users_roles = self.pool.get('res.users').browse(cr, uid, uid).roles_id
618 for role in users_roles:
619 if role.name=='Period':
622 cr.execute('update account_journal_period set state=%s where period_id=%s', (mode, id))
623 cr.execute('update account_period set state=%s where id=%s', (mode, id))
628 class account_journal_period(osv.osv):
629 _name = "account.journal.period"
630 _description = "Journal - Period"
632 def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
633 result = {}.fromkeys(ids, 'STOCK_NEW')
634 for r in self.read(cr, uid, ids, ['state']):
636 'draft': 'STOCK_NEW',
637 'printed': 'STOCK_PRINT_PREVIEW',
638 'done': 'STOCK_DIALOG_AUTHENTICATION',
639 }.get(r['state'], 'STOCK_NEW')
643 'name': fields.char('Journal-Period Name', size=64, required=True),
644 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
645 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
646 'icon': fields.function(_icon_get, method=True, string='Icon', type='string'),
647 'active': fields.boolean('Active', required=True),
648 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'Status', required=True, readonly=True)
651 def _check(self, cr, uid, ids, context={}):
652 for obj in self.browse(cr, uid, ids, context):
653 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))
656 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
659 def write(self, cr, uid, ids, vals, context={}):
660 self._check(cr, uid, ids, context)
661 return super(account_journal_period, self).write(cr, uid, ids, vals, context)
663 def create(self, cr, uid, vals, context={}):
664 period_id=vals.get('period_id',False)
666 period = self.pool.get('account.period').browse(cr, uid,period_id)
667 vals['state']=period.state
668 return super(account_journal_period, self).create(cr, uid, vals, context)
670 def unlink(self, cr, uid, ids, context={}):
671 self._check(cr, uid, ids, context)
672 return super(account_journal_period, self).unlink(cr, uid, ids, context)
675 'state': lambda *a: 'draft',
676 'active': lambda *a: True,
680 account_journal_period()
682 class account_fiscalyear(osv.osv):
683 _inherit = "account.fiscalyear"
684 _description = "Fiscal Year"
686 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
690 #----------------------------------------------------------
692 #----------------------------------------------------------
693 class account_move(osv.osv):
694 _name = "account.move"
695 _description = "Account Entry"
698 def name_get(self, cursor, user, ids, context=None):
702 data_move = self.pool.get('account.move').browse(cursor,user,ids)
703 for move in data_move:
704 if move.state=='draft':
705 name = '*' + str(move.id)
708 res.append((move.id, name))
712 def _get_period(self, cr, uid, context):
713 periods = self.pool.get('account.period').find(cr, uid)
719 def _amount_compute(self, cr, uid, ids, name, args, context, where =''):
720 if not ids: return {}
721 cr.execute('select move_id,sum(debit) from account_move_line where move_id in ('+','.join(map(str,ids))+') group by move_id')
722 result = dict(cr.fetchall())
724 result.setdefault(id, 0.0)
728 'name': fields.char('Number', size=64, required=True),
729 'ref': fields.char('Ref', size=64),
730 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
731 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
732 'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'Status', required=True, readonly=True),
733 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
734 'to_check': fields.boolean('To Be Verified'),
735 'partner_id': fields.related('line_id', 'partner_id', type="many2one", relation="res.partner", string="Partner"),
736 'amount': fields.function(_amount_compute, method=True, string='Amount', digits=(16,2)),
737 'date': fields.date('Date', required=True),
738 'type': fields.selection([
739 ('pay_voucher','Cash Payment'),
740 ('bank_pay_voucher','Bank Payment'),
741 ('rec_voucher','Cash Receipt'),
742 ('bank_rec_voucher','Bank Receipt'),
743 ('cont_voucher','Contra'),
744 ('journal_sale_vou','Journal Sale'),
745 ('journal_pur_voucher','Journal Purchase'),
746 ('journal_voucher','Journal Voucher'),
747 ],'Type', readonly=True, select=True, states={'draft':[('readonly',False)]}),
750 'name': lambda *a: '/',
751 'state': lambda *a: 'draft',
752 'period_id': _get_period,
753 'type' : lambda *a : 'journal_voucher',
754 'date': lambda *a:time.strftime('%Y-%m-%d'),
757 def _check_centralisation(self, cursor, user, ids):
758 for move in self.browse(cursor, user, ids):
759 if move.journal_id.centralisation:
760 move_ids = self.search(cursor, user, [
761 ('period_id', '=', move.period_id.id),
762 ('journal_id', '=', move.journal_id.id),
764 if len(move_ids) > 1:
768 def _check_period_journal(self, cursor, user, ids):
769 for move in self.browse(cursor, user, ids):
770 for line in move.line_id:
771 if line.period_id.id != move.period_id.id:
773 if line.journal_id.id != move.journal_id.id:
778 (_check_centralisation,
779 'You can not create more than one move per period on centralized journal',
781 (_check_period_journal,
782 'You can not create entries on different period/journal in the same move',
785 def post(self, cr, uid, ids, context=None):
786 if self.validate(cr, uid, ids, context) and len(ids):
787 for move in self.browse(cr, uid, ids):
790 journal = move.journal_id
791 if journal.sequence_id:
792 new_name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
794 raise osv.except_osv(_('Error'), _('No sequence defined in the journal !'))
796 self.write(cr, uid, [move.id], {'name':new_name})
798 cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('posted',))
800 raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non balanced entry !'))
803 def button_validate(self, cursor, user, ids, context=None):
804 return self.post(cursor, user, ids, context=context)
806 def button_cancel(self, cr, uid, ids, context={}):
807 for line in self.browse(cr, uid, ids, context):
808 if not line.journal_id.update_posted:
809 raise osv.except_osv(_('Error !'), _('You can not modify a posted entry of this journal !\nYou should mark the journal to allow canceling entries.'))
811 cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('draft',))
814 def write(self, cr, uid, ids, vals, context={}):
816 c['novalidate'] = True
817 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
818 self.validate(cr, uid, ids, context)
822 # TODO: Check if period is closed !
824 def create(self, cr, uid, vals, context={}):
825 if 'line_id' in vals:
826 if 'journal_id' in vals:
827 for l in vals['line_id']:
829 l[2]['journal_id'] = vals['journal_id']
830 context['journal_id'] = vals['journal_id']
831 if 'period_id' in vals:
832 for l in vals['line_id']:
834 l[2]['period_id'] = vals['period_id']
835 context['period_id'] = vals['period_id']
837 default_period = self._get_period(cr, uid, context)
838 for l in vals['line_id']:
840 l[2]['period_id'] = default_period
841 context['period_id'] = default_period
843 accnt_journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'])
844 if 'line_id' in vals:
846 c['novalidate'] = True
847 result = super(account_move, self).create(cr, uid, vals, c)
848 self.validate(cr, uid, [result], context)
850 result = super(account_move, self).create(cr, uid, vals, context)
853 def copy(self, cr, uid, id, default=None, context=None):
856 default = default.copy()
857 default.update({'state':'draft', 'name':'/',})
858 return super(account_move, self).copy(cr, uid, id, default, context)
860 def unlink(self, cr, uid, ids, context={}, check=True):
862 for move in self.browse(cr, uid, ids, context):
863 if move['state'] <> 'draft':
864 raise osv.except_osv(_('UserError'),
865 _('You can not delete posted movement: "%s"!') % \
867 line_ids = map(lambda x: x.id, move.line_id)
868 context['journal_id'] = move.journal_id.id
869 context['period_id'] = move.period_id.id
870 self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
871 self.pool.get('account.move.line').unlink(cr, uid, line_ids, context=context)
872 toremove.append(move.id)
873 result = super(account_move, self).unlink(cr, uid, toremove, context)
876 def _compute_balance(self, cr, uid, id, context={}):
877 move = self.browse(cr, uid, [id])[0]
879 for line in move.line_id:
880 amount+= (line.debit - line.credit)
883 def _centralise(self, cr, uid, move, mode):
885 account_id = move.journal_id.default_debit_account_id.id
888 raise osv.except_osv(_('UserError'),
889 _('There is no default default debit account defined \n' \
890 'on journal "%s"') % move.journal_id.name)
892 account_id = move.journal_id.default_credit_account_id.id
895 raise osv.except_osv(_('UserError'),
896 _('There is no default default credit account defined \n' \
897 'on journal "%s"') % move.journal_id.name)
899 # find the first line of this move with the current mode
900 # or create it if it doesn't exist
901 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode))
906 line_id = self.pool.get('account.move.line').create(cr, uid, {
907 'name': 'Centralisation '+mode,
908 'centralisation': mode,
909 'account_id': account_id,
911 'journal_id': move.journal_id.id,
912 'period_id': move.period_id.id,
913 'date': move.period_id.date_stop,
916 }, {'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
918 # find the first line of this move with the other mode
919 # so that we can exclude it from our calculation
920 cr.execute('select id from account_move_line where move_id=%s and centralisation=%s limit 1', (move.id, mode2))
927 cr.execute('select sum('+mode+') from account_move_line where move_id=%s and id<>%s', (move.id, line_id2))
928 result = cr.fetchone()[0] or 0.0
929 cr.execute('update account_move_line set '+mode2+'=%s where id=%s', (result, line_id))
933 # Validate a balanced move. If it is a centralised journal, create a move.
935 def validate(self, cr, uid, ids, context={}):
937 for move in self.browse(cr, uid, ids, context):
938 #unlink analytic lines on move_lines
939 for obj_line in move.line_id:
940 for obj in obj_line.analytic_lines:
941 self.pool.get('account.analytic.line').unlink(cr,uid,obj.id)
943 journal = move.journal_id
948 for line in move.line_id:
949 amount += line.debit - line.credit
950 line_ids.append(line.id)
951 if line.state=='draft':
952 line_draft_ids.append(line.id)
955 company_id = line.account_id.company_id.id
956 if not company_id == line.account_id.company_id.id:
957 raise osv.except_osv(_('Error'), _("Couldn't create move between different companies"))
959 if line.account_id.currency_id:
960 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):
961 raise osv.except_osv(_('Error'), _("""Couldn't create move with currency different than 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)))
963 if abs(amount) < 0.0001:
964 if not len(line_draft_ids):
966 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
967 'journal_id': move.journal_id.id,
968 'period_id': move.period_id.id,
970 }, context, check=False)
974 if journal.type not in ('purchase','sale'):
977 for line in move.line_id:
979 key = (line.account_id.id, line.tax_code_id.id)
981 code = account2[key][0]
982 amount = account2[key][1] * (line.debit + line.credit)
983 elif line.account_id.id in account:
984 code = account[line.account_id.id][0]
985 amount = account[line.account_id.id][1] * (line.debit + line.credit)
986 if (code or amount) and not (line.tax_code_id or line.tax_amount):
987 self.pool.get('account.move.line').write(cr, uid, [line.id], {
990 }, context, check=False)
995 if journal.centralisation:
996 self._centralise(cr, uid, move, 'debit')
997 self._centralise(cr, uid, move, 'credit')
998 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
1000 }, context, check=False)
1003 self.pool.get('account.move.line').write(cr, uid, line_ids, {
1004 'journal_id': move.journal_id.id,
1005 'period_id': move.period_id.id,
1006 #'tax_code_id': False,
1007 #'tax_amount': False,
1009 }, context, check=False)
1013 for tmp in move.line_id:
1014 list_ids.append(tmp.id)
1015 self.pool.get('account.move.line').create_analytic_lines(cr, uid, list_ids, context)
1019 class account_move_reconcile(osv.osv):
1020 _name = "account.move.reconcile"
1021 _description = "Account Reconciliation"
1023 'name': fields.char('Name', size=64, required=True),
1024 'type': fields.char('Type', size=16, required=True),
1025 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry lines'),
1026 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
1027 'create_date': fields.date('Creation date', readonly=True),
1030 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
1032 def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
1033 for rec in self.browse(cr, uid, ids, context):
1035 for line in rec.line_partial_ids:
1036 total += (line.debit or 0.0) - (line.credit or 0.0)
1038 self.pool.get('account.move.line').write(cr, uid,
1039 map(lambda x: x.id, rec.line_partial_ids),
1040 {'reconcile_id': rec.id }
1044 def name_get(self, cr, uid, ids, context=None):
1048 for r in self.browse(cr, uid, ids, context):
1049 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
1051 name = '%s (%.2f)' % (r.name, total)
1052 result.append((r.id,name))
1054 result.append((r.id,r.name))
1058 account_move_reconcile()
1060 #----------------------------------------------------------
1062 #----------------------------------------------------------
1065 child_depend: la taxe depend des taxes filles
1067 class account_tax_code(osv.osv):
1069 A code for the tax object.
1071 This code is used for some tax declarations.
1073 def _sum(self, cr, uid, ids, name, args, context, where =''):
1074 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
1075 acc_set = ",".join(map(str, ids2))
1076 if context.get('based_on', 'invoices') == 'payments':
1077 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1078 FROM account_move_line AS line, \
1079 account_move AS move \
1080 LEFT JOIN account_invoice invoice ON \
1081 (invoice.move_id = move.id) \
1082 WHERE line.tax_code_id in ('+acc_set+') '+where+' \
1083 AND move.id = line.move_id \
1084 AND ((invoice.state = \'paid\') \
1085 OR (invoice.id IS NULL)) \
1086 GROUP BY line.tax_code_id')
1088 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
1089 FROM account_move_line AS line \
1090 WHERE line.tax_code_id in ('+acc_set+') '+where+' \
1091 GROUP BY line.tax_code_id')
1092 res=dict(cr.fetchall())
1093 for record in self.browse(cr, uid, ids, context):
1094 def _rec_get(record):
1095 amount = res.get(record.id, 0.0)
1096 for rec in record.child_ids:
1097 amount += _rec_get(rec) * rec.sign
1099 res[record.id] = round(_rec_get(record), 2)
1102 def _sum_year(self, cr, uid, ids, name, args, context):
1103 if 'fiscalyear_id' in context and context['fiscalyear_id']:
1104 fiscalyear_id = context['fiscalyear_id']
1106 fiscalyear_id = self.pool.get('account.fiscalyear').find(cr, uid, exception=False)
1109 pids = map(lambda x: str(x.id), self.pool.get('account.fiscalyear').browse(cr, uid, fiscalyear_id).period_ids)
1111 where = ' and period_id in (' + (','.join(pids))+')'
1112 return self._sum(cr, uid, ids, name, args, context,
1115 def _sum_period(self, cr, uid, ids, name, args, context):
1116 if 'period_id' in context and context['period_id']:
1117 period_id = context['period_id']
1119 period_id = self.pool.get('account.period').find(cr, uid)
1120 if not len(period_id):
1121 return dict.fromkeys(ids, 0.0)
1122 period_id = period_id[0]
1123 return self._sum(cr, uid, ids, name, args, context,
1124 where=' and line.period_id='+str(period_id))
1126 _name = 'account.tax.code'
1127 _description = 'Tax Code'
1130 'name': fields.char('Tax Case Name', size=64, required=True),
1131 'code': fields.char('Case Code', size=64),
1132 'info': fields.text('Description'),
1133 'sum': fields.function(_sum_year, method=True, string="Year Sum"),
1134 'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1135 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1136 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Childs Codes'),
1137 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1138 'company_id': fields.many2one('res.company', 'Company', required=True),
1139 'sign': fields.float('Sign for parent', required=True),
1140 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want that any vat related to this Tax Code appears on invoices"),
1144 def name_get(self, cr, uid, ids, context=None):
1147 if isinstance(ids, (int, long)):
1149 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1150 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1153 def _default_company(self, cr, uid, context={}):
1154 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1156 return user.company_id.id
1157 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1159 'company_id': _default_company,
1160 'sign': lambda *args: 1.0,
1161 'notprintable': lambda *a: False,
1163 def _check_recursion(self, cr, uid, ids):
1166 cr.execute('select distinct parent_id from account_tax_code where id in ('+','.join(map(str,ids))+')')
1167 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1174 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1176 _order = 'code,name'
1179 class account_tax(osv.osv):
1183 Type: percent, fixed, none, code
1184 PERCENT: tax = price * amount
1185 FIXED: tax = price + amount
1187 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1188 return result in the context
1189 Ex: result=round(price_unit*0.21,4)
1191 _name = 'account.tax'
1192 _description = 'Tax'
1194 'name': fields.char('Tax Name', size=64, required=True, translate=True, help="This name will be used to be displayed on reports"),
1195 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the taxes lines from the lowest sequences to the higher ones. The order is important if you have a tax that have several tax childs. In this case, the evaluation order is important."),
1196 'amount': fields.float('Amount', required=True, digits=(14,4)),
1197 'active': fields.boolean('Active'),
1198 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code'),('balance','Balance')], 'Tax Type', required=True,
1199 help="The computation method for the tax amount."),
1200 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True,
1201 help="If not applicable (computed through a Python code), the tax do not appears on the invoice."),
1202 'domain':fields.char('Domain', size=32, help="This field is only used if you develop your own module allowing developpers to create specific taxes in a custom domain."),
1203 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1204 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1205 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1206 'child_ids':fields.one2many('account.tax', 'parent_id', 'Childs Tax Account'),
1207 'child_depend':fields.boolean('Tax on Childs', help="Indicate if the tax computation is based on the value computed for the computation of child taxes or based on the total amount."),
1208 'python_compute':fields.text('Python Code'),
1209 'python_compute_inv':fields.text('Python Code (reverse)'),
1210 'python_applicable':fields.text('Python Code'),
1211 '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."),
1214 # Fields used for the VAT declaration
1216 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1217 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1218 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1219 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1221 # Same fields for refund invoices
1223 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the VAT declaration."),
1224 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1225 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1226 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1227 '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"),
1228 'company_id': fields.many2one('res.company', 'Company', required=True),
1229 'description': fields.char('Tax Code',size=32),
1230 'price_include': fields.boolean('Tax Included in Price', help="Check this is the price you use on the product and invoices is including this tax."),
1231 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase'),('all','All')], 'Tax Application', required=True)
1234 def search(self, cr, uid, args, offset=0, limit=None, order=None,
1235 context=None, count=False):
1236 if context and context.has_key('type'):
1237 if context['type'] in ('out_invoice','out_refund'):
1238 args.append(('type_tax_use','=','sale'))
1239 elif context['type'] in ('in_invoice','in_refund'):
1240 args.append(('type_tax_use','=','purchase'))
1241 return super(account_tax, self).search(cr, uid, args, offset, limit, order, context, count)
1243 def name_get(self, cr, uid, ids, context={}):
1247 for record in self.read(cr, uid, ids, ['description','name'], context):
1248 name = record['description'] and record['description'] or record['name']
1249 res.append((record['id'],name ))
1252 def _default_company(self, cr, uid, context={}):
1253 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1255 return user.company_id.id
1256 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1258 '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''',
1259 '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''',
1260 'applicable_type': lambda *a: 'true',
1261 'type': lambda *a: 'percent',
1262 'amount': lambda *a: 0,
1263 'price_include': lambda *a: 0,
1264 'active': lambda *a: 1,
1265 'type_tax_use': lambda *a: 'all',
1266 'sequence': lambda *a: 1,
1267 'tax_group': lambda *a: 'vat',
1268 'ref_tax_sign': lambda *a: 1,
1269 'ref_base_sign': lambda *a: 1,
1270 'tax_sign': lambda *a: 1,
1271 'base_sign': lambda *a: 1,
1272 'include_base_amount': lambda *a: False,
1273 'company_id': _default_company,
1277 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1280 if tax.applicable_type=='code':
1281 localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id), 'product':product, 'partner':partner}
1282 exec tax.python_applicable in localdict
1283 if localdict.get('result', False):
1289 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1290 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1293 cur_price_unit=price_unit
1295 # we compute the amount for the current tax object and append it to the result
1297 data = {'id':tax.id,
1299 'account_collected_id':tax.account_collected_id.id,
1300 'account_paid_id':tax.account_paid_id.id,
1301 'base_code_id': tax.base_code_id.id,
1302 'ref_base_code_id': tax.ref_base_code_id.id,
1303 'sequence': tax.sequence,
1304 'base_sign': tax.base_sign,
1305 'tax_sign': tax.tax_sign,
1306 'ref_base_sign': tax.ref_base_sign,
1307 'ref_tax_sign': tax.ref_tax_sign,
1308 'price_unit': cur_price_unit,
1309 'tax_code_id': tax.tax_code_id.id,
1310 'ref_tax_code_id': tax.ref_tax_code_id.id,
1313 if tax.type=='percent':
1314 amount = cur_price_unit * tax.amount
1315 data['amount'] = amount
1317 elif tax.type=='fixed':
1318 data['amount'] = tax.amount
1319 elif tax.type=='code':
1320 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1321 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1322 exec tax.python_compute in localdict
1323 amount = localdict['result']
1324 data['amount'] = amount
1325 elif tax.type=='balance':
1326 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1327 data['balance'] = cur_price_unit
1329 amount2 = data['amount']
1330 if len(tax.child_ids):
1331 if tax.child_depend:
1334 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner)
1335 res.extend(child_tax)
1336 if tax.child_depend:
1338 for name in ('base','ref_base'):
1339 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1340 r[name+'_code_id'] = latest[name+'_code_id']
1341 r[name+'_sign'] = latest[name+'_sign']
1342 r['price_unit'] = latest['price_unit']
1343 latest[name+'_code_id'] = False
1344 for name in ('tax','ref_tax'):
1345 if latest[name+'_code_id'] and latest[name+'_sign'] and not r[name+'_code_id']:
1346 r[name+'_code_id'] = latest[name+'_code_id']
1347 r[name+'_sign'] = latest[name+'_sign']
1348 r['amount'] = data['amount']
1349 latest[name+'_code_id'] = False
1350 if tax.include_base_amount:
1351 cur_price_unit+=amount2
1354 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1357 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1361 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1362 one tax for each tax id in IDS and their childs
1364 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner)
1367 if r.get('balance',False):
1368 r['amount'] = round(r['balance'] * quantity, 2) - total
1370 r['amount'] = round(r['amount'] * quantity, 2)
1371 total += r['amount']
1375 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1376 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1380 cur_price_unit=price_unit
1382 tax_parent_tot = 0.0
1384 if (tax.type=='percent') and not tax.include_base_amount:
1385 tax_parent_tot+=tax.amount
1388 if tax.type=='percent':
1389 if tax.include_base_amount:
1390 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1392 amount = (cur_price_unit / (1 + tax_parent_tot)) * tax.amount
1394 elif tax.type=='fixed':
1397 elif tax.type=='code':
1398 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1399 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1400 exec tax.python_compute_inv in localdict
1401 amount = localdict['result']
1402 elif tax.type=='balance':
1403 data['amount'] = cur_price_unit - reduce(lambda x,y: y.get('amount',0.0)+x, res, 0.0)
1404 data['balance'] = cur_price_unit
1407 if tax.include_base_amount:
1408 cur_price_unit -= amount
1417 'account_collected_id': tax.account_collected_id.id,
1418 'account_paid_id': tax.account_paid_id.id,
1419 'base_code_id': tax.base_code_id.id,
1420 'ref_base_code_id': tax.ref_base_code_id.id,
1421 'sequence': tax.sequence,
1422 'base_sign': tax.base_sign,
1423 'tax_sign': tax.tax_sign,
1424 'ref_base_sign': tax.ref_base_sign,
1425 'ref_tax_sign': tax.ref_tax_sign,
1426 'price_unit': cur_price_unit,
1427 'tax_code_id': tax.tax_code_id.id,
1428 'ref_tax_code_id': tax.ref_tax_code_id.id,
1430 if len(tax.child_ids):
1431 if tax.child_depend:
1435 parent_tax = self._unit_compute_inv(cr, uid, tax.child_ids, amount, address_id, product, partner)
1436 res.extend(parent_tax)
1441 total += r['amount']
1443 r['price_unit'] -= total
1447 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1449 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1450 Price Unit is a VAT included price
1454 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1455 one tax for each tax id in IDS and their childs
1457 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1460 if r.get('balance',False):
1461 r['amount'] = round(r['balance'] * quantity, 2) - total
1463 r['amount'] = round(r['amount'] * quantity, 2)
1464 total += r['amount']
1468 # ---------------------------------------------------------
1469 # Account Entries Models
1470 # ---------------------------------------------------------
1472 class account_model(osv.osv):
1473 _name = "account.model"
1474 _description = "Account Model"
1476 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
1477 'ref': fields.char('Ref', size=64),
1478 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
1479 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
1480 'legend' :fields.text('Legend',readonly=True,size=100),
1484 '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'),
1486 def generate(self, cr, uid, ids, datas={}, context={}):
1488 for model in self.browse(cr, uid, ids, context):
1489 period_id = self.pool.get('account.period').find(cr,uid, context=context)
1491 raise osv.except_osv('No period found !', 'Unable to find a valid period !')
1492 period_id = period_id[0]
1494 if model.journal_id.sequence_id:
1495 name = self.pool.get('ir.sequence').get_id(cr, uid, model.journal_id.sequence_id.id)
1496 move_id = self.pool.get('account.move').create(cr, uid, {
1499 'period_id': period_id,
1500 'journal_id': model.journal_id.id,
1502 move_ids.append(move_id)
1503 for line in model.lines_id:
1506 'journal_id': model.journal_id.id,
1507 'period_id': period_id
1511 'quantity': line.quantity,
1512 'debit': line.debit,
1513 'credit': line.credit,
1514 'account_id': line.account_id.id,
1517 'partner_id': line.partner_id.id,
1518 'date': time.strftime('%Y-%m-%d'),
1519 'date_maturity': time.strftime('%Y-%m-%d')
1522 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
1523 self.pool.get('account.move.line').create(cr, uid, val, context=c)
1528 class account_model_line(osv.osv):
1529 _name = "account.model.line"
1530 _description = "Account Model Entries"
1532 'name': fields.char('Name', size=64, required=True),
1533 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from the lowest sequences to the higher ones"),
1534 'quantity': fields.float('Quantity', digits=(16,2), help="The optionnal quantity on entries"),
1535 'debit': fields.float('Debit', digits=(16,2)),
1536 'credit': fields.float('Credit', digits=(16,2)),
1538 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
1540 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
1542 'ref': fields.char('Ref.', size=16),
1544 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optionnal other currency."),
1545 'currency_id': fields.many2one('res.currency', 'Currency'),
1547 'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
1548 '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."),
1549 'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
1552 'date': lambda *a: 'today'
1555 _sql_constraints = [
1556 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model !'),
1557 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model !'),
1559 account_model_line()
1561 # ---------------------------------------------------------
1562 # Account Subscription
1563 # ---------------------------------------------------------
1566 class account_subscription(osv.osv):
1567 _name = "account.subscription"
1568 _description = "Account Subscription"
1570 'name': fields.char('Name', size=64, required=True),
1571 'ref': fields.char('Ref.', size=16),
1572 'model_id': fields.many2one('account.model', 'Model', required=True),
1574 'date_start': fields.date('Starting date', required=True),
1575 'period_total': fields.integer('Number of period', required=True),
1576 'period_nbr': fields.integer('Period', required=True),
1577 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
1578 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'Status', required=True, readonly=True),
1580 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
1583 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
1584 'period_type': lambda *a: 'month',
1585 'period_total': lambda *a: 12,
1586 'period_nbr': lambda *a: 1,
1587 'state': lambda *a: 'draft',
1589 def state_draft(self, cr, uid, ids, context={}):
1590 self.write(cr, uid, ids, {'state':'draft'})
1593 def check(self, cr, uid, ids, context={}):
1595 for sub in self.browse(cr, uid, ids, context):
1597 for line in sub.lines_id:
1598 if not line.move_id.id:
1602 todone.append(sub.id)
1604 self.write(cr, uid, todone, {'state':'done'})
1607 def remove_line(self, cr, uid, ids, context={}):
1609 for sub in self.browse(cr, uid, ids, context):
1610 for line in sub.lines_id:
1611 if not line.move_id.id:
1612 toremove.append(line.id)
1614 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
1615 self.write(cr, uid, ids, {'state':'draft'})
1618 def compute(self, cr, uid, ids, context={}):
1619 for sub in self.browse(cr, uid, ids, context):
1621 for i in range(sub.period_total):
1622 self.pool.get('account.subscription.line').create(cr, uid, {
1624 'subscription_id': sub.id,
1626 if sub.period_type=='day':
1627 ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(days=sub.period_nbr)).strftime('%Y-%m-%d')
1628 if sub.period_type=='month':
1629 ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(months=sub.period_nbr)).strftime('%Y-%m-%d')
1630 if sub.period_type=='year':
1631 ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(years=sub.period_nbr)).strftime('%Y-%m-%d')
1632 self.write(cr, uid, ids, {'state':'running'})
1634 account_subscription()
1636 class account_subscription_line(osv.osv):
1637 _name = "account.subscription.line"
1638 _description = "Account Subscription Line"
1640 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
1641 'date': fields.date('Date', required=True),
1642 'move_id': fields.many2one('account.move', 'Entry'),
1646 def move_create(self, cr, uid, ids, context={}):
1648 for line in self.browse(cr, uid, ids, context):
1652 ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
1653 tocheck[line.subscription_id.id] = True
1654 self.write(cr, uid, [line.id], {'move_id':ids[0]})
1656 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
1659 account_subscription_line()
1662 class account_config_wizard(osv.osv_memory):
1663 _name = 'account.config.wizard'
1665 def _get_charts(self, cr, uid, context):
1666 module_obj=self.pool.get('ir.module.module')
1667 ids=module_obj.search(cr, uid, [('category_id', '=', 'Account Charts'), ('state', '<>', 'installed')])
1668 res=[(m.id, m.shortdesc) for m in module_obj.browse(cr, uid, ids)]
1669 res.append((-1, 'None'))
1670 res.sort(key=lambda x: x[1])
1674 'name':fields.char('Name', required=True, size=64, help="Name of the fiscal year as displayed on screens."),
1675 'code':fields.char('Code', required=True, size=64, help="Name of the fiscal year as displayed in reports."),
1676 'date1': fields.date('Starting Date', required=True),
1677 'date2': fields.date('Ending Date', required=True),
1678 'period':fields.selection([('month','Month'),('3months','3 Months')], 'Periods', required=True),
1679 'charts' : fields.selection(_get_charts, 'Charts of Account',required=True)
1682 'code': lambda *a: time.strftime('%Y'),
1683 'name': lambda *a: time.strftime('%Y'),
1684 'date1': lambda *a: time.strftime('%Y-01-01'),
1685 'date2': lambda *a: time.strftime('%Y-12-31'),
1686 'period':lambda *a:'month',
1688 def action_cancel(self,cr,uid,ids,conect=None):
1690 'view_type': 'form',
1691 "view_mode": 'form',
1692 'res_model': 'ir.actions.configuration.wizard',
1693 'type': 'ir.actions.act_window',
1697 def install_account_chart(self, cr, uid, ids, context=None):
1698 for res in self.read(cr,uid,ids):
1699 chart_id = res['charts']
1701 mod_obj = self.pool.get('ir.module.module')
1702 mod_obj.button_install(cr, uid, [chart_id], context=context)
1704 db, pool = pooler.restart_pool(cr.dbname, update_module=True)
1706 def action_create(self, cr, uid,ids, context=None):
1707 for res in self.read(cr,uid,ids):
1708 if 'date1' in res and 'date2' in res:
1709 res_obj = self.pool.get('account.fiscalyear')
1710 start_date=res['date1']
1711 end_date=res['date2']
1712 name=res['name']#DateTime.strptime(start_date, '%Y-%m-%d').strftime('%m.%Y') + '-' + DateTime.strptime(end_date, '%Y-%m-%d').strftime('%m.%Y')
1716 'date_start':start_date,
1717 'date_stop':end_date,
1719 new_id=res_obj.create(cr, uid, vals, context=context)
1720 if res['period']=='month':
1721 res_obj.create_period(cr,uid,[new_id])
1722 elif res['period']=='3months':
1723 res_obj.create_period3(cr,uid,[new_id])
1724 self.install_account_chart(cr,uid,ids)
1726 'view_type': 'form',
1727 "view_mode": 'form',
1728 'res_model': 'ir.actions.configuration.wizard',
1729 'type': 'ir.actions.act_window',
1735 account_config_wizard()
1738 # ---------------------------------------------------------------
1739 # Account Templates : Account, Tax, Tax Code and chart. + Wizard
1740 # ---------------------------------------------------------------
1742 class account_tax_template(osv.osv):
1743 _name = 'account.tax.template'
1744 account_tax_template()
1746 class account_account_template(osv.osv):
1748 _name = "account.account.template"
1749 _description ='Templates for Accounts'
1752 'name': fields.char('Name', size=128, required=True, select=True),
1753 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Force all moves for this account to have this secondary currency."),
1754 'code': fields.char('Code', size=64),
1755 'type': fields.selection([
1756 ('receivable','Receivable'),
1757 ('payable','Payable'),
1759 ('consolidation','Consolidation'),
1761 ('closed','Closed'),
1762 ], 'Internal Type', required=True,),
1763 'user_type': fields.many2one('account.account.type', 'Account Type', required=True),
1764 'reconcile': fields.boolean('Allow Reconciliation', help="Check this option if the user can make a reconciliation of the entries in this account."),
1765 'shortcut': fields.char('Shortcut', size=12),
1766 'note': fields.text('Note'),
1767 'parent_id': fields.many2one('account.account.template','Parent Account Template', ondelete='cascade'),
1768 'child_parent_ids':fields.one2many('account.account.template','parent_id','Children'),
1769 'tax_ids': fields.many2many('account.tax.template', 'account_account_template_tax_rel','account_id','tax_id', 'Default Taxes'),
1773 'reconcile': lambda *a: False,
1774 'type' : lambda *a :'view',
1777 def _check_recursion(self, cr, uid, ids):
1780 cr.execute('select parent_id from account_account_template where id in ('+','.join(map(str,ids))+')')
1781 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1788 (_check_recursion, 'Error ! You can not create recursive account templates.', ['parent_id'])
1792 def name_get(self, cr, uid, ids, context={}):
1795 reads = self.read(cr, uid, ids, ['name','code'], context)
1797 for record in reads:
1798 name = record['name']
1800 name = record['code']+' '+name
1801 res.append((record['id'],name ))
1804 account_account_template()
1806 class account_tax_code_template(osv.osv):
1808 _name = 'account.tax.code.template'
1809 _description = 'Tax Code Template'
1813 'name': fields.char('Tax Case Name', size=64, required=True),
1814 'code': fields.char('Case Code', size=64),
1815 'info': fields.text('Description'),
1816 'parent_id': fields.many2one('account.tax.code.template', 'Parent Code', select=True),
1817 'child_ids': fields.one2many('account.tax.code.template', 'parent_id', 'Childs Codes'),
1818 'sign': fields.float('Sign for parent', required=True),
1819 'notprintable':fields.boolean("Not Printable in Invoice", help="Check this box if you don't want that any vat related to this Tax Code appears on invoices"),
1823 'sign': lambda *args: 1.0,
1824 'notprintable': lambda *a: False,
1827 def name_get(self, cr, uid, ids, context=None):
1830 if isinstance(ids, (int, long)):
1832 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1833 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1836 def _check_recursion(self, cr, uid, ids):
1839 cr.execute('select distinct parent_id from account_tax_code_template where id in ('+','.join(map(str,ids))+')')
1840 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1847 (_check_recursion, 'Error ! You can not create recursive Tax Codes.', ['parent_id'])
1849 _order = 'code,name'
1850 account_tax_code_template()
1853 class account_chart_template(osv.osv):
1854 _name="account.chart.template"
1855 _description= "Templates for Account Chart"
1858 'name': fields.char('Name', size=64, required=True),
1859 'account_root_id': fields.many2one('account.account.template','Root Account',required=True,domain=[('parent_id','=',False)]),
1860 'tax_code_root_id': fields.many2one('account.tax.code.template','Root Tax Code',required=True,domain=[('parent_id','=',False)]),
1861 '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'),
1862 'bank_account_view_id': fields.many2one('account.account.template','Bank Account',required=True),
1863 'property_account_receivable': fields.many2one('account.account.template','Receivable Account'),
1864 'property_account_payable': fields.many2one('account.account.template','Payable Account'),
1865 'property_account_expense_categ': fields.many2one('account.account.template','Expense Category Account'),
1866 'property_account_income_categ': fields.many2one('account.account.template','Income Category Account'),
1867 'property_account_expense': fields.many2one('account.account.template','Expense Account on Product Template'),
1868 'property_account_income': fields.many2one('account.account.template','Income Account on Product Template'),
1871 account_chart_template()
1873 class account_tax_template(osv.osv):
1875 _name = 'account.tax.template'
1876 _description = 'Templates for Taxes'
1879 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
1880 'name': fields.char('Tax Name', size=64, required=True),
1881 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the taxes lines from the lowest sequences to the higher ones. The order is important if you have a tax that have several tax children. In this case, the evaluation order is important."),
1882 'amount': fields.float('Amount', required=True, digits=(14,4)),
1883 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
1884 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True),
1885 '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."),
1886 'account_collected_id':fields.many2one('account.account.template', 'Invoice Tax Account'),
1887 'account_paid_id':fields.many2one('account.account.template', 'Refund Tax Account'),
1888 'parent_id':fields.many2one('account.tax.template', 'Parent Tax Account', select=True),
1889 'child_depend':fields.boolean('Tax on Childs', help="Indicate if the tax computation is based on the value computed for the computation of child taxes or based on the total amount."),
1890 'python_compute':fields.text('Python Code'),
1891 'python_compute_inv':fields.text('Python Code (reverse)'),
1892 'python_applicable':fields.text('Python Code'),
1893 '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."),
1896 # Fields used for the VAT declaration
1898 'base_code_id': fields.many2one('account.tax.code.template', 'Base Code', help="Use this code for the VAT declaration."),
1899 'tax_code_id': fields.many2one('account.tax.code.template', 'Tax Code', help="Use this code for the VAT declaration."),
1900 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1901 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1903 # Same fields for refund invoices
1905 'ref_base_code_id': fields.many2one('account.tax.code.template', 'Refund Base Code', help="Use this code for the VAT declaration."),
1906 'ref_tax_code_id': fields.many2one('account.tax.code.template', 'Refund Tax Code', help="Use this code for the VAT declaration."),
1907 'ref_base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."),
1908 'ref_tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."),
1909 '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."),
1910 'description': fields.char('Internal Name', size=32),
1911 'type_tax_use': fields.selection([('sale','Sale'),('purchase','Purchase')], 'Tax Use in')
1914 def name_get(self, cr, uid, ids, context={}):
1918 for record in self.read(cr, uid, ids, ['description','name'], context):
1919 name = record['description'] and record['description'] or record['name']
1920 res.append((record['id'],name ))
1923 def _default_company(self, cr, uid, context={}):
1924 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1926 return user.company_id.id
1927 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1930 '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''',
1931 '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''',
1932 'applicable_type': lambda *a: 'true',
1933 'type': lambda *a: 'percent',
1934 'amount': lambda *a: 0,
1935 'sequence': lambda *a: 1,
1936 'tax_group': lambda *a: 'vat',
1937 'ref_tax_sign': lambda *a: 1,
1938 'ref_base_sign': lambda *a: 1,
1939 'tax_sign': lambda *a: 1,
1940 'base_sign': lambda *a: 1,
1941 'include_base_amount': lambda *a: False,
1946 account_tax_template()
1948 # Fiscal Position Templates
1950 class account_fiscal_position_template(osv.osv):
1951 _name = 'account.fiscal.position.template'
1952 _description = 'Template for Fiscal Position'
1955 'name': fields.char('Fiscal Position Template', size=64, translate=True, required=True),
1956 'chart_template_id': fields.many2one('account.chart.template', 'Chart Template', required=True),
1957 'account_ids': fields.one2many('account.fiscal.position.account.template', 'position_id', 'Accounts Mapping'),
1958 'tax_ids': fields.one2many('account.fiscal.position.tax.template', 'position_id', 'Taxes Mapping')
1961 account_fiscal_position_template()
1963 class account_fiscal_position_tax_template(osv.osv):
1964 _name = 'account.fiscal.position.tax.template'
1965 _description = 'Fiscal Position Template Taxes Mapping'
1966 _rec_name = 'position_id'
1969 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
1970 'tax_src_id': fields.many2one('account.tax.template', 'Tax Source', required=True),
1971 'tax_dest_id': fields.many2one('account.tax.template', 'Replacement Tax')
1974 account_fiscal_position_tax_template()
1976 class account_fiscal_position_account_template(osv.osv):
1977 _name = 'account.fiscal.position.account.template'
1978 _description = 'Fiscal Position Template Accounts Mapping'
1979 _rec_name = 'position_id'
1981 'position_id': fields.many2one('account.fiscal.position.template', 'Fiscal Position', required=True, ondelete='cascade'),
1982 'account_src_id': fields.many2one('account.account.template', 'Account Source', domain=[('type','<>','view')], required=True),
1983 'account_dest_id': fields.many2one('account.account.template', 'Account Destination', domain=[('type','<>','view')], required=True)
1986 account_fiscal_position_account_template()
1988 # Multi charts of Accounts wizard
1990 class wizard_multi_charts_accounts(osv.osv_memory):
1992 Create a new account chart for a company.
1995 * an account chart template
1996 * a number of digits for formatting code of non-view accounts
1997 * a list of bank account owned by the company
1999 * generates all accounts from the template and assign them to the right company
2000 * generates all taxes and tax codes, changing account assignations
2001 * generates all accounting properties and assign correctly
2003 _name='wizard.multi.charts.accounts'
2006 'company_id':fields.many2one('res.company','Company',required=True),
2007 'chart_template_id': fields.many2one('account.chart.template','Chart Template',required=True),
2008 'bank_accounts_id': fields.one2many('account.bank.accounts.wizard', 'bank_account_id', 'Bank Accounts',required=True),
2009 'code_digits':fields.integer('# of Digits',required=True,help="No. of Digits to use for account code"),
2010 '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."),
2013 def _get_chart(self, cr, uid, context={}):
2014 ids = self.pool.get('account.chart.template').search(cr, uid, [], context=context)
2019 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr,uid,[uid],c)[0].company_id.id,
2020 'chart_template_id': _get_chart,
2021 'code_digits': lambda *a:6,
2024 def action_create(self, cr, uid, ids, context=None):
2025 obj_multi = self.browse(cr,uid,ids[0])
2026 obj_acc = self.pool.get('account.account')
2027 obj_acc_tax = self.pool.get('account.tax')
2028 obj_journal = self.pool.get('account.journal')
2029 obj_sequence = self.pool.get('ir.sequence')
2030 obj_acc_template = self.pool.get('account.account.template')
2031 obj_fiscal_position_template = self.pool.get('account.fiscal.position.template')
2032 obj_fiscal_position = self.pool.get('account.fiscal.position')
2035 obj_acc_root = obj_multi.chart_template_id.account_root_id
2036 tax_code_root_id = obj_multi.chart_template_id.tax_code_root_id.id
2037 company_id = obj_multi.company_id.id
2040 acc_template_ref = {}
2041 tax_template_ref = {}
2042 tax_code_template_ref = {}
2045 #create all the tax code
2046 children_tax_code_template = self.pool.get('account.tax.code.template').search(cr, uid, [('parent_id','child_of',[tax_code_root_id])], order='id')
2047 for tax_code_template in self.pool.get('account.tax.code.template').browse(cr, uid, children_tax_code_template):
2049 'name': (tax_code_root_id == tax_code_template.id) and obj_multi.company_id.name or tax_code_template.name,
2050 'code': tax_code_template.code,
2051 'info': tax_code_template.info,
2052 '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,
2053 'company_id': company_id,
2054 'sign': tax_code_template.sign,
2056 new_tax_code = self.pool.get('account.tax.code').create(cr,uid,vals)
2057 #recording the new tax code to do the mapping
2058 tax_code_template_ref[tax_code_template.id] = new_tax_code
2061 for tax in obj_multi.chart_template_id.tax_template_ids:
2065 'sequence': tax.sequence,
2066 'amount':tax.amount,
2068 'applicable_type': tax.applicable_type,
2069 'domain':tax.domain,
2070 'parent_id': tax.parent_id and ((tax.parent_id.id in tax_template_ref) and tax_template_ref[tax.parent_id.id]) or False,
2071 'child_depend': tax.child_depend,
2072 'python_compute': tax.python_compute,
2073 'python_compute_inv': tax.python_compute_inv,
2074 'python_applicable': tax.python_applicable,
2075 'tax_group':tax.tax_group,
2076 '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,
2077 '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,
2078 'base_sign': tax.base_sign,
2079 'tax_sign': tax.tax_sign,
2080 '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,
2081 '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,
2082 'ref_base_sign': tax.ref_base_sign,
2083 'ref_tax_sign': tax.ref_tax_sign,
2084 'include_base_amount': tax.include_base_amount,
2085 'description':tax.description,
2086 'company_id': company_id,
2087 'type_tax_use': tax.type_tax_use
2089 new_tax = obj_acc_tax.create(cr,uid,vals_tax)
2090 #as the accounts have not been created yet, we have to wait before filling these fields
2091 todo_dict[new_tax] = {
2092 'account_collected_id': tax.account_collected_id and tax.account_collected_id.id or False,
2093 'account_paid_id': tax.account_paid_id and tax.account_paid_id.id or False,
2095 tax_template_ref[tax.id] = new_tax
2097 #deactivate the parent_store functionnality on account_account for rapidity purpose
2098 self.pool._init = True
2100 children_acc_template = obj_acc_template.search(cr, uid, [('parent_id','child_of',[obj_acc_root.id])])
2101 children_acc_template.sort()
2102 for account_template in obj_acc_template.browse(cr, uid, children_acc_template):
2104 for tax in account_template.tax_ids:
2105 tax_ids.append(tax_template_ref[tax.id])
2106 #create the account_account
2108 dig = obj_multi.code_digits
2109 code_main = account_template.code and len(account_template.code) or 0
2110 code_acc = account_template.code or ''
2111 if code_main>0 and code_main<=dig and account_template.type != 'view':
2112 code_acc=str(code_acc) + (str('0'*(dig-code_main)))
2114 'name': (obj_acc_root.id == account_template.id) and obj_multi.company_id.name or account_template.name,
2115 #'sign': account_template.sign,
2116 'currency_id': account_template.currency_id and account_template.currency_id.id or False,
2118 'type': account_template.type,
2119 'user_type': account_template.user_type and account_template.user_type.id or False,
2120 'reconcile': account_template.reconcile,
2121 'shortcut': account_template.shortcut,
2122 'note': account_template.note,
2123 '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,
2124 'tax_ids': [(6,0,tax_ids)],
2125 'company_id': company_id,
2127 new_account = obj_acc.create(cr,uid,vals)
2128 acc_template_ref[account_template.id] = new_account
2129 #reactivate the parent_store functionnality on account_account
2130 self.pool._init = False
2131 self.pool.get('account.account')._parent_store_compute(cr)
2133 for key,value in todo_dict.items():
2134 if value['account_collected_id'] or value['account_paid_id']:
2135 obj_acc_tax.write(cr, uid, [key], vals={
2136 'account_collected_id': acc_template_ref[value['account_collected_id']],
2137 'account_paid_id': acc_template_ref[value['account_paid_id']],
2142 view_id = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Journal View')])[0]
2143 seq_id = obj_sequence.search(cr,uid,[('name','=','Account Journal')])[0]
2145 if obj_multi.seq_journal:
2146 seq_id_sale = obj_sequence.search(cr,uid,[('name','=','Sale Journal')])[0]
2147 seq_id_purchase = obj_sequence.search(cr,uid,[('name','=','Purchase Journal')])[0]
2149 seq_id_sale = seq_id
2150 seq_id_purchase = seq_id
2152 vals_journal['view_id'] = view_id
2155 vals_journal['name'] = _('Sales Journal')
2156 vals_journal['type'] = 'sale'
2157 vals_journal['code'] = _('SAJ')
2158 vals_journal['sequence_id'] = seq_id_sale
2160 if obj_multi.chart_template_id.property_account_receivable:
2161 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2162 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_income_categ.id]
2164 obj_journal.create(cr,uid,vals_journal)
2167 vals_journal['name'] = _('Purchase Journal')
2168 vals_journal['type'] = 'purchase'
2169 vals_journal['code'] = _('EXJ')
2170 vals_journal['sequence_id'] = seq_id_purchase
2172 if obj_multi.chart_template_id.property_account_payable:
2173 vals_journal['default_credit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2174 vals_journal['default_debit_account_id'] = acc_template_ref[obj_multi.chart_template_id.property_account_expense_categ.id]
2176 obj_journal.create(cr,uid,vals_journal)
2179 view_id_cash = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Cash Journal View')])[0]
2180 view_id_cur = self.pool.get('account.journal.view').search(cr,uid,[('name','=','Multi-Currency Cash Journal View')])[0]
2181 ref_acc_bank = obj_multi.chart_template_id.bank_account_view_id
2184 for line in obj_multi.bank_accounts_id:
2185 #create the account_account for this bank journal
2186 tmp = self.pool.get('res.partner.bank').name_get(cr, uid, [line.acc_no.id])[0][1]
2187 dig = obj_multi.code_digits
2189 'name': line.acc_no.bank and line.acc_no.bank.name+' '+tmp or tmp,
2190 'currency_id': line.currency_id and line.currency_id.id or False,
2191 'code': str(ref_acc_bank.code.ljust(dig,'0') + str(current_num)),
2193 'user_type': account_template.user_type and account_template.user_type.id or False,
2195 'parent_id': acc_template_ref[ref_acc_bank.id] or False,
2196 'company_id': company_id,
2198 acc_cash_id = obj_acc.create(cr,uid,vals)
2200 if obj_multi.seq_journal:
2202 'name': _('Bank Journal ') + vals['name'],
2203 'code': 'account.journal',
2205 seq_id = obj_sequence.create(cr,uid,vals_seq)
2207 #create the bank journal
2208 vals_journal['name']= vals['name']
2209 vals_journal['code']= _('BNK') + str(current_num)
2210 vals_journal['sequence_id'] = seq_id
2211 vals_journal['type'] = 'cash'
2212 if line.currency_id:
2213 vals_journal['view_id'] = view_id_cur
2214 vals_journal['currency'] = line.currency_id.id
2216 vals_journal['view_id'] = view_id_cash
2217 vals_journal['default_credit_account_id'] = acc_cash_id
2218 vals_journal['default_debit_account_id'] = acc_cash_id
2219 obj_journal.create(cr,uid,vals_journal)
2223 #create the properties
2224 property_obj = self.pool.get('ir.property')
2225 fields_obj = self.pool.get('ir.model.fields')
2228 ('property_account_receivable','res.partner','account.account'),
2229 ('property_account_payable','res.partner','account.account'),
2230 ('property_account_expense_categ','product.category','account.account'),
2231 ('property_account_income_categ','product.category','account.account'),
2232 ('property_account_expense','product.template','account.account'),
2233 ('property_account_income','product.template','account.account')
2235 for record in todo_list:
2237 r = property_obj.search(cr, uid, [('name','=', record[0] ),('company_id','=',company_id)])
2238 account = getattr(obj_multi.chart_template_id, record[0])
2239 field = fields_obj.search(cr, uid, [('name','=',record[0]),('model','=',record[1]),('relation','=',record[2])])
2242 'company_id': company_id,
2243 'fields_id': field[0],
2244 'value': account and 'account.account,'+str(acc_template_ref[account.id]) or False,
2247 #the property exist: modify it
2248 property_obj.write(cr, uid, r, vals)
2250 #create the property
2251 property_obj.create(cr, uid, vals)
2253 fp_ids = obj_fiscal_position_template.search(cr, uid,[('chart_template_id', '=', obj_multi.chart_template_id.id)])
2256 for position in obj_fiscal_position_template.browse(cr, uid, fp_ids):
2259 'company_id' : company_id,
2260 'name' : position.name,
2262 new_fp = obj_fiscal_position.create(cr, uid, vals_fp)
2264 obj_tax_fp = self.pool.get('account.fiscal.position.tax')
2265 obj_ac_fp = self.pool.get('account.fiscal.position.account')
2267 for tax in position.tax_ids:
2269 'tax_src_id' : tax_template_ref[tax.tax_src_id.id],
2270 'tax_dest_id' : tax_template_ref[tax.tax_dest_id.id],
2271 'position_id' : new_fp,
2273 obj_tax_fp.create(cr, uid, vals_tax)
2275 for acc in position.account_ids:
2277 'account_src_id' : acc_template_ref[acc.account_src_id.id],
2278 'account_dest_id' : acc_template_ref[acc.account_dest_id.id],
2279 'position_id' : new_fp,
2281 obj_ac_fp.create(cr, uid, vals_acc)
2284 'view_type': 'form',
2285 "view_mode": 'form',
2286 'res_model': 'ir.actions.configuration.wizard',
2287 'type': 'ir.actions.act_window',
2290 def action_cancel(self,cr,uid,ids,conect=None):
2292 'view_type': 'form',
2293 "view_mode": 'form',
2294 'res_model': 'ir.actions.configuration.wizard',
2295 'type': 'ir.actions.act_window',
2300 wizard_multi_charts_accounts()
2302 class account_bank_accounts_wizard(osv.osv_memory):
2303 _name='account.bank.accounts.wizard'
2306 'acc_no':fields.many2one('res.partner.bank','Account No.',required=True),
2307 'bank_account_id':fields.many2one('wizard.multi.charts.accounts', 'Bank Account', required=True),
2308 'currency_id':fields.many2one('res.currency', 'Currency'),
2311 account_bank_accounts_wizard()
2313 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: