1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
8 # WARNING: This program as such is intended to be used by professional
9 # programmers who take the whole responsability of assessing all potential
10 # consequences resulting from its eventual inadequacies and bugs
11 # End users who are looking for a ready-to-use solution with commercial
12 # garantees and support are strongly adviced to contract a Free Software
15 # This program is Free Software; you can redistribute it and/or
16 # modify it under the terms of the GNU General Public License
17 # as published by the Free Software Foundation; either version 2
18 # of the License, or (at your option) any later version.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 ##############################################################################
32 from osv import fields, osv
34 from tools.misc import currency
35 from tools.translate import _
38 from mx.DateTime import RelativeDateTime, now, DateTime, localtime
41 class account_payment_term(osv.osv):
42 _name = "account.payment.term"
43 _description = "Payment Term"
45 'name': fields.char('Payment Term', size=32, translate=True),
46 'active': fields.boolean('Active'),
47 'note': fields.text('Description', translate=True),
48 'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms'),
51 'active': lambda *a: 1,
54 def compute(self, cr, uid, id, value, date_ref=False, context={}):
56 date_ref = now().strftime('%Y-%m-%d')
57 pt = self.browse(cr, uid, id, context)
60 for line in pt.line_ids:
61 if line.value=='fixed':
62 amt = round(line.value_amount, 2)
63 elif line.value=='procent':
64 amt = round(value * line.value_amount, 2)
65 elif line.value=='balance':
66 amt = round(amount, 2)
68 next_date = mx.DateTime.strptime(date_ref, '%Y-%m-%d') + RelativeDateTime(days=line.days)
69 if line.condition == 'end of month':
70 next_date += RelativeDateTime(day=-1)
71 result.append( (next_date.strftime('%Y-%m-%d'), amt) )
75 account_payment_term()
77 class account_payment_term_line(osv.osv):
78 _name = "account.payment.term.line"
79 _description = "Payment Term Line"
81 'name': fields.char('Line Name', size=32,required=True),
82 '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"),
83 'value': fields.selection([('procent','Percent'),('balance','Balance'),('fixed','Fixed Amount')], 'Value',required=True),
84 'value_amount': fields.float('Value Amount'),
85 'days': fields.integer('Number of Days',required=True),
86 'condition': fields.selection([('net days','Net Days'),('end of month','End of Month')], 'Condition', required=True, help="The payment delay condition id a number of days expressed in 2 ways: net days or end of the month. The 'net days' condition implies that the paiment arrive after 'Number of Days' calendar days. The 'end of the month' condition requires that the paiement arrives before the end of the month that is that is after 'Number of Days' calendar days."),
87 'payment_id': fields.many2one('account.payment.term','Payment Term', required=True, select=True),
90 'value': lambda *a: 'balance',
91 'sequence': lambda *a: 5,
92 'condition': lambda *a: 'net days',
95 account_payment_term_line()
98 class account_account_type(osv.osv):
99 _name = "account.account.type"
100 _description = "Account Type"
102 'name': fields.char('Acc. Type Name', size=64, required=True, translate=True),
103 'code': fields.char('Code', size=32, required=True),
104 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of account types."),
105 'code_from': fields.char('Code From', size=10, help="Gives the range of account code available for this type of account. These fields are given for information and are not used in any constraint."),
106 'code_to': fields.char('Code To', size=10, help="Gives the range of account code available for this type of account. These fields are just given for information and are not used in any constraint."),
107 'partner_account': fields.boolean('Partner account'),
108 'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True),
111 'close_method': lambda *a: 'none',
112 'sequence': lambda *a: 5,
115 account_account_type()
117 def _code_get(self, cr, uid, context={}):
118 acc_type_obj = self.pool.get('account.account.type')
119 ids = acc_type_obj.search(cr, uid, [])
120 res = acc_type_obj.read(cr, uid, ids, ['code', 'name'], context)
121 return [(r['code'], r['name']) for r in res]
123 #----------------------------------------------------------
125 #----------------------------------------------------------
127 class account_tax(osv.osv):
128 _name = 'account.tax'
131 class account_account(osv.osv):
133 _name = "account.account"
134 _description = "Account"
136 def search(self, cr, uid, args, offset=0, limit=None, order=None,
137 context=None, count=False):
142 if args[pos][0]=='journal_id':
146 jour = self.pool.get('account.journal').browse(cr, uid, args[pos][2])
147 if (not (jour.account_control_ids or jour.type_control_ids)) or not args[pos][2]:
150 ids3 = map(lambda x: x.code, jour.type_control_ids)
151 ids1 = super(account_account,self).search(cr, uid, [('type','in',ids3)])
152 ids1 += map(lambda x: x.id, jour.account_control_ids)
153 args[pos] = ('id','in',ids1)
155 return super(account_account,self).search(cr, uid, args, offset, limit,
156 order, context=context, count=count)
158 def _credit(self, cr, uid, ids, field_name, arg, context={}):
159 acc_set = ",".join(map(str, ids))
160 query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
161 cr.execute(("SELECT a.id, " \
162 "SUM(COALESCE(l.credit * a.sign, 0)) " \
163 "FROM account_account a " \
164 "LEFT JOIN account_move_line l " \
165 "ON (a.id = l.account_id) " \
166 "WHERE a.type != 'view' " \
167 "AND a.id IN (%s) " \
168 "AND " + query + " " \
170 "GROUP BY a.id") % (acc_set, ))
175 for account_id, sum in res2:
176 res[account_id] += sum
179 def _debit(self, cr, uid, ids, field_name, arg, context={}):
180 acc_set = ",".join(map(str, ids))
181 query = self.pool.get('account.move.line')._query_get(cr, uid, context=context)
182 cr.execute(("SELECT a.id, " \
183 "SUM(COALESCE(l.debit * a.sign, 0)) " \
184 "FROM account_account a " \
185 "LEFT JOIN account_move_line l " \
186 "ON (a.id = l.account_id) " \
187 "WHERE a.type != 'view' " \
188 "AND a.id IN (%s) " \
189 "AND " + query + " " \
191 "GROUP BY a.id") % (acc_set, ))
196 for account_id, sum in res2:
197 res[account_id] += sum
200 def _balance(self, cr, uid, ids, field_name, arg, context={}):
201 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
202 ids2 = {}.fromkeys(ids + ids2).keys()
203 acc_set = ",".join(map(str, ids2))
204 query = self.pool.get('account.move.line')._query_get(cr, uid,
206 cr.execute(("SELECT a.id, " \
207 "SUM((COALESCE(l.debit, 0) - COALESCE(l.credit, 0))) " \
208 "FROM account_account a " \
209 "LEFT JOIN account_move_line l " \
210 "ON (a.id=l.account_id) " \
211 "WHERE a.type != 'view' " \
212 "AND a.id IN (%s) " \
213 "AND " + query + " " \
215 "GROUP BY a.id") % (acc_set, ))
217 for account_id, sum in cr.fetchall():
218 res[account_id] = round(sum,2)
219 cr.execute("SELECT a.id, a.company_id " \
220 "FROM account_account a " \
221 "WHERE id IN (%s)" % acc_set)
222 resc = dict(cr.fetchall())
223 cr.execute("SELECT id, currency_id FROM res_company")
224 rescur = dict(cr.fetchall())
227 ids3 = self.search(cr, uid, [('parent_id', 'child_of', [id])])
228 to_currency_id = rescur[resc[id]]
231 res.setdefault(id, 0.0)
232 if resc[idx]<>resc[id] and resc[idx] and resc[id]:
233 from_currency_id = rescur[resc[idx]]
234 res[id] += self.pool.get('res.currency').compute(cr,
235 uid, from_currency_id, to_currency_id,
236 res.get(idx, 0.0), context=context)
238 res[id] += res.get(idx, 0.0)
240 res[id] = round(res.get(id,0.0), 2)
243 def _get_company_currency(self, cr, uid, ids, field_name, arg, context={}):
245 for rec in self.browse(cr, uid, ids, context):
246 result[rec.id] = (rec.company_id.currency_id.id,rec.company_id.currency_id.code)
250 'name': fields.char('Name', size=128, required=True, select=True),
251 'sign': fields.selection([(-1, 'Negative'), (1, 'Positive')], 'Sign', required=True, help='Allows to change the displayed amount of the balance to see positive results instead of negative ones in expenses accounts'),
252 'currency_id': fields.many2one('res.currency', 'Secondary Currency', help="Force all moves for this account to have this secondary currency."),
253 'code': fields.char('Code', size=64),
254 'type': fields.selection(_code_get, 'Account Type', required=True),
255 'parent_id': fields.many2many('account.account', 'account_account_rel', 'child_id', 'parent_id', 'Parents'),
256 'child_id': fields.many2many('account.account', 'account_account_rel', 'parent_id', 'child_id', 'Children'),
257 'balance': fields.function(_balance, digits=(16,2), method=True, string='Balance'),
258 'credit': fields.function(_credit, digits=(16,2), method=True, string='Credit'),
259 'debit': fields.function(_debit, digits=(16,2), method=True, string='Debit'),
260 'reconcile': fields.boolean('Reconcile', help="Check this account if the user can make a reconciliation of the entries in this account."),
261 'shortcut': fields.char('Shortcut', size=12),
262 'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True, help="Tell Tiny ERP how to process the entries of this account when you close a fiscal year. None removes all entries to start with an empty account for the new fiscal year. Balance creates only one entry to keep the balance for the new fiscal year. Detail keeps the detail of all entries of the preceeding years. Unreconciled keeps the detail of unreconciled entries only."),
263 'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel',
264 'account_id','tax_id', 'Default Taxes'),
265 'note': fields.text('Note'),
266 'company_currency_id': fields.function(_get_company_currency, method=True, type='many2one', relation='res.currency', string='Company Currency'),
267 'company_id': fields.many2one('res.company', 'Company', required=True),
268 'active': fields.boolean('Active', select=2),
271 def _default_company(self, cr, uid, context={}):
272 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
274 return user.company_id.id
275 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
278 'sign': lambda *a: 1,
279 'type': lambda *a: 'view',
280 'reconcile': lambda *a: False,
281 'close_method': lambda *a: 'balance',
282 'company_id': _default_company,
283 'active': lambda *a: True,
286 def _check_recursion(self, cr, uid, ids):
289 cr.execute('select distinct parent_id from account_account_rel where child_id in ('+','.join(map(str,ids))+')')
290 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
297 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
299 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
307 if name and str(name).startswith('partner:'):
308 part_id = int(name.split(':')[1])
309 part = self.pool.get('res.partner').browse(cr, user, part_id, context)
310 args += [('id','in', (part.property_account_payable.id, part.property_account_receivable.id))]
312 if name and str(name).startswith('type:'):
313 type = name.split(':')[1]
314 args += [('type','=', type)]
319 ids = self.search(cr, user, [('code','=like',name+"%")]+ args, limit=limit)
321 ids = self.search(cr, user, [('shortcut','=',name)]+ args, limit=limit)
323 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
325 ids = self.search(cr, user, args, context=context, limit=limit)
326 return self.name_get(cr, user, ids, context=context)
328 def name_get(self, cr, uid, ids, context={}):
331 reads = self.read(cr, uid, ids, ['name','code'], context)
334 name = record['name']
336 name = record['code']+' - '+name
337 res.append((record['id'],name ))
340 def copy(self, cr, uid, id, default=None, context={}):
341 account = self.browse(cr, uid, id, context=context)
343 default['parent_id'] = False
345 for child in account.child_id:
346 new_child_ids.append(self.copy(cr, uid, child.id, default, context=context))
347 default['child_id'] = [(6, 0, new_child_ids)]
349 default['child_id'] = False
350 return super(account_account, self).copy(cr, uid, id, default, context=context)
352 def write(self, cr, uid, ids, vals, context=None):
355 if 'active' in vals and not vals['active']:
356 line_obj = self.pool.get('account.move.line')
357 account_ids = self.search(cr, uid, [('parent_id', 'child_of', ids)])
358 if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]):
361 return super(account_account, self).write(cr, uid, ids, vals, context=context)
364 class account_journal_view(osv.osv):
365 _name = "account.journal.view"
366 _description = "Journal View"
368 'name': fields.char('Journal View', size=64, required=True),
369 'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
372 account_journal_view()
375 class account_journal_column(osv.osv):
376 def _col_get(self, cr, user, context={}):
378 cols = self.pool.get('account.move.line')._columns
380 result.append( (col, cols[col].string) )
383 _name = "account.journal.column"
384 _description = "Journal Column"
386 'name': fields.char('Column Name', size=64, required=True),
387 'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
388 'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
389 'sequence': fields.integer('Sequence'),
390 'required': fields.boolean('Required'),
391 'readonly': fields.boolean('Readonly'),
394 account_journal_column()
396 class account_journal(osv.osv):
397 _name = "account.journal"
398 _description = "Journal"
400 'name': fields.char('Journal Name', size=64, required=True, translate=True),
401 'code': fields.char('Code', size=16),
402 'type': fields.selection([('sale','Sale'), ('purchase','Purchase'), ('cash','Cash'), ('general','General'), ('situation','Situation')], 'Type', size=32, required=True),
404 'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view'), ('code', '<>', 'closed')]),
405 'account_control_ids': fields.many2many('account.account', 'account_account_type_rel', 'journal_id','account_id', 'Account', domain=[('type','<>','view'), ('type', '<>', 'closed')]),
407 'active': fields.boolean('Active'),
408 '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 Tiny 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."),
409 'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account'),
410 'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account'),
411 '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."),
412 'update_posted': fields.boolean('Allow Cancelling Entries'),
413 'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals", required=True),
414 'user_id': fields.many2one('res.users', 'User', help="The responsible user of this journal"),
415 'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
416 'currency': fields.many2one('res.currency', 'Currency', help='The currency used to enter statement'),
419 'active': lambda *a: 1,
420 'user_id': lambda self,cr,uid,context: uid,
422 def create(self, cr, uid, vals, context={}):
423 journal_id = super(osv.osv, self).create(cr, uid, vals, context)
424 # journal_name = self.browse(cr, uid, [journal_id])[0].code
425 # periods = self.pool.get('account.period')
426 # ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
427 # for period in periods.browse(cr, uid, ids):
428 # self.pool.get('account.journal.period').create(cr, uid, {
429 # 'name': (journal_name or '')+':'+(period.code or ''),
430 # 'journal_id': journal_id,
431 # 'period_id': period.id
434 def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
441 ids = self.search(cr, user, [('code','ilike',name)]+ args, limit=limit)
443 ids = self.search(cr, user, [('name',operator,name)]+ args, limit=limit)
444 return self.name_get(cr, user, ids, context=context)
447 class account_fiscalyear(osv.osv):
448 _name = "account.fiscalyear"
449 _description = "Fiscal Year"
451 'name': fields.char('Fiscal Year', size=64, required=True),
452 'code': fields.char('Code', size=6, required=True),
453 'date_start': fields.date('Start date', required=True),
454 'date_stop': fields.date('End date', required=True),
455 'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
456 'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', redonly=True),
460 'state': lambda *a: 'draft',
462 _order = "date_start"
463 def create_period3(self,cr, uid, ids, context={}):
464 return self.create_period(cr, uid, ids, context, 3)
466 def create_period(self,cr, uid, ids, context={}, interval=1):
467 for fy in self.browse(cr, uid, ids, context):
469 ds = mx.DateTime.strptime(fy.date_start, '%Y-%m-%d')
470 while ds.strftime('%Y-%m-%d')<fy.date_stop:
471 de = ds + RelativeDateTime(months=interval, days=-1)
472 self.pool.get('account.period').create(cr, uid, {
473 'name': ds.strftime('%d/%m') + ' - '+de.strftime('%d/%m'),
474 'code': ds.strftime('%d/%m') + '-'+de.strftime('%d/%m'),
475 'date_start': ds.strftime('%Y-%m-%d'),
476 'date_stop': de.strftime('%Y-%m-%d'),
477 'fiscalyear_id': fy.id,
479 ds = ds + RelativeDateTime(months=interval)
482 def find(self, cr, uid, dt=None, exception=True, context={}):
484 dt = time.strftime('%Y-%m-%d')
485 ids = self.search(cr, uid, [('date_start', '<=', dt), ('date_stop', '>=', dt)])
488 raise osv.except_osv(_('Error !'), _('No fiscal year defined for this date !\nPlease create one.'))
494 class account_period(osv.osv):
495 _name = "account.period"
496 _description = "Account period"
498 'name': fields.char('Period Name', size=64, required=True),
499 'code': fields.char('Code', size=12),
500 'date_start': fields.date('Start of period', required=True, states={'done':[('readonly',True)]}),
501 'date_stop': fields.date('End of period', required=True, states={'done':[('readonly',True)]}),
502 'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
503 'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', readonly=True)
506 'state': lambda *a: 'draft',
508 _order = "date_start"
509 def next(self, cr, uid, period, step, context={}):
510 ids = self.search(cr, uid, [('date_start','>',period.date_start)])
515 def find(self, cr, uid, dt=None, context={}):
517 dt = time.strftime('%Y-%m-%d')
518 #CHECKME: shouldn't we check the state of the period?
519 ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
521 raise osv.except_osv(_('Error !'), _('No period defined for this date !\nPlease create a fiscal year.'))
525 class account_journal_period(osv.osv):
526 _name = "account.journal.period"
527 _description = "Journal - Period"
529 def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
530 result = {}.fromkeys(ids, 'STOCK_NEW')
531 for r in self.read(cr, uid, ids, ['state']):
533 'draft': 'STOCK_NEW',
534 'printed': 'STOCK_PRINT_PREVIEW',
535 'done': 'STOCK_DIALOG_AUTHENTICATION',
536 }.get(r['state'], 'STOCK_NEW')
540 'name': fields.char('Journal-Period Name', size=64, required=True),
541 'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
542 'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
543 'icon': fields.function(_icon_get, method=True, string='Icon', type='string'),
544 'active': fields.boolean('Active', required=True),
545 'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True)
548 def _check(self, cr, uid, ids, context={}):
549 for obj in self.browse(cr, uid, ids, context):
550 cr.execute('select * from account_move_line where journal_id=%d and period_id=%d limit 1', (obj.journal_id.id, obj.period_id.id))
553 raise osv.except_osv(_('Error !'), _('You can not modify/delete a journal with entries for this period !'))
556 def write(self, cr, uid, ids, vals, context={}):
557 self._check(cr, uid, ids, context)
558 return super(account_journal_period, self).write(cr, uid, ids, vals, context)
560 def create(self, cr, uid, vals, context={}):
561 period_id=vals.get('period_id',False)
563 period = self.pool.get('account.period').browse(cr, uid,period_id)
564 vals['state']=period.state
565 return super(account_journal_period, self).create(cr, uid, vals, context)
567 def unlink(self, cr, uid, ids, context={}):
568 self._check(cr, uid, ids, context)
569 return super(account_journal_period, self).unlink(cr, uid, ids, context)
572 'state': lambda *a: 'draft',
573 'active': lambda *a: True,
577 account_journal_period()
579 class account_fiscalyear(osv.osv):
580 _inherit = "account.fiscalyear"
581 _description = "Fiscal Year"
583 'start_journal_period_id':fields.many2one('account.journal.period','New Entries Journal'),
584 'end_journal_period_id':fields.many2one('account.journal.period','End of Year Entries Journal', readonly=True),
588 #----------------------------------------------------------
590 #----------------------------------------------------------
591 class account_move(osv.osv):
592 _name = "account.move"
593 _description = "Account Entry"
595 def name_get(self, cursor, user, ids, context=None):
599 data_move = self.pool.get('account.move').browse(cursor,user,ids)
600 for move in data_move:
601 if move.state=='draft':
602 name = '*' + move.name
605 res.append((move.id, name))
609 def _get_period(self, cr, uid, context):
610 periods = self.pool.get('account.period').find(cr, uid)
616 'name': fields.char('Entry Name', size=64, required=True),
617 'ref': fields.char('Ref', size=64),
618 'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
619 'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}),
620 'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'State', required=True, readonly=True),
621 'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
624 'state': lambda *a: 'draft',
625 'period_id': _get_period,
628 def _check_centralisation(self, cursor, user, ids):
629 for move in self.browse(cursor, user, ids):
630 if move.journal_id.centralisation:
631 move_ids = self.search(cursor, user, [
632 ('period_id', '=', move.period_id.id),
633 ('journal_id', '=', move.journal_id.id),
635 if len(move_ids) > 1:
639 def _check_period_journal(self, cursor, user, ids):
640 for move in self.browse(cursor, user, ids):
641 for line in move.line_id:
642 if line.period_id.id != move.period_id.id:
644 if line.journal_id.id != move.journal_id.id:
649 (_check_centralisation,
650 'You can not create more than one move per period on centralized journal',
652 (_check_period_journal,
653 'You can not create entries on different period/journal in the same move',
656 def post(self, cr, uid, ids, context=None):
657 if self.validate(cr, uid, ids, context) and len(ids):
658 cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('posted',))
660 raise osv.except_osv(_('Integrity Error !'), _('You can not validate a non balanced entry !'))
663 def button_validate(self, cursor, user, ids, context=None):
664 return self.post(cursor, user, ids, context=context)
666 def button_cancel(self, cr, uid, ids, context={}):
667 for line in self.browse(cr, uid, ids, context):
668 if not line.journal_id.update_posted:
669 raise osv.except_osv(_('Error !'), _('You can not modify a posted entry of this journal !'))
671 cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('draft',))
674 def write(self, cr, uid, ids, vals, context={}):
676 c['novalidate'] = True
677 result = super(osv.osv, self).write(cr, uid, ids, vals, c)
678 self.validate(cr, uid, ids, context)
682 # TODO: Check if period is closed !
684 def create(self, cr, uid, vals, context={}):
685 if 'line_id' in vals:
686 if 'journal_id' in vals:
687 for l in vals['line_id']:
689 l[2]['journal_id'] = vals['journal_id']
690 context['journal_id'] = vals['journal_id']
691 if 'period_id' in vals:
692 for l in vals['line_id']:
694 l[2]['period_id'] = vals['period_id']
695 context['period_id'] = vals['period_id']
697 default_period = self._get_period(cr, uid, context)
698 for l in vals['line_id']:
700 l[2]['period_id'] = default_period
701 context['period_id'] = default_period
703 if not 'name' in vals:
704 journal = self.pool.get('account.journal').browse(cr, uid, context.get('journal_id', vals.get('journal_id', False)))
705 if journal.sequence_id:
706 vals['name'] = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
708 raise osv.except_osv(_('Error'), _('No sequence defined in the journal !'))
709 if 'line_id' in vals:
711 c['novalidate'] = True
712 result = super(account_move, self).create(cr, uid, vals, c)
713 self.validate(cr, uid, [result], context)
715 result = super(account_move, self).create(cr, uid, vals, context)
718 def unlink(self, cr, uid, ids, context={}, check=True):
720 for move in self.browse(cr, uid, ids, context):
721 if move['state'] <> 'draft':
722 raise osv.except_osv(_('UserError'),
723 _('You can not delete posted movement: "%s"!') % \
725 line_ids = map(lambda x: x.id, move.line_id)
726 context['journal_id'] = move.journal_id.id
727 context['period_id'] = move.period_id.id
728 self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
729 toremove.append(move.id)
730 result = super(account_move, self).unlink(cr, uid, toremove, context)
733 def _compute_balance(self, cr, uid, id, context={}):
734 move = self.browse(cr, uid, [id])[0]
736 for line in move.line_id:
737 amount+= (line.debit - line.credit)
740 def _centralise(self, cr, uid, move, mode):
742 account_id = move.journal_id.default_debit_account_id.id
745 raise osv.except_osv(_('UserError'),
746 _('There is no default default debit account defined \n' \
747 'on journal "%s"') % move.journal_id.name)
749 account_id = move.journal_id.default_credit_account_id.id
752 raise osv.except_osv(_('UserError'),
753 _('There is no default default credit account defined \n' \
754 'on journal "%s"') % move.journal_id.name)
756 # find the first line of this move with the current mode
757 # or create it if it doesn't exist
758 cr.execute('select id from account_move_line where move_id=%d and centralisation=%s limit 1', (move.id, mode))
763 line_id = self.pool.get('account.move.line').create(cr, uid, {
764 'name': 'Centralisation '+mode,
765 'centralisation': mode,
766 'account_id': account_id,
768 'journal_id': move.journal_id.id,
769 'period_id': move.period_id.id,
770 'date': move.period_id.date_stop,
773 }, {'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
775 # find the first line of this move with the other mode
776 # so that we can exclude it from our calculation
777 cr.execute('select id from account_move_line where move_id=%d and centralisation=%s limit 1', (move.id, mode2))
784 cr.execute('select sum('+mode+') from account_move_line where move_id=%d and id<>%d', (move.id, line_id2))
785 result = cr.fetchone()[0] or 0.0
786 cr.execute('update account_move_line set '+mode2+'=%f where id=%d', (result, line_id))
790 # Validate a balanced move. If it is a centralised journal, create a move.
792 def validate(self, cr, uid, ids, context={}):
794 for move in self.browse(cr, uid, ids, context):
795 journal = move.journal_id
800 for line in move.line_id:
801 amount += line.debit - line.credit
802 line_ids.append(line.id)
803 if line.state=='draft':
804 line_draft_ids.append(line.id)
807 company_id = line.account_id.company_id.id
808 if not company_id == line.account_id.company_id.id:
809 raise osv.except_osv(_('Error'), _('Couldn\'t create move between different companies'))
811 if line.account_id.currency_id:
812 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):
813 raise osv.except_osv(_('Error'), _('Couldn\'t create move with currency different than the secondary currency of the account'))
815 if abs(amount) < 0.0001:
816 if not len(line_draft_ids):
818 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
819 'journal_id': move.journal_id.id,
820 'period_id': move.period_id.id,
822 }, context, check=False)
826 if journal.type not in ('purchase','sale'):
829 for line in move.line_id:
830 if move.journal_id.type == 'sale':
833 key = 'account_paid_id'
836 key = 'account_collected_id'
840 key = 'account_collected_id'
843 key = 'account_paid_id'
844 if line.account_id.tax_ids:
845 code = amount = False
846 for tax in line.account_id.tax_ids:
848 acc = getattr(tax, key).id
849 account[acc] = (getattr(tax,
850 field_base + 'tax_code_id').id,
851 getattr(tax, field_base + 'tax_sign'))
852 account2[(acc,getattr(tax,
853 field_base + 'tax_code_id').id)] = (getattr(tax,
854 field_base + 'tax_code_id').id,
855 getattr(tax, field_base + 'tax_sign'))
856 code = getattr(tax, field_base + 'base_code_id').id
857 amount = getattr(tax, field_base+'base_sign') * \
858 (line.debit + line.credit)
860 if code and not (line.tax_code_id or line.tax_amount):
861 self.pool.get('account.move.line').write(cr, uid,
865 }, context=context, check=False)
870 key = (line.account_id.id, line.tax_code_id.id)
872 code = account2[key][0]
873 amount = account2[key][1] * (line.debit + line.credit)
874 elif line.account_id.id in account:
875 code = account[line.account_id.id][0]
876 amount = account[line.account_id.id][1] * (line.debit + line.credit)
877 if (code or amount) and not (line.tax_code_id or line.tax_amount):
878 self.pool.get('account.move.line').write(cr, uid, [line.id], {
881 }, context, check=False)
886 if journal.centralisation:
887 self._centralise(cr, uid, move, 'debit')
888 self._centralise(cr, uid, move, 'credit')
889 self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
891 }, context, check=False)
894 self.pool.get('account.move.line').write(cr, uid, line_ids, {
895 'journal_id': move.journal_id.id,
896 'period_id': move.period_id.id,
897 #'tax_code_id': False,
898 #'tax_amount': False,
900 }, context, check=False)
905 class account_move_reconcile(osv.osv):
906 _name = "account.move.reconcile"
907 _description = "Account Reconciliation"
909 'name': fields.char('Name', size=64, required=True),
910 'type': fields.char('Type', size=16, required=True),
911 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry lines'),
912 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'),
913 'create_date': fields.date('Creation date', readonly=True),
916 'name': lambda self,cr,uid,ctx={}: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile') or '/',
918 def reconcile_partial_check(self, cr, uid, ids, type='auto', context={}):
919 for rec in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
921 for line in rec.line_partial_ids:
922 total += (line.debit or 0.0) - (line.credit or 0.0)
924 self.write(cr,uid, map(lambda x: x.id, rec.line_partial_ids), {'reconcile_id': rec.id })
925 for line in rec.line_partial_ids:
926 total += (line.debit or 0.0) - (line.credit or 0.0)
928 def name_get(self, cr, uid, ids, context=None):
930 for r in self.browse(cr, uid, ids, context):
931 total = reduce(lambda y,t: (t.debit or 0.0) - (t.credit or 0.0) + y, r.line_partial_ids, 0.0)
933 result[r.id] = '%s (%.2f)' % (r.name, total)
935 result[r.id] = r.name
937 account_move_reconcile()
939 #----------------------------------------------------------
941 #----------------------------------------------------------
944 child_depend: la taxe depend des taxes filles
946 class account_tax_code(osv.osv):
948 A code for the tax object.
950 This code is used for some tax declarations.
952 def _sum(self, cr, uid, ids, name, args, context, where =''):
953 ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
954 acc_set = ",".join(map(str, ids2))
955 if context.get('based_on', 'invoices') == 'payments':
956 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
957 FROM account_move_line AS line, \
958 account_move AS move \
959 LEFT JOIN account_invoice invoice ON \
960 (invoice.move_id = move.id) \
961 WHERE line.tax_code_id in ('+acc_set+') '+where+' \
962 AND move.id = line.move_id \
963 AND ((invoice.state = \'paid\') \
964 OR (invoice.id IS NULL)) \
965 GROUP BY line.tax_code_id')
967 cr.execute('SELECT line.tax_code_id, sum(line.tax_amount) \
968 FROM account_move_line AS line \
969 WHERE line.tax_code_id in ('+acc_set+') '+where+' \
970 GROUP BY line.tax_code_id')
971 res=dict(cr.fetchall())
972 for record in self.browse(cr, uid, ids, context):
973 def _rec_get(record):
974 amount = res.get(record.id, 0.0)
975 for rec in record.child_ids:
976 amount += _rec_get(rec) * rec.sign
978 res[record.id] = round(_rec_get(record), 2)
981 def _sum_period(self, cr, uid, ids, name, args, context):
982 if not 'period_id' in context:
983 period_id = self.pool.get('account.period').find(cr, uid)
984 if not len(period_id):
985 return dict.fromkeys(ids, 0.0)
986 period_id = period_id[0]
988 period_id = context['period_id']
989 return self._sum(cr, uid, ids, name, args, context,
990 where=' and line.period_id='+str(period_id))
992 _name = 'account.tax.code'
993 _description = 'Tax Code'
995 'name': fields.char('Tax Case Name', size=64, required=True),
996 'code': fields.char('Case Code', size=16),
997 'info': fields.text('Description'),
998 'sum': fields.function(_sum, method=True, string="Year Sum"),
999 'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
1000 'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
1001 'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Childs Codes'),
1002 'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines'),
1003 'company_id': fields.many2one('res.company', 'Company', required=True),
1004 'sign': fields.float('Sign for parent', required=True),
1007 def name_get(self, cr, uid, ids, context=None):
1010 if isinstance(ids, (int, long)):
1012 reads = self.read(cr, uid, ids, ['name','code'], context, load='_classic_write')
1013 return [(x['id'], (x['code'] and x['code'] + ' - ' or '') + x['name']) \
1016 def _default_company(self, cr, uid, context={}):
1017 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1019 return user.company_id.id
1020 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1022 'company_id': _default_company,
1023 'sign': lambda *args: 1.0,
1025 def _check_recursion(self, cr, uid, ids):
1028 cr.execute('select distinct parent_id from account_tax_code where id in ('+','.join(map(str,ids))+')')
1029 ids = filter(None, map(lambda x:x[0], cr.fetchall()))
1036 (_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
1038 _order = 'code,name'
1041 class account_tax(osv.osv):
1045 Type: percent, fixed, none, code
1046 PERCENT: tax = price * amount
1047 FIXED: tax = price + amount
1049 CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1050 return result in the context
1051 Ex: result=round(price_unit*0.21,4)
1053 _name = 'account.tax'
1054 _description = 'Tax'
1056 'name': fields.char('Tax Name', size=64, required=True),
1057 '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."),
1058 'amount': fields.float('Amount', required=True, digits=(14,4)),
1059 'active': fields.boolean('Active'),
1060 'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
1061 'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True),
1062 '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."),
1063 'account_collected_id':fields.many2one('account.account', 'Invoice Tax Account'),
1064 'account_paid_id':fields.many2one('account.account', 'Refund Tax Account'),
1065 'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1066 'child_ids':fields.one2many('account.tax', 'parent_id', 'Childs Tax Account'),
1067 '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."),
1068 'python_compute':fields.text('Python Code'),
1069 'python_compute_inv':fields.text('Python Code (reverse)'),
1070 'python_applicable':fields.text('Python Code'),
1071 '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."),
1074 # Fields used for the VAT declaration
1076 'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1077 'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1078 'base_sign': fields.float('Base Code Sign', help="Usualy 1 or -1."),
1079 'tax_sign': fields.float('Tax Code Sign', help="Usualy 1 or -1."),
1081 # Same fields for refund invoices
1083 'ref_base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1084 'ref_tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1085 'ref_base_sign': fields.float('Base Code Sign', help="Usualy 1 or -1."),
1086 'ref_tax_sign': fields.float('Tax Code Sign', help="Usualy 1 or -1."),
1087 '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"),
1088 'company_id': fields.many2one('res.company', 'Company', required=True),
1091 def _default_company(self, cr, uid, context={}):
1092 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1094 return user.company_id.id
1095 return self.pool.get('res.company').search(cr, uid, [('parent_id', '=', False)])[0]
1097 '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''',
1098 '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''',
1099 'applicable_type': lambda *a: 'true',
1100 'type': lambda *a: 'percent',
1101 'amount': lambda *a: 0,
1102 'active': lambda *a: 1,
1103 'sequence': lambda *a: 1,
1104 'tax_group': lambda *a: 'vat',
1105 'ref_tax_sign': lambda *a: 1,
1106 'ref_base_sign': lambda *a: 1,
1107 'tax_sign': lambda *a: 1,
1108 'base_sign': lambda *a: 1,
1109 'include_base_amount': lambda *a: False,
1110 'company_id': _default_company,
1114 def _applicable(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1117 if tax.applicable_type=='code':
1118 localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id), 'product':product, 'partner':partner}
1119 exec tax.python_applicable in localdict
1120 if localdict.get('result', False):
1126 def _unit_compute(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1127 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1130 cur_price_unit=price_unit
1132 # we compute the amount for the current tax object and append it to the result
1134 if tax.type=='percent':
1135 amount = cur_price_unit * tax.amount
1136 res.append({'id':tax.id,
1139 'account_collected_id':tax.account_collected_id.id,
1140 'account_paid_id':tax.account_paid_id.id,
1141 'base_code_id': tax.base_code_id.id,
1142 'ref_base_code_id': tax.ref_base_code_id.id,
1143 'sequence': tax.sequence,
1144 'base_sign': tax.base_sign,
1145 'tax_sign': tax.tax_sign,
1146 'ref_base_sign': tax.ref_base_sign,
1147 'ref_tax_sign': tax.ref_tax_sign,
1148 'price_unit': cur_price_unit,
1149 'tax_code_id': tax.tax_code_id.id,
1150 'ref_tax_code_id': tax.ref_tax_code_id.id,
1153 elif tax.type=='fixed':
1154 res.append({'id':tax.id,
1156 'amount':tax.amount,
1157 'account_collected_id':tax.account_collected_id.id,
1158 'account_paid_id':tax.account_paid_id.id,
1159 'base_code_id': tax.base_code_id.id,
1160 'ref_base_code_id': tax.ref_base_code_id.id,
1161 'sequence': tax.sequence,
1162 'base_sign': tax.base_sign,
1163 'tax_sign': tax.tax_sign,
1164 'ref_base_sign': tax.ref_base_sign,
1165 'ref_tax_sign': tax.ref_tax_sign,
1167 'tax_code_id': tax.tax_code_id.id,
1168 'ref_tax_code_id': tax.ref_tax_code_id.id,})
1169 elif tax.type=='code':
1170 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1171 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1172 exec tax.python_compute in localdict
1173 amount = localdict['result']
1178 'account_collected_id': tax.account_collected_id.id,
1179 'account_paid_id': tax.account_paid_id.id,
1180 'base_code_id': tax.base_code_id.id,
1181 'ref_base_code_id': tax.ref_base_code_id.id,
1182 'sequence': tax.sequence,
1183 'base_sign': tax.base_sign,
1184 'tax_sign': tax.tax_sign,
1185 'ref_base_sign': tax.ref_base_sign,
1186 'ref_tax_sign': tax.ref_tax_sign,
1187 'price_unit': cur_price_unit,
1188 'tax_code_id': tax.tax_code_id.id,
1189 'ref_tax_code_id': tax.ref_tax_code_id.id,
1191 amount2 = res[-1]['amount']
1192 if len(tax.child_ids):
1193 if tax.child_depend:
1196 child_tax = self._unit_compute(cr, uid, tax.child_ids, amount, address_id, product, partner)
1197 res.extend(child_tax)
1198 if tax.include_base_amount:
1199 cur_price_unit+=amount2
1202 def compute(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1205 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1209 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1210 one tax for each tax id in IDS and their childs
1212 res = self._unit_compute(cr, uid, taxes, price_unit, address_id, product, partner)
1214 r['amount'] *= quantity
1217 def _unit_compute_inv(self, cr, uid, taxes, price_unit, address_id=None, product=None, partner=None):
1218 taxes = self._applicable(cr, uid, taxes, price_unit, address_id, product, partner)
1222 cur_price_unit=price_unit
1224 # we compute the amount for the current tax object and append it to the result
1226 if tax.type=='percent':
1227 amount = cur_price_unit - (cur_price_unit / (1 + tax.amount))
1228 res.append({'id':tax.id,
1231 'account_collected_id':tax.account_collected_id.id,
1232 'account_paid_id':tax.account_paid_id.id,
1233 'base_code_id': tax.base_code_id.id,
1234 'ref_base_code_id': tax.ref_base_code_id.id,
1235 'sequence': tax.sequence,
1236 'base_sign': tax.base_sign,
1237 'tax_sign': tax.tax_sign,
1238 'ref_base_sign': tax.ref_base_sign,
1239 'ref_tax_sign': tax.ref_tax_sign,
1240 'price_unit': cur_price_unit - amount,
1241 'tax_code_id': tax.tax_code_id.id,
1242 'ref_tax_code_id': tax.ref_tax_code_id.id,})
1244 elif tax.type=='fixed':
1245 res.append({'id':tax.id,
1247 'amount':tax.amount,
1248 'account_collected_id':tax.account_collected_id.id,
1249 'account_paid_id':tax.account_paid_id.id,
1250 'base_code_id': tax.base_code_id.id,
1251 'ref_base_code_id': tax.ref_base_code_id.id,
1252 'sequence': tax.sequence,
1253 'base_sign': tax.base_sign,
1254 'tax_sign': tax.tax_sign,
1255 'ref_base_sign': tax.ref_base_sign,
1256 'ref_tax_sign': tax.ref_tax_sign,
1258 'tax_code_id': tax.tax_code_id.id,
1259 'ref_tax_code_id': tax.ref_tax_code_id.id,})
1261 elif tax.type=='code':
1262 address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1263 localdict = {'price_unit':cur_price_unit, 'address':address, 'product':product, 'partner':partner}
1264 exec tax.python_compute_inv in localdict
1265 amount = localdict['result']
1270 'account_collected_id': tax.account_collected_id.id,
1271 'account_paid_id': tax.account_paid_id.id,
1272 'base_code_id': tax.base_code_id.id,
1273 'ref_base_code_id': tax.ref_base_code_id.id,
1274 'sequence': tax.sequence,
1275 'base_sign': tax.base_sign,
1276 'tax_sign': tax.tax_sign,
1277 'ref_base_sign': tax.ref_base_sign,
1278 'ref_tax_sign': tax.ref_tax_sign,
1279 'price_unit': cur_price_unit - amount,
1280 'tax_code_id': tax.tax_code_id.id,
1281 'ref_tax_code_id': tax.ref_tax_code_id.id,
1284 amount2 = res[-1]['amount']
1285 if len(tax.child_ids):
1286 if tax.child_depend:
1291 for t in tax.child_ids:
1292 parent_tax = self._unit_compute_inv(cr, uid, [t], amount, address_id, product, partner)
1293 res.extend(parent_tax)
1294 if tax.include_base_amount:
1295 cur_price_unit-=amount
1299 def compute_inv(self, cr, uid, taxes, price_unit, quantity, address_id=None, product=None, partner=None):
1301 Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1302 Price Unit is a VAT included price
1306 tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1307 one tax for each tax id in IDS and their childs
1309 res = self._unit_compute_inv(cr, uid, taxes, price_unit, address_id, product, partner=None)
1311 r['amount'] *= quantity
1315 # ---------------------------------------------------------
1317 # ---------------------------------------------------------
1319 class account_budget_post(osv.osv):
1320 _name = 'account.budget.post'
1321 _description = 'Budget item'
1323 'code': fields.char('Code', size=64, required=True),
1324 'name': fields.char('Name', size=256, required=True),
1325 'dotation_ids': fields.one2many('account.budget.post.dotation', 'post_id', 'Expenses'),
1326 'account_ids': fields.many2many('account.account', 'account_budget_rel', 'budget_id', 'account_id', 'Accounts'),
1331 def spread(self, cr, uid, ids, fiscalyear_id=False, amount=0.0):
1332 dobj = self.pool.get('account.budget.post.dotation')
1333 for o in self.browse(cr, uid, ids):
1334 # delete dotations for this post
1335 dobj.unlink(cr, uid, dobj.search(cr, uid, [('post_id','=',o.id)]))
1337 # create one dotation per period in the fiscal year, and spread the total amount/quantity over those dotations
1338 fy = self.pool.get('account.fiscalyear').browse(cr, uid, [fiscalyear_id])[0]
1339 num = len(fy.period_ids)
1340 for p in fy.period_ids:
1341 dobj.create(cr, uid, {'post_id': o.id, 'period_id': p.id, 'amount': amount/num})
1343 account_budget_post()
1345 class account_budget_post_dotation(osv.osv):
1346 _name = 'account.budget.post.dotation'
1347 _description = "Budget item endowment"
1349 'name': fields.char('Name', size=64),
1350 'post_id': fields.many2one('account.budget.post', 'Item', select=True),
1351 'period_id': fields.many2one('account.period', 'Period'),
1352 # 'quantity': fields.float('Quantity', digits=(16,2)),
1353 'amount': fields.float('Amount', digits=(16,2)),
1355 account_budget_post_dotation()
1358 # ---------------------------------------------------------
1359 # Account Entries Models
1360 # ---------------------------------------------------------
1362 class account_model(osv.osv):
1363 _name = "account.model"
1364 _description = "Account Model"
1366 'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
1367 'ref': fields.char('Ref', size=64),
1368 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
1369 'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
1371 def generate(self, cr, uid, ids, datas={}, context={}):
1373 for model in self.browse(cr, uid, ids, context):
1374 period_id = self.pool.get('account.period').find(cr,uid, context=context)
1376 raise osv.except_osv(_('No period found !'), _('Unable to find a valid period !'))
1377 period_id = period_id[0]
1379 if model.journal_id.sequence_id:
1380 name = self.pool.get('ir.sequence').get_id(cr, uid, model.journal_id.sequence_id.id)
1381 move_id = self.pool.get('account.move').create(cr, uid, {
1384 'period_id': period_id,
1385 'journal_id': model.journal_id.id,
1387 move_ids.append(move_id)
1388 for line in model.lines_id:
1391 'journal_id': model.journal_id.id,
1392 'period_id': period_id
1396 'quantity': line.quantity,
1397 'debit': line.debit,
1398 'credit': line.credit,
1399 'account_id': line.account_id.id,
1402 'partner_id': line.partner_id.id,
1403 'date': time.strftime('%Y-%m-%d'),
1404 'date_maturity': time.strftime('%Y-%m-%d')
1407 c.update({'journal_id': model.journal_id.id,'period_id': period_id})
1408 self.pool.get('account.move.line').create(cr, uid, val, context=c)
1412 class account_model_line(osv.osv):
1413 _name = "account.model.line"
1414 _description = "Account Model Entries"
1416 'name': fields.char('Name', size=64, required=True),
1417 'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from the lowest sequences to the higher ones"),
1418 'quantity': fields.float('Quantity', digits=(16,2), help="The optionnal quantity on entries"),
1419 'debit': fields.float('Debit', digits=(16,2)),
1420 'credit': fields.float('Credit', digits=(16,2)),
1422 'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
1424 'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
1426 'ref': fields.char('Ref.', size=16),
1428 'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optionnal other currency."),
1429 'currency_id': fields.many2one('res.currency', 'Currency'),
1431 'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
1432 '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."),
1433 'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
1436 'date': lambda *a: 'today'
1439 _sql_constraints = [
1440 ('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model !'),
1441 ('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model !'),
1443 account_model_line()
1445 # ---------------------------------------------------------
1446 # Account Subscription
1447 # ---------------------------------------------------------
1450 class account_subscription(osv.osv):
1451 _name = "account.subscription"
1452 _description = "Account Subscription"
1454 'name': fields.char('Name', size=64, required=True),
1455 'ref': fields.char('Ref.', size=16),
1456 'model_id': fields.many2one('account.model', 'Model', required=True),
1458 'date_start': fields.date('Starting date', required=True),
1459 'period_total': fields.integer('Number of period', required=True),
1460 'period_nbr': fields.integer('Period', required=True),
1461 'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
1462 'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
1464 'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
1467 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
1468 'period_type': lambda *a: 'month',
1469 'period_total': lambda *a: 12,
1470 'period_nbr': lambda *a: 1,
1471 'state': lambda *a: 'draft',
1473 def state_draft(self, cr, uid, ids, context={}):
1474 self.write(cr, uid, ids, {'state':'draft'})
1477 def check(self, cr, uid, ids, context={}):
1479 for sub in self.browse(cr, uid, ids, context):
1481 for line in sub.lines_id:
1482 if not line.move_id.id:
1486 todone.append(sub.id)
1488 self.write(cr, uid, todone, {'state':'done'})
1491 def remove_line(self, cr, uid, ids, context={}):
1493 for sub in self.browse(cr, uid, ids, context):
1494 for line in sub.lines_id:
1495 if not line.move_id.id:
1496 toremove.append(line.id)
1498 self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
1499 self.write(cr, uid, ids, {'state':'draft'})
1502 def compute(self, cr, uid, ids, context={}):
1503 for sub in self.browse(cr, uid, ids, context):
1505 for i in range(sub.period_total):
1506 self.pool.get('account.subscription.line').create(cr, uid, {
1508 'subscription_id': sub.id,
1510 if sub.period_type=='day':
1511 ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(days=sub.period_nbr)).strftime('%Y-%m-%d')
1512 if sub.period_type=='month':
1513 ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(months=sub.period_nbr)).strftime('%Y-%m-%d')
1514 if sub.period_type=='year':
1515 ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(years=sub.period_nbr)).strftime('%Y-%m-%d')
1516 self.write(cr, uid, ids, {'state':'running'})
1518 account_subscription()
1520 class account_subscription_line(osv.osv):
1521 _name = "account.subscription.line"
1522 _description = "Account Subscription Line"
1524 'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
1525 'date': fields.date('Date', required=True),
1526 'move_id': fields.many2one('account.move', 'Entry'),
1530 def move_create(self, cr, uid, ids, context={}):
1532 for line in self.browse(cr, uid, ids, context):
1536 ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
1537 tocheck[line.subscription_id.id] = True
1538 self.write(cr, uid, [line.id], {'move_id':ids[0]})
1540 self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
1543 account_subscription_line()
1546 class account_config_fiscalyear(osv.osv_memory):
1547 _name = 'account.config.fiscalyear'
1549 'name':fields.char('Name', required=True,size=64),
1550 'code':fields.char('Code', required=True,size=64),
1551 'date1': fields.date('Start of period', required=True),
1552 'date2': fields.date('End of period', required=True),
1555 'date1': lambda *a: time.strftime('%Y-01-01'),
1556 'date2': lambda *a: time.strftime('%Y-12-31'),
1558 def action_cancel(self,cr,uid,ids,conect=None):
1560 'view_type': 'form',
1561 "view_mode": 'form',
1562 'res_model': 'ir.module.module.configuration.wizard',
1563 'type': 'ir.actions.act_window',
1566 def action_create(self, cr, uid,ids, context=None):
1567 res=self.read(cr,uid,ids)[0]
1568 if 'date1' in res and 'date2' in res:
1569 res_obj = self.pool.get('account.fiscalyear')
1570 start_date=res['date1']
1571 end_date=res['date2']
1572 name=res['name']#DateTime.strptime(start_date, '%Y-%m-%d').strftime('%m.%Y') + '-' + DateTime.strptime(end_date, '%Y-%m-%d').strftime('%m.%Y')
1576 'date_start':start_date,
1577 'date_stop':end_date,
1579 new_id=res_obj.create(cr, uid, vals, context=context)
1581 'view_type': 'form',
1582 "view_mode": 'form',
1583 'res_model': 'ir.module.module.configuration.wizard',
1584 'type': 'ir.actions.act_window',
1589 account_config_fiscalyear()
1594 class account_config_journal_bank_accounts(osv.osv_memory):
1595 _name='account.config.journal.bank.account'
1597 'name':fields.char('Journal Name', size=64),
1598 'lines_id': fields.one2many('account.config.journal.bank.account.line', 'journal_id', 'Journal Lines'),
1601 def action_cancel(self,cr,uid,ids,conect=None):
1603 'view_type': 'form',
1604 "view_mode": 'form',
1605 'res_model': 'ir.module.module.configuration.wizard',
1606 'type': 'ir.actions.act_window',
1610 def action_create(self, cr, uid, ids, context=None):
1611 config_res=self.read(cr,uid,ids)[0]
1612 res_obj = self.pool.get('account.journal')
1613 line_obj=self.pool.get('account.config.journal.bank.account.line')
1614 if 'lines_id' in config_res and config_res['lines_id']:
1615 lines=line_obj.read(cr,uid,config_res['lines_id'])
1617 sequence_ids=self.pool.get('ir.sequence').search(cr,uid,[('name','=','Account Journal')])
1618 if 'name' in res and 'bank_account_id' in res and 'view_id' in res and sequence_ids and len(sequence_ids):
1622 'view_id':res['view_id'],
1623 'default_credit_account_id':res['bank_account_id'],
1624 'default_debit_account_id':res['bank_account_id'],
1625 'sequence_id':sequence_ids[0]
1627 res_obj.create(cr, uid, vals, context=context)
1629 'view_type': 'form',
1630 "view_mode": 'form',
1631 'res_model': 'ir.module.module.configuration.wizard',
1632 'type': 'ir.actions.act_window',
1636 account_config_journal_bank_accounts()
1638 class account_config_journal_bank_accounts_line(osv.osv_memory):
1639 _name='account.config.journal.bank.account.line'
1640 def _journal_view_get(self, cr, uid, context={}):
1641 journal_obj = self.pool.get('account.journal.view')
1642 ids = journal_obj.search(cr, uid, [])
1643 res = journal_obj.read(cr, uid, ids, ['id', 'name'], context)
1644 return [(r['id'], r['name']) for r in res]
1646 'name':fields.char('Journal Name', size=64,required=True),
1647 'bank_account_id':fields.many2one('account.account', 'Bank Account', required=True, domain=[('type','=','cash')]),
1648 'view_id':fields.selection(_journal_view_get, 'Journal View', required=True),
1649 'journal_id':fields.many2one('account.config.journal.bank.account', 'Journal', required=True),
1651 account_config_journal_bank_accounts_line()