1
# -*- encoding: utf-8 -*-
2
##############################################################################
4
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
6
# $Id: account.py 1005 2005-07-25 08:41:42Z nicoe $
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
37
from mx.DateTime import RelativeDateTime, now, DateTime, localtime
39
class account_payment_term(osv.osv):
40
_name = "account.payment.term"
41
_description = "Payment Term"
43
'name': fields.char('Payment Term', size=32),
44
'active': fields.boolean('Active'),
45
'note': fields.text('Description'),
46
'line_ids': fields.one2many('account.payment.term.line', 'payment_id', 'Terms')
49
'active': lambda *a: 1,
52
def compute(self, cr, uid, id, value, date_ref=False, context={}):
54
date_ref = now().strftime('%Y-%m-%d')
55
pt = self.browse(cr, uid, id, context)
58
for line in pt.line_ids:
59
if line.value=='fixed':
60
amt = line.value_amount
61
elif line.value=='procent':
62
amt = round(amount * line.value_amount,2)
63
elif line.value=='balance':
66
next_date = mx.DateTime.strptime(date_ref, '%Y-%m-%d') + RelativeDateTime(days=line.days)
67
if line.condition == 'end of month':
68
next_date += RelativeDateTime(day=-1)
69
result.append( (next_date.strftime('%Y-%m-%d'), amt) )
72
account_payment_term()
74
class account_payment_term_line(osv.osv):
75
_name = "account.payment.term.line"
76
_description = "Payment Term Line"
78
'name': fields.char('Line Name', size=32,required=True),
79
'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the payment term lines from the lowest sequences to the higher ones"),
80
'value': fields.selection([('procent','Procent'),('balance','Balance'),('fixed','Fixed Amount')], 'Value',required=True),
81
'value_amount': fields.float('Value Amount'),
82
'days': fields.integer('Number of Days',required=True),
83
'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."),
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
'condition': lambda *a: 'net days',
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
'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."),
103
'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."),
104
'partner_account': fields.boolean('Partner account'),
105
'close_method': fields.selection([('none','None'), ('balance','Balance'), ('detail','Detail'),('unreconciled','Unreconciled')], 'Deferral Method', required=True),
108
'close_method': lambda *a: 'none',
109
'sequence': lambda *a: 5,
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
#----------------------------------------------------------
123
class account_account(osv.osv):
125
_name = "account.account"
126
_description = "Account"
128
def _credit(self, cr, uid, ids, field_name, arg, context={}):
129
acc_set = ",".join(map(str, ids))
130
cr.execute("SELECT a.id, COALESCE(SUM(l.credit*a.sign),0) FROM account_account a LEFT JOIN account_move_line l ON (a.id=l.account_id) WHERE a.type!='view' AND a.id IN (%s) AND l.active AND l.state<>'draft' GROUP BY a.id" % acc_set)
135
for account_id, sum in res2:
136
res[account_id] += sum
139
def _debit(self, cr, uid, ids, field_name, arg, context={}):
140
acc_set = ",".join(map(str, ids))
141
cr.execute("SELECT a.id, COALESCE(SUM(l.debit*a.sign),0) FROM account_account a LEFT JOIN account_move_line l ON (a.id=l.account_id) WHERE a.type!='view' AND a.id IN (%s) and l.active AND l.state<>'draft' GROUP BY a.id" % acc_set)
146
for account_id, sum in res2:
147
res[account_id] += sum
150
def _balance(self, cr, uid, ids, field_name, arg, context={}):
151
ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
152
acc_set = ",".join(map(str, ids2))
153
cr.execute("SELECT a.id, COALESCE(SUM((l.debit-l.credit)),0) FROM account_account a LEFT JOIN account_move_line l ON (a.id=l.account_id) WHERE a.id IN (%s) and l.active AND l.state<>'draft' GROUP BY a.id" % acc_set)
155
for account_id, sum in cr.fetchall():
156
res[account_id] = round(sum,2)
158
ids3 = self.search(cr, uid, [('parent_id', 'child_of', [id])])
161
res.setdefault(id, 0.0)
162
res[id] += res.get(idx, 0.0)
164
res[id] = round(res.get(id,0.0), 2)
168
'name': fields.char('Name', size=128, required=True, translate=True, select=True),
169
'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'),
170
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
171
'code': fields.char('Code', size=64),
172
'type': fields.selection(_code_get, 'Account Type', required=True),
173
'parent_id': fields.many2many('account.account', 'account_account_rel', 'child_id', 'parent_id', 'Parents'),
174
'child_id': fields.many2many('account.account', 'account_account_rel', 'parent_id', 'child_id', 'Children'),
175
'balance': fields.function(_balance, digits=(16,2), method=True, string='Balance'),
176
'credit': fields.function(_credit, digits=(16,2), method=True, string='Credit'),
177
'debit': fields.function(_debit, digits=(16,2), method=True, string='Debit'),
178
'reconcile': fields.boolean('Reconcile', help="Check this account if the user can make a reconciliation of the entries in this account."),
179
'shortcut': fields.char('Shortcut', size=12),
180
'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."),
181
'tax_ids': fields.many2many('account.tax', 'account_account_tax_default_rel', 'account_id','tax_id', 'Default Taxes'),
182
'company_id': fields.many2one('res.company', 'Company'),
184
'active': fields.boolean('Active'),
185
'note': fields.text('Note')
188
'sign': lambda *a: 1,
189
'type': lambda *a: 'view',
190
'active': lambda *a: True,
191
'reconcile': lambda *a: False,
192
'close_method': lambda *a: 'balance',
194
def _check_recursion(self, cr, uid, ids):
197
cr.execute('select distinct parent_id from account_account_rel where child_id in ('+','.join(map(str,ids))+')')
198
ids = filter(None, map(lambda x:x[0], cr.fetchall()))
205
(_check_recursion, 'Error ! You can not create recursive accounts.', ['parent_id'])
208
cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='account_tax'")
209
if len(cr.dictfetchall())==0:
210
cr.execute("CREATE TABLE account_tax (id SERIAL NOT NULL, perm_id INTEGER, PRIMARY KEY(id))");
213
def name_search(self, cr, user, name, args=[], operator='ilike', context={}):
216
ids = self.search(cr, user, [('code','=like',name+"%")]+ args)
218
ids = self.search(cr, user, [('shortcut','=',name)]+ args)
220
ids = self.search(cr, user, [('name',operator,name)]+ args)
222
ids = self.search(cr, user, args)
223
return self.name_get(cr, user, ids, context=context)
225
def name_get(self, cr, uid, ids, context={}):
228
reads = self.read(cr, uid, ids, ['name','code'], context)
231
name = record['name']
233
name = record['code']+' - '+name
234
res.append((record['id'],name ))
238
class account_journal_view(osv.osv):
239
_name = "account.journal.view"
240
_description = "Journal View"
242
'name': fields.char('Journal View', size=64, required=True),
243
'columns_id': fields.one2many('account.journal.column', 'view_id', 'Columns')
246
account_journal_view()
249
class account_journal_column(osv.osv):
250
def _col_get(self, cr, user, context={}):
252
cols = self.pool.get('account.move.line')._columns
254
result.append( (col, cols[col].string) )
257
_name = "account.journal.column"
258
_description = "Journal Column"
260
'name': fields.char('Column Name', size=64, required=True),
261
'field': fields.selection(_col_get, 'Field Name', method=True, required=True, size=32),
262
'view_id': fields.many2one('account.journal.view', 'Journal View', select=True),
263
'sequence': fields.integer('Sequence'),
264
'required': fields.boolean('Required'),
265
'readonly': fields.boolean('Readonly'),
268
account_journal_column()
271
class account_journal(osv.osv):
272
_name = "account.journal"
273
_description = "Journal"
275
'name': fields.char('Journal Name', size=64, required=True),
276
'code': fields.char('Code', size=9),
277
'type': fields.selection([('sale','Sale'), ('purchase','Purchase'), ('cash','Cash'), ('general','General'), ('situation','Situation')], 'Type', size=32, required=True),
278
'type_control_ids': fields.many2many('account.account.type', 'account_journal_type_rel', 'journal_id','type_id', 'Type Controls', domain=[('code','<>','view')]),
279
'active': fields.boolean('Active'),
280
'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."),
281
'default_credit_account_id': fields.many2one('account.account', 'Default Credit Account'),
282
'default_debit_account_id': fields.many2one('account.account', 'Default Debit Account'),
283
'centralisation': fields.boolean('Centralisation', help="Use a centralisation journal if you want that each entry doesn't create a counterpart but share the same counterpart for each entry of this journal."),
284
'update_posted': fields.boolean('Allow Cancelling Entries'),
285
'sequence_id': fields.many2one('ir.sequence', 'Entry Sequence', help="The sequence gives the display order for a list of journals"),
286
'user_id': fields.many2one('res.users', 'User', help="The responsible user of this journal"),
287
'groups_id': fields.many2many('res.groups', 'account_journal_group_rel', 'journal_id', 'group_id', 'Groups'),
290
'active': lambda *a: 1,
291
'user_id': lambda self,cr,uid,context: uid,
293
def create(self, cr, uid, vals, context={}):
294
journal_id = super(osv.osv, self).create(cr, uid, vals, context)
295
# journal_name = self.browse(cr, uid, [journal_id])[0].code
296
# periods = self.pool.get('account.period')
297
# ids = periods.search(cr, uid, [('date_stop','>=',time.strftime('%Y-%m-%d'))])
298
# for period in periods.browse(cr, uid, ids):
299
# self.pool.get('account.journal.period').create(cr, uid, {
300
# 'name': (journal_name or '')+':'+(period.code or ''),
301
# 'journal_id': journal_id,
302
# 'period_id': period.id
305
def name_search(self, cr, user, name, args=[], operator='ilike', context={}):
308
ids = self.search(cr, user, [('code','ilike',name)]+ args)
310
ids = self.search(cr, user, [('name',operator,name)]+ args)
311
return self.name_get(cr, user, ids, context=context)
314
class account_bank(osv.osv):
315
_name = "account.bank"
316
_description = "Banks"
318
'name': fields.char('Bank Name', size=64, required=True),
319
'code': fields.char('Code', size=6),
320
'partner_id': fields.many2one('res.partner', 'Bank Partner', help="The link to the partner that represent this bank. The partner contains all information about contacts, phones, applied taxes, eso."),
321
'bank_account_ids': fields.one2many('account.bank.account', 'bank_id', 'Bank Accounts'),
322
'note': fields.text('Notes'),
327
class account_bank_account(osv.osv):
328
_name = "account.bank.account"
329
_description = "Bank Accounts"
331
'name': fields.char('Bank Account', size=64, required=True),
332
'code': fields.char('Code', size=6),
333
'iban': fields.char('IBAN', size=24),
334
'swift': fields.char('Swift Code', size=24),
335
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
336
'journal_id': fields.many2one('account.journal', 'Journal', required=True),
337
'account_id': fields.many2one('account.account', 'General Account', required=True, select=True),
338
'bank_id': fields.many2one('account.bank', 'Bank'),
341
account_bank_account()
344
class account_fiscalyear(osv.osv):
345
_name = "account.fiscalyear"
346
_description = "Fiscal Year"
348
'name': fields.char('Fiscal Year', size=64, required=True),
349
'code': fields.char('Code', size=6, required=True),
350
'company_id': fields.many2one('res.company', 'Company'),
351
'date_start': fields.date('Start date', required=True),
352
'date_stop': fields.date('End date', required=True),
353
'period_ids': fields.one2many('account.period', 'fiscalyear_id', 'Periods'),
354
'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', redonly=True)
357
'state': lambda *a: 'draft',
360
def create_period3(self,cr, uid, ids, context={}):
361
return self.create_period(cr, uid, ids, context, 3)
363
def create_period(self,cr, uid, ids, context={}, interval=1):
364
for fy in self.browse(cr, uid, ids, context):
366
ds = mx.DateTime.strptime(fy.date_start, '%Y-%m-%d')
367
while ds.strftime('%Y-%m-%d')<fy.date_stop:
368
de = ds + RelativeDateTime(months=interval, days=-1)
369
self.pool.get('account.period').create(cr, uid, {
370
'name': ds.strftime('%d/%m') + ' - '+de.strftime('%d/%m'),
371
'code': ds.strftime('%d/%m') + '-'+de.strftime('%d/%m'),
372
'date_start': ds.strftime('%Y-%m-%d'),
373
'date_stop': de.strftime('%Y-%m-%d'),
374
'fiscalyear_id': fy.id,
376
ds = ds + RelativeDateTime(months=interval)
380
class account_period(osv.osv):
381
_name = "account.period"
382
_description = "Account period"
384
'name': fields.char('Period Name', size=64, required=True),
385
'code': fields.char('Code', size=12),
386
'date_start': fields.date('Start of period', required=True, states={'done':[('readonly',True)]}),
387
'date_stop': fields.date('End of period', required=True, states={'done':[('readonly',True)]}),
388
'fiscalyear_id': fields.many2one('account.fiscalyear', 'Fiscal Year', required=True, states={'done':[('readonly',True)]}, select=True),
389
'state': fields.selection([('draft','Draft'), ('done','Done')], 'State', readonly=True)
392
'state': lambda *a: 'draft',
394
_order = "date_start"
396
def find(self, cr, uid, dt=None, context={}):
398
dt = time.strftime('%Y-%m-%d')
399
#CHECKME: shouldn't we check the state of the period?
400
ids = self.search(cr, uid, [('date_start','<=',dt),('date_stop','>=',dt)])
402
raise osv.except_osv('Error !', 'No period defined for this date !\nPlease create a fiscal year.')
406
class account_journal_period(osv.osv):
407
_name = "account.journal.period"
408
_description = "Journal - Period"
409
def _icon_get(self, cr, uid, ids, field_name, arg=None, context={}):
410
result = {}.fromkeys(ids, 'STOCK_NEW')
411
for r in self.read(cr, uid, ids, ['state']):
413
'draft': 'STOCK_NEW',
414
'printed': 'STOCK_PRINT_PREVIEW',
415
'done': 'STOCK_DIALOG_AUTHENTICATION',
416
}.get(r['state'], 'STOCK_NEW')
419
'name': fields.char('Journal-Period Name', size=64, required=True),
420
'journal_id': fields.many2one('account.journal', 'Journal', required=True, ondelete="cascade"),
421
'period_id': fields.many2one('account.period', 'Period', required=True, ondelete="cascade"),
422
'icon': fields.function(_icon_get, method=True, string='Icon'),
423
'active': fields.boolean('Active', required=True),
424
'state': fields.selection([('draft','Draft'), ('printed','Printed'), ('done','Done')], 'State', required=True, readonly=True)
426
def _check(self, cr, uid, ids, context={}):
427
for obj in self.browse(cr, uid, ids, context):
428
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))
431
raise osv.except_osv('Error !', 'You can not modify/delete a journal with entries for this period !')
434
def write(self, cr, uid, ids, vals, context={}):
435
self._check(cr, uid, ids, context)
436
return super(account_journal_period, self).write(cr, uid, ids, vals, context)
438
def unlink(self, cr, uid, ids, context={}):
439
self._check(cr, uid, ids, context)
440
return super(account_journal_period, self).unlink(cr, uid, ids, context)
443
'state': lambda *a: 'draft',
444
'active': lambda *a: True,
447
account_journal_period()
449
#----------------------------------------------------------
451
#----------------------------------------------------------
452
class account_move(osv.osv):
453
_name = "account.move"
454
_description = "Account Entry"
456
def _get_period(self, cr, uid, context):
457
periods = self.pool.get('account.period').find(cr, uid)
463
'name': fields.char('Entry Name', size=64, required=True),
464
'ref': fields.char('Ref', size=64),
465
'period_id': fields.many2one('account.period', 'Period', required=True, states={'posted':[('readonly',True)]}),
466
'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'posted':[('readonly',True)]}, relate=True),
467
'state': fields.selection([('draft','Draft'), ('posted','Posted')], 'State', required=True, readonly=True),
468
'line_id': fields.one2many('account.move.line', 'move_id', 'Entries', states={'posted':[('readonly',True)]}),
471
'state': lambda *a: 'draft',
472
'period_id': _get_period,
474
def button_validate(self, cr, uid, ids, context={}):
475
if self.validate(cr, uid, ids, context) and len(ids):
476
cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('posted',))
478
cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('draft',))
480
raise osv.except_osv('Integrity Error !', 'You can not validate a non balanced entry !')
483
def button_cancel(self, cr, uid, ids, context={}):
484
for line in self.browse(cr, uid, ids, context):
485
if not line.journal_id.update_posted:
486
raise osv.except_osv('Error !', 'You can not modify a posted entry of this journal !')
488
cr.execute('update account_move set state=%s where id in ('+','.join(map(str,ids))+')', ('draft',))
491
def write(self, cr, uid, ids, vals, context={}):
493
c['novalidate'] = True
494
result = super(osv.osv, self).write(cr, uid, ids, vals, c)
495
self.validate(cr, uid, ids, context)
499
# TODO: Check if period is closed !
501
def create(self, cr, uid, vals, context={}):
502
if 'line_id' in vals:
503
if 'journal_id' in vals:
504
for l in vals['line_id']:
506
l[2]['journal_id'] = vals['journal_id']
507
context['journal_id'] = vals['journal_id']
508
if 'period_id' in vals:
509
for l in vals['line_id']:
511
l[2]['period_id'] = vals['period_id']
512
context['period_id'] = vals['period_id']
514
default_period = self._get_period(cr, uid, context)
515
for l in vals['line_id']:
517
l[2]['period_id'] = default_period
518
context['period_id'] = default_period
520
if 'line_id' in vals:
522
c['novalidate'] = True
523
result = super(account_move, self).create(cr, uid, vals, c)
524
self.validate(cr, uid, [result], context)
526
result = super(account_move, self).create(cr, uid, vals, context)
529
def unlink(self, cr, uid, ids, context={}, check=True):
531
for move in self.browse(cr, uid, ids, context):
532
line_ids = map(lambda x: x.id, move.line_id)
533
context['journal_id'] = move.journal_id.id
534
context['period_id'] = move.period_id.id
535
self.pool.get('account.move.line')._update_check(cr, uid, line_ids, context)
536
toremove.append(move.id)
537
result = super(account_move, self).unlink(cr, uid, toremove, context)
540
def _compute_balance(self, cr, uid, id, context={}):
541
move = self.browse(cr, uid, [id])[0]
543
for line in move.line_id:
544
amount+= (line.debit - line.credit)
547
def _centralise(self, cr, uid, move, mode):
549
account_id = move.journal_id.default_debit_account_id.id
552
account_id = move.journal_id.default_credit_account_id.id
555
# find the first line of this move with the current mode
556
# or create it if it doesn't exist
557
cr.execute('select id from account_move_line where move_id=%d and centralisation=%s limit 1', (move.id, mode))
562
line_id = self.pool.get('account.move.line').create(cr, uid, {
563
'name': 'Centralisation '+mode,
564
'centralisation': mode,
565
'account_id': account_id,
567
'journal_id': move.journal_id.id,
568
'period_id': move.period_id.id,
569
'date': move.period_id.date_stop,
572
}, {'journal_id': move.journal_id.id, 'period_id': move.period_id.id})
574
# find the first line of this move with the other mode
575
# so that we can exclude it from our calculation
576
cr.execute('select id from account_move_line where move_id=%d and centralisation=%s limit 1', (move.id, mode2))
583
cr.execute('select sum('+mode+') from account_move_line where move_id=%d and id<>%d', (move.id, line_id2))
584
result = cr.fetchone()[0] or 0.0
585
cr.execute('update account_move_line set '+mode2+'=%f where id=%d', (result, line_id))
589
# Validate a balanced move. If it is a centralised journal, create a move.
591
def validate(self, cr, uid, ids, context={}):
593
for move in self.browse(cr, uid, ids, context):
594
journal = move.journal_id
598
for line in move.line_id:
599
amount += line.debit - line.credit
600
line_ids.append(line.id)
601
if line.state=='draft':
602
line_draft_ids.append(line.id)
603
if abs(amount) < 0.0001:
604
if not len(line_draft_ids):
606
self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
607
'journal_id': move.journal_id.id,
608
'period_id': move.period_id.id,
610
}, context, check=False)
615
if journal.type not in ('purchase','sale'):
617
if journal.type=='purchase':
620
for line in move.line_id:
621
if line.account_id.tax_ids:
622
code = amount = False
623
for tax in line.account_id.tax_ids:
625
acc = (line.debit >0) and tax.account_paid_id.id or tax.account_collected_id.id
626
account[acc] = (getattr(tax,field_base+'tax_code_id').id, getattr(tax,field_base+'tax_sign'))
627
account2[(acc,getattr(tax,field_base+'tax_code_id').id)] = (getattr(tax,field_base+'tax_code_id').id, getattr(tax,field_base+'tax_sign'))
628
code = getattr(tax,field_base+'base_code_id').id
629
amount = getattr(tax, field_base+'base_sign') * (line.debit + line.credit)
632
self.pool.get('account.move.line').write(cr, uid, [line.id], {
635
}, context, check=False)
640
key = (line.account_id.id,line.tax_code_id.id)
642
code = account2[key][0]
643
amount = account2[key][1] * (line.debit + line.credit)
644
elif line.account_id.id in account:
645
code = account[line.account_id.id][0]
646
amount = account[line.account_id.id][1] * (line.debit + line.credit)
648
self.pool.get('account.move.line').write(cr, uid, [line.id], {
651
}, context, check=False)
656
if journal.centralisation:
657
self._centralise(cr, uid, move, 'debit')
658
self._centralise(cr, uid, move, 'credit')
659
self.pool.get('account.move.line').write(cr, uid, line_draft_ids, {
661
}, context, check=False)
664
self.pool.get('account.move.line').write(cr, uid, line_ids, {
665
'journal_id': move.journal_id.id,
666
'period_id': move.period_id.id,
667
#'tax_code_id': False,
670
}, context, check=False)
675
class account_move_reconcile(osv.osv):
676
_name = "account.move.reconcile"
677
_description = "Account Reconciliation"
679
'name': fields.char('Name', size=64, required=True),
680
'type': fields.char('Type', size=16, required=True),
681
'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry lines'),
684
'name': lambda *a: 'reconcile '+time.strftime('%Y-%m-%d')
686
account_move_reconcile()
689
# use a sequence for names ?
691
class account_bank_statement(osv.osv):
692
def _default_journal_id(self, cr, uid, context={}):
693
if context.get('journal_id', False):
694
return context['journal_id']
695
if context.get('journal_id', False):
700
def _default_balance_start(self, cr, uid, context={}):
701
cr.execute('select id from account_bank_statement where journal_id=%d order by date desc limit 1', (1,))
704
return self.browse(cr, uid, [res[0]], context)[0].balance_end
707
def _end_balance(self, cr, uid, ids, prop, unknow_none, unknow_dict):
709
statements = self.browse(cr, uid, ids)
710
for statement in statements:
711
res[statement.id] = statement.balance_start
712
for line in statement.line_ids:
713
res[statement.id] += line.amount
715
res[r] = round(res[r], 2)
718
def _get_period(self, cr, uid, context={}):
719
periods = self.pool.get('account.period').find(cr, uid)
726
_name = "account.bank.statement"
727
_description = "Bank Statement"
729
'name': fields.char('Name', size=64, required=True),
730
'date': fields.date('Date', required=True, states={'confirm':[('readonly',True)]}),
731
'journal_id': fields.many2one('account.journal', 'Journal', required=True, states={'confirm':[('readonly',True)]}, domain=[('type','=','cash')], relate=True),
732
'period_id': fields.many2one('account.period', 'Period', required=True, states={'confirm':[('readonly',True)]}),
733
'balance_start': fields.float('Starting Balance', digits=(16,2), states={'confirm':[('readonly',True)]}),
734
'balance_end_real': fields.float('Ending Balance', digits=(16,2), states={'confirm':[('readonly',True)]}),
735
'balance_end': fields.function(_end_balance, method=True, string='Balance'),
736
'line_ids': fields.one2many('account.bank.statement.line', 'statement_id', 'Statement lines', states={'confirm':[('readonly',True)]}),
737
'move_line_ids': fields.one2many('account.move.line', 'statement_id', 'Entry lines', states={'confirm':[('readonly',True)]}),
738
'state': fields.selection([('draft','Draft'),('confirm','Confirm')], 'State', required=True, states={'confirm':[('readonly',True)]}, readonly="1"),
741
'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement'),
742
'date': lambda *a: time.strftime('%Y-%m-%d'),
743
'state': lambda *a: 'draft',
744
'balance_start': _default_balance_start,
745
'journal_id': _default_journal_id,
746
'period_id': _get_period,
748
def button_confirm(self, cr, uid, ids, context={}):
750
for st in self.browse(cr, uid, ids, context):
751
if not st.state=='draft':
753
if not (abs(st.balance_end - st.balance_end_real) < 0.0001):
754
raise osv.except_osv('Error !', 'The statement balance is incorrect !\nCheck that the ending balance equals the computed one.')
755
if (not st.journal_id.default_credit_account_id) or (not st.journal_id.default_debit_account_id):
756
raise osv.except_osv('Configration Error !', 'Please verify that an account is defined in the journal.')
757
for move in st.line_ids:
760
self.pool.get('account.move.line').create(cr, uid, {
763
'partner_id': ((move.partner_id) and move.partner_id.id) or False,
764
'account_id': (move.account_id) and move.account_id.id,
765
'credit': ((move.amount>0) and move.amount) or 0.0,
766
'debit': ((move.amount<0) and -move.amount) or 0.0,
767
'statement_id': st.id,
768
'journal_id': st.journal_id.id,
769
'period_id': st.period_id.id,
771
if not st.journal_id.centralisation:
773
c['journal_id'] = st.journal_id.id
774
c['period_id'] = st.period_id.id
775
fields = ['move_id','name','date','partner_id','account_id','credit','debit']
776
default = self.pool.get('account.move.line').default_get(cr, uid, fields, context=c)
778
'statement_id': st.id,
779
'journal_id': st.journal_id.id,
780
'period_id': st.period_id.id,
782
self.pool.get('account.move.line').create(cr, uid, default, context=context)
784
self.write(cr, uid, done, {'state':'confirm'}, context=context)
786
def button_cancel(self, cr, uid, ids, context={}):
788
for st in self.browse(cr, uid, ids, context):
789
if st.state=='draft':
791
ids = [x.move_id.id for x in st.move_line_ids]
792
self.pool.get('account.move').unlink(cr, uid, ids, context)
794
self.write(cr, uid, done, {'state':'draft'}, context=context)
796
def onchange_journal_id(self, cr, uid, id, journal_id, context={}):
799
cr.execute('select balance_end_real from account_bank_statement where journal_id=%d order by date desc limit 1', (journal_id,))
802
return {'value': {'balance_start': res[0] or 0.0}}
804
account_bank_statement()
806
class account_bank_statement_line(osv.osv):
807
def onchange_partner_id(self, cr, uid, id, partner_id, type, context={}):
810
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context)
812
account_id = part.property_account_payable[0]
814
account_id = part.property_account_receivable[0]
815
cr.execute('select sum(debit-credit) from account_move_line where (reconcile_id is null) and partner_id=%d and account_id=%d', (partner_id, account_id))
816
balance = cr.fetchone()[0] or 0.0
817
val = {'amount': balance, 'account_id':account_id}
819
_order = "date,name desc"
820
_name = "account.bank.statement.line"
821
_description = "Bank Statement Line"
823
'name': fields.char('Name', size=64, required=True),
824
'date': fields.date('Date'),
825
'amount': fields.float('Amount'),
826
'type': fields.selection([('supplier','Supplier'),('customer','Customer'),('general','General')], 'Type', required=True),
827
'partner_id': fields.many2one('res.partner', 'Partner'),
828
'account_id': fields.many2one('account.account','Account', required=True),
829
'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True),
832
'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement.line'),
833
'date': lambda *a: time.strftime('%Y-%m-%d'),
834
'type': lambda *a: 'general',
836
account_bank_statement_line()
839
#----------------------------------------------------------
841
#----------------------------------------------------------
844
child_depend: la taxe depend des taxes filles
846
class account_tax_code(osv.osv):
848
A code for the tax object.
850
This code is used for some tax declarations.
852
def _sum(self, cr, uid, ids, prop, unknow_none, unknow_dict, where =''):
853
ids2 = self.search(cr, uid, [('parent_id', 'child_of', ids)])
854
acc_set = ",".join(map(str, ids2))
855
cr.execute('SELECT tax_code_id,sum(tax_amount) FROM account_move_line WHERE tax_code_id in ('+acc_set+') '+where+' GROUP BY tax_code_id')
856
res=dict(cr.fetchall())
858
ids3 = self.search(cr, uid, [('parent_id', 'child_of', [id])])
861
res.setdefault(id, 0.0)
862
res[id] += res.get(idx, 0.0)
864
res[id] = round(res.get(id,0.0), 2)
867
def _sum_period(self, cr, uid, ids, prop, unknow_none, context={}):
868
if not 'period_id' in context:
869
period_id = self.pool.get('account.period').find(cr, uid)
870
if not len(period_id):
871
return dict.fromkeys(ids, 0.0)
872
period_id = period_id[0]
874
period_id = context['period_id']
875
return self._sum(cr, uid, ids, prop, unknow_none, context, where=' and period_id='+str(period_id))
877
_name = 'account.tax.code'
878
_description = 'Tax Code'
880
'name': fields.char('Tax Case Name', size=64, required=True),
881
'code': fields.char('Case Code', size=16),
882
'info': fields.text('Description'),
883
'sum': fields.function(_sum, method=True, string="Year Sum"),
884
'sum_period': fields.function(_sum_period, method=True, string="Period Sum"),
885
'parent_id': fields.many2one('account.tax.code', 'Parent Code', select=True),
886
'child_ids': fields.one2many('account.tax.code', 'parent_id', 'Childs Codes'),
887
'line_ids': fields.one2many('account.move.line', 'tax_code_id', 'Lines')
891
class account_move_line(osv.osv):
892
_name = "account.move.line"
893
_description = "Entry lines"
895
def default_get(self, cr, uid, fields, context={}):
896
data = self._default_get(cr, uid, fields, context)
897
for f in data.keys():
902
def _default_get(self, cr, uid, fields, context={}):
903
# Compute simple values
904
data = super(account_move_line, self).default_get(cr, uid, fields, context)
906
# Compute the current move
909
statement_acc_id = False
910
if context.get('journal_id',False) and context.get('period_id',False):
911
cr.execute('select move_id \
915
journal_id=%d and period_id=%d and create_uid=%d and state=%s \
916
order by id desc limit 1', (context['journal_id'], context['period_id'], uid, 'draft'))
918
move_id = (res and res[0]) or False
919
cr.execute('select date \
923
journal_id=%d and period_id=%d and create_uid=%d order by id desc', (context['journal_id'], context['period_id'], uid))
925
data['date'] = res and res[0] or time.strftime('%Y-%m-%d')
926
cr.execute('select statement_id, account_id \
930
journal_id=%d and period_id=%d and statement_id is not null and create_uid=%d order by id desc', (context['journal_id'], context['period_id'], uid))
932
statement_id = res and res[0] or False
933
statement_acc_id = res and res[1]
938
data['move_id'] = move_id
942
move = self.pool.get('account.move').browse(cr, uid, move_id, context)
943
for l in move.line_id:
944
partner_id = partner_id or l.partner_id.id
945
total += (l.debit - l.credit)
946
for tax in l.account_id.tax_ids:
947
acc = (l.debit >0) and tax.account_paid_id.id or tax.account_collected_id.id
948
taxes.setdefault((acc,tax.tax_code_id.id), False)
949
taxes[(l.account_id.id,l.tax_code_id.id)] = True
950
data.setdefault('name', l.name)
952
data['partner_id'] = partner_id
956
if not taxes[t] and t[0]:
958
for l in move.line_id:
959
for tax in l.account_id.tax_ids:
960
taxes = self.pool.get('account.tax').compute(cr, uid, [tax.id], l.debit or l.credit, 1, False)
961
key = (l.debit and 'account_paid_id') or 'account_collected_id'
963
if (t2[key] == t[0]) and (tax.tax_code_id.id==t[1]):
968
data['debit'] = s>0 and s or 0.0
969
data['credit'] = s<0 and -s or 0.0
971
data['tax_code_id'] = t[1]
973
data['account_id'] = t[0]
976
# Compute line for tax T
981
# Compute latest line
983
data['credit'] = total>0 and total
984
data['debit'] = total<0 and -total
986
data['account_id'] = move.journal_id.default_credit_account_id.id or False
988
data['account_id'] = move.journal_id.default_debit_account_id.id or False
989
if data['account_id']:
990
account = self.pool.get('account.account').browse(cr, uid, data['account_id'])
991
data['tax_code_id'] = self._default_get_tax(cr, uid, account )
994
def _default_get_tax(self, cr, uid, account, debit=0, credit=0, context={}):
996
return account.tax_ids[0].base_code_id.id
999
def _on_create_write(self, cr, uid, id, context={}):
1000
ml = self.browse(cr, uid, id, context)
1001
return map(lambda x: x.id, ml.move_id.line_id)
1003
def _balance(self, cr, uid, ids, prop, unknow_none, unknow_dict):
1005
# TODO group the foreach in sql
1007
cr.execute('SELECT date,account_id FROM account_move_line WHERE id=%d', (id,))
1008
dt, acc = cr.fetchone()
1009
cr.execute('SELECT SUM(debit-credit) FROM account_move_line WHERE account_id=%d AND (date<%s OR (date=%s AND id<=%d)) and active', (acc,dt,dt,id))
1010
res[id] = cr.fetchone()[0]
1014
'name': fields.char('Name', size=64, required=True),
1015
'quantity': fields.float('Quantity', digits=(16,2), help="The optionnal quantity expressed by this line, eg: number of product sold. The quantity is not a legal requirement but is very usefull for some reports."),
1016
'debit': fields.float('Debit', digits=(16,2), states={'reconciled':[('readonly',True)]}),
1017
'credit': fields.float('Credit', digits=(16,2), states={'reconciled':[('readonly',True)]}),
1018
'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade", states={'reconciled':[('readonly',True)]}, domain=[('type','<>','view')]),
1020
'move_id': fields.many2one('account.move', 'Entry', required=True, ondelete="cascade", states={'reconciled':[('readonly',True)]}, help="The entry of this entry line.", select=True),
1022
'ref': fields.char('Ref.', size=32),
1023
'statement_id': fields.many2one('account.bank.statement', 'Statement', help="The bank statement used for bank reconciliation", select=True),
1024
'reconcile_id': fields.many2one('account.move.reconcile', 'Reconcile', readonly=True, ondelete='set null', select=True),
1025
'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optionnal other currency if it is a multi-currency entry."),
1026
'currency_id': fields.many2one('res.currency', 'Currency', help="The optionnal other currency if it is a multi-currency entry."),
1028
'period_id': fields.many2one('account.period', 'Period', required=True),
1029
'journal_id': fields.many2one('account.journal', 'Journal', required=True, relate=True),
1030
'blocked': fields.boolean('Litigation', help="You can check this box to mark the entry line as a litigation with the associated partner"),
1032
'partner_id': fields.many2one('res.partner', 'Partner Ref.', states={'reconciled':[('readonly',True)]}),
1033
'date_maturity': fields.date('Maturity date', states={'reconciled':[('readonly',True)]}, help="This field is used for payable and receivable entries. You can put the limit date for the payment of this entry line."),
1034
'date': fields.date('Effective date', required=True),
1035
'date_created': fields.date('Creation date'),
1036
'analytic_lines': fields.one2many('account.analytic.line', 'move_id', 'Analytic lines'),
1037
'centralisation': fields.selection([('normal','Normal'),('credit','Credit Centralisation'),('debit','Debit Centralisation')], 'Centralisation', size=6),
1038
'balance': fields.function(_balance, method=True, string='Balance'),
1039
'active': fields.boolean('Active'),
1040
'state': fields.selection([('draft','Draft'), ('valid','Valid'), ('reconciled','Reconciled')], 'State', readonly=True),
1041
'tax_code_id': fields.many2one('account.tax.code', 'Tax Account'),
1042
'tax_amount': fields.float('Tax/Base Amount', digits=(16,2), select=True),
1045
'blocked': lambda *a: False,
1046
'active': lambda *a: True,
1047
'centralisation': lambda *a: 'normal',
1048
'date_created': lambda *a: time.strftime('%Y-%m-%d'),
1049
'state': lambda *a: 'draft',
1050
'journal_id': lambda self, cr, uid, c: c.get('journal_id', False),
1051
'period_id': lambda self, cr, uid, c: c.get('period_id', False),
1053
_order = "date desc,id desc"
1054
_sql_constraints = [
1055
('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in accounting entry !'),
1056
('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in accounting entry !'),
1058
def onchange_partner_id(self, cr, uid, ids, move_id, partner_id, account_id=None, debit=0, credit=0, journal=False):
1059
if (not partner_id) or account_id:
1061
part = self.pool.get('res.partner').browse(cr, uid, partner_id)
1062
id1 = part.property_account_payable[0]
1063
id2 = part.property_account_receivable[0]
1064
cr.execute('select sum(debit-credit) from account_move_line where (reconcile_id is null) and partner_id=%d and account_id=%d', (partner_id, id2))
1065
balance = cr.fetchone()[0] or 0.0
1067
if (not debit) and (not credit):
1068
if abs(balance)>0.01:
1069
val['credit'] = ((balance>0) and balance) or 0
1070
val['debit'] = ((balance<0) and -balance) or 0
1071
val['account_id'] = id2
1073
cr.execute('select sum(debit-credit) from account_move_line where (reconcile_id is null) and partner_id=%d and account_id=%d', (partner_id, id1))
1074
balance = cr.fetchone()[0] or 0.0
1075
val['credit'] = ((balance>0) and balance) or 0
1076
val['debit'] = ((balance<0) and -balance) or 0
1077
val['account_id'] = id1
1079
val['account_id'] = (debit>0) and id2 or id1
1081
jt = self.pool.get('account.journal').browse(cr, uid, journal).type
1083
val['account_id'] = id2
1084
elif jt=='purchase':
1085
val['account_id'] = id1
1086
return {'value':val}
1088
def reconcile(self, cr, uid, ids, type='auto', writeoff_acc_id=False, writeoff_period_id=False, writeoff_journal_id=False, context={}):
1089
id_set = ','.join(map(str, ids))
1090
lines = self.read(cr, uid, ids, context=context)
1091
unrec_lines = filter(lambda x: not x['reconcile_id'], lines)
1095
for line in unrec_lines:
1096
credit += line['credit']
1097
debit += line['debit']
1098
account_id = line['account_id'][0]
1099
partner_id = (line['partner_id'] and line['partner_id'][0]) or False
1100
writeoff = debit - credit
1101
date = time.strftime('%Y-%m-%d')
1103
cr.execute('SELECT account_id,reconcile_id FROM account_move_line WHERE id IN ('+id_set+') GROUP BY account_id,reconcile_id')
1105
#TODO: move this check to a constraint in the account_move_reconcile object
1107
raise 'Entries are not of the same account !'
1109
raise 'Some entries are already reconciled !'
1111
if not writeoff_acc_id:
1112
raise osv.except_osv('Warning', 'You have to provide an account for the write off entry !')
1116
self_credit = writeoff
1122
self_debit = -writeoff
1125
(0, 0, {'name':'Write-Off', 'debit':self_debit, 'credit':self_credit, 'account_id':account_id, 'date':date, 'partner_id':partner_id}),
1126
(0, 0, {'name':'Write-Off', 'debit':debit, 'credit':credit, 'account_id':writeoff_acc_id, 'date':date, 'partner_id':partner_id})
1130
if writeoff_journal_id:
1131
journal = self.pool.get('account.journal').browse(cr, uid, writeoff_journal_id)
1132
if journal.sequence_id:
1133
name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
1135
writeoff_move_id = self.pool.get('account.move').create(cr, uid, {
1137
'period_id': writeoff_period_id,
1138
'journal_id': writeoff_journal_id,
1141
'line_id': writeoff_lines
1144
writeoff_line_ids = self.search(cr, uid, [('move_id', '=', writeoff_move_id), ('account_id', '=', account_id)])
1145
ids += writeoff_line_ids
1147
self.write(cr, uid, ids, {'state': 'reconciled'}, update_check=False)
1148
r_id = self.pool.get('account.move.reconcile').create(cr, uid, {
1151
'line_id': map(lambda x: (4,x,False), ids)
1153
# the id of the move.reconcile is written in the move.line (self) by the create method above
1154
# because of the way the line_id are defined: (4, x, False)
1155
wf_service = netsvc.LocalService("workflow")
1157
wf_service.trg_trigger(uid, 'account.move.line', id, cr)
1160
def view_header_get(self, cr, user, view_id, view_type, context):
1161
if (not context.get('journal_id', False)) or (not context.get('period_id', False)):
1163
cr.execute('select code from account_journal where id=%d', (context['journal_id'],))
1164
j = cr.fetchone()[0] or ''
1165
cr.execute('select code from account_period where id=%d', (context['period_id'],))
1166
p = cr.fetchone()[0] or ''
1171
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context={}, toolbar=False):
1172
result = super(osv.osv, self).fields_view_get(cr, uid, view_id,view_type,context)
1173
if view_type=='tree' and 'journal_id' in context:
1174
title = self.view_header_get(cr, uid, view_id, view_type, context)
1175
journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
1177
# if the journal view has a state field, color lines depending on
1180
for field in journal.view_id.columns_id:
1181
if field.field=='state':
1182
state = ' colors="red:state==\'draft\'"'
1184
#xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5"%s>\n\t''' % (title, state)
1185
xml = '''<?xml version="1.0"?>\n<tree string="%s" editable="top" refresh="5" on_write="_on_create_write"%s>\n\t''' % (title, state)
1195
for field in journal.view_id.columns_id:
1196
fields.append(field.field)
1199
attrs.append('readonly="1"')
1201
attrs.append('required="1"')
1203
attrs.append('required="0"')
1204
if field.field == 'partner_id':
1205
attrs.append('on_change="onchange_partner_id(move_id,partner_id,account_id,debit,credit,((\'journal_id\' in context) and context[\'journal_id\']) or {})"')
1206
if field.field in widths:
1207
attrs.append('width="'+str(widths[field.field])+'"')
1208
xml += '''<field name="%s" %s/>\n''' % (field.field,' '.join(attrs))
1210
xml += '''</tree>'''
1211
result['arch'] = xml
1212
result['fields'] = self.fields_get(cr, uid, fields, context)
1215
def unlink(self, cr, uid, ids, context={}, check=True):
1216
self._update_check(cr, uid, ids, context)
1217
for line in self.browse(cr, uid, ids, context):
1218
context['journal_id']=line.journal_id.id
1219
context['period_id']=line.period_id.id
1220
result = super(account_move_line, self).unlink(cr, uid, [line.id], context=context)
1222
self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context=context)
1226
# TO VERIFY: check if try to write journal of only one line ???
1228
def write(self, cr, uid, ids, vals, context={}, check=True, update_check=True):
1230
self._update_check(cr, uid, ids, context)
1231
result = super(osv.osv, self).write(cr, uid, ids, vals, context)
1234
for line in self.browse(cr, uid, ids):
1235
if line.move_id.id not in done:
1236
done.append(line.move_id.id)
1237
self.pool.get('account.move').validate(cr, uid, [line.move_id.id], context)
1240
def _update_journal_check(self, cr, uid, journal_id, period_id, context={}):
1241
cr.execute('select state from account_journal_period where journal_id=%d and period_id=%d', (journal_id, period_id))
1242
result = cr.fetchall()
1243
for (state,) in result:
1245
raise osv.except_osv('Error !', 'You can not add/modify entries in a closed journal.')
1247
journal = self.pool.get('account.journal').browse(cr, uid, journal_id, context)
1248
period = self.pool.get('account.period').browse(cr, uid, period_id, context)
1249
self.pool.get('account.journal.period').create(cr, uid, {
1250
'name': (journal.code or journal.name)+':'+(period.name or ''),
1251
'journal_id': journal.id,
1252
'period_id': period.id
1256
def _update_check(self, cr, uid, ids, context={}):
1258
for line in self.browse(cr, uid, ids, context):
1259
if line.move_id.state<>'draft':
1260
raise osv.except_osv('Error !', 'You can not modify or delete a confirmed entry !')
1261
if line.reconcile_id:
1262
raise osv.except_osv('Error !', 'You can not modify or delete a reconciled entry !')
1263
t = (line.journal_id.id, line.period_id.id)
1265
self._update_journal_check(cr, uid, line.journal_id.id, line.period_id.id, context)
1269
def create(self, cr, uid, vals, context={}, check=True):
1270
if 'journal_id' in vals and 'journal_id' not in context:
1271
context['journal_id'] = vals['journal_id']
1272
if 'period_id' in vals and 'period_id' not in context:
1273
context['period_id'] = vals['period_id']
1274
if 'journal_id' not in context and 'move_id' in vals:
1275
m = self.pool.get('account.move').browse(cr, uid, vals['move_id'])
1276
context['journal_id'] = m.journal_id.id
1277
context['period_id'] = m.period_id.id
1278
self._update_journal_check(cr, uid, context['journal_id'], context['period_id'], context)
1279
move_id = vals.get('move_id', False)
1280
journal = self.pool.get('account.journal').browse(cr, uid, context['journal_id'])
1282
if journal.centralisation:
1283
# use the first move ever created for this journal and period
1284
cr.execute('select id from account_move where journal_id=%d and period_id=%d order by id limit 1', (context['journal_id'],context['period_id']))
1287
vals['move_id'] = res[0]
1289
if not vals.get('move_id', False):
1290
if journal.sequence_id:
1291
name = self.pool.get('ir.sequence').get_id(cr, uid, journal.sequence_id.id)
1294
'period_id': context['period_id'],
1295
'journal_id': context['journal_id']
1297
move_id = self.pool.get('account.move').create(cr, uid, v, context)
1298
vals['move_id'] = move_id
1300
raise osv.except_osv('No piece number !', 'Can not create an automatic sequence for this piece !\n\nPut a sequence in the journal definition for automatic numbering or create a sequence manually for this piece.')
1302
if ('account_id' in vals) and journal.type_control_ids:
1303
type = self.pool.get('account.account').browse(cr, uid, vals['account_id']).type
1305
for t in journal.type_control_ids:
1310
raise osv.except_osv('Bad account !', 'You can not use this general account in this journal !')
1312
result = super(osv.osv, self).create(cr, uid, vals, context)
1314
self.pool.get('account.move').validate(cr, uid, [vals['move_id']], context)
1318
class account_tax(osv.osv):
1322
Type: percent, fixed, none, code
1323
PERCENT: tax = price * amount
1324
FIXED: tax = price + amount
1326
CODE: execute python code. localcontext = {'price_unit':pu, 'address':address_object}
1327
return result in the context
1328
Ex: result=round(price_unit*0.21,4)
1330
_name = 'account.tax'
1331
_description = 'Tax'
1333
'name': fields.char('Tax Name', size=64, required=True),
1334
'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."),
1335
'amount': fields.float('Amount', required=True, digits=(14,4)),
1336
'active': fields.boolean('Active'),
1337
'type': fields.selection( [('percent','Percent'), ('fixed','Fixed'), ('none','None'), ('code','Python Code')], 'Tax Type', required=True),
1338
'applicable_type': fields.selection( [('true','True'), ('code','Python Code')], 'Applicable Type', required=True),
1339
'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."),
1340
'account_collected_id':fields.many2one('account.account', 'Collected Tax Account'),
1341
'account_paid_id':fields.many2one('account.account', 'Paid Tax Account'),
1342
'parent_id':fields.many2one('account.tax', 'Parent Tax Account', select=True),
1343
'child_ids':fields.one2many('account.tax', 'parent_id', 'Childs Tax Account'),
1344
'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."),
1345
'python_compute':fields.text('Python Code'),
1346
'python_applicable':fields.text('Python Code'),
1347
'company_id': fields.many2one('res.company', 'Company'),
1348
'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."),
1351
# Fields used for the VAT declaration
1353
'base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1354
'tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1355
'base_sign': fields.float('Base Code Sign', help="Usualy 1 or -1."),
1356
'tax_sign': fields.float('Tax Code Sign', help="Usualy 1 or -1."),
1358
# Same fields for refund invoices
1360
'ref_base_code_id': fields.many2one('account.tax.code', 'Base Code', help="Use this code for the VAT declaration."),
1361
'ref_tax_code_id': fields.many2one('account.tax.code', 'Tax Code', help="Use this code for the VAT declaration."),
1362
'ref_base_sign': fields.float('Base Code Sign', help="Usualy 1 or -1."),
1363
'ref_tax_sign': fields.float('Tax Code Sign', help="Usualy 1 or -1."),
1366
'python_compute': lambda *a: '''# price_unit\n# address : res.partner.address object or False\n\nresult = price_unit * 0.10''',
1367
'applicable_type': lambda *a: 'true',
1368
'type': lambda *a: 'percent',
1369
'amount': lambda *a: 0.196,
1370
'active': lambda *a: 1,
1371
'sequence': lambda *a: 1,
1372
'tax_group': lambda *a: 'vat',
1373
'ref_tax_sign': lambda *a: -1,
1374
'ref_base_sign': lambda *a: -1,
1375
'tax_sign': lambda *a: 1,
1376
'base_sign': lambda *a: 1,
1380
def _applicable(self, cr, uid, taxes, price_unit, address_id=None):
1383
if tax.applicable_type=='code':
1384
localdict = {'price_unit':price_unit, 'address':self.pool.get('res.partner.address').browse(cr, uid, address_id)}
1385
exec tax.python_applicable in localdict
1386
if localdict.get('result', False):
1392
def _unit_compute(self, cr, uid, ids, price_unit, address_id=None):
1393
taxes = self.browse(cr, uid, ids)
1394
return self._unit_compute_br(cr, uid, taxes, price_unit, address_id)
1396
def _unit_compute_br(self, cr, uid, taxes, price_unit, address_id=None):
1397
taxes = self._applicable(cr, uid, taxes, price_unit, address_id)
1401
# we compute the amount for the current tax object and append it to the result
1402
if tax.type=='percent':
1403
amount = price_unit * tax.amount
1404
res.append({'id':tax.id, 'name':tax.name, 'amount':amount, 'account_collected_id':tax.account_collected_id.id, 'account_paid_id':tax.account_paid_id.id})
1405
elif tax.type=='fixed':
1406
res.append({'id':tax.id, 'name':tax.name, 'amount':tax.amount, 'account_collected_id':tax.account_collected_id.id, 'account_paid_id':tax.account_paid_id.id})
1407
elif tax.type=='code':
1408
address = address_id and self.pool.get('res.partner.address').browse(cr, uid, address_id) or None
1409
localdict = {'price_unit':price_unit, 'address':address}
1410
exec tax.python_compute in localdict
1411
amount = localdict['result']
1416
'account_collected_id': tax.account_collected_id.id,
1417
'account_paid_id': tax.account_paid_id.id
1419
amount2 = res[-1]['amount']
1420
if len(tax.child_ids):
1421
if tax.child_depend:
1426
for t in tax.child_ids:
1427
parent_tax = self._unit_compute_br(cr, uid, [t], amount, address_id)
1428
res.extend(parent_tax)
1431
def compute(self, cr, uid, ids, price_unit, quantity, address_id=None):
1433
Compute tax values for given PRICE_UNIT, QUANTITY and a buyer/seller ADDRESS_ID.
1437
tax = {'name':'', 'amount':0.0, 'account_collected_id':1, 'account_paid_id':2}
1438
one tax for each tax id in IDS and their childs
1440
res = self._unit_compute(cr, uid, ids, price_unit, address_id)
1442
r['amount'] = round(quantity * r['amount'],2)
1446
# ---------------------------------------------------------
1448
# ---------------------------------------------------------
1450
class account_budget_post(osv.osv):
1451
_name = 'account.budget.post'
1452
_description = 'Budget item'
1454
'code': fields.char('Code', size=64, required=True),
1455
'name': fields.char('Name', size=256, required=True),
1456
'sens': fields.selection( [('charge','Charge'), ('produit','Product')], 'Direction', required=True),
1457
'dotation_ids': fields.one2many('account.budget.post.dotation', 'post_id', 'Expenses'),
1458
'account_ids': fields.many2many('account.account', 'account_budget_rel', 'budget_id', 'account_id', 'Accounts'),
1461
'sens': lambda *a: 'produit',
1464
def spread(self, cr, uid, ids, fiscalyear_id=False, quantity=0.0, amount=0.0):
1465
dobj = self.pool.get('account.budget.post.dotation')
1466
for o in self.browse(cr, uid, ids):
1467
# delete dotations for this post
1468
dobj.unlink(cr, uid, dobj.search(cr, uid, [('post_id','=',o.id)]))
1470
# create one dotation per period in the fiscal year, and spread the total amount/quantity over those dotations
1471
fy = self.pool.get('account.fiscalyear').browse(cr, uid, [fiscalyear_id])[0]
1472
num = len(fy.period_ids)
1473
for p in fy.period_ids:
1474
dobj.create(cr, uid, {'post_id': o.id, 'period_id': p.id, 'quantity': quantity/num, 'amount': amount/num})
1476
account_budget_post()
1478
class account_budget_post_dotation(osv.osv):
1479
_name = 'account.budget.post.dotation'
1480
_description = "Budget item endowment"
1482
'name': fields.char('Name', size=64),
1483
'post_id': fields.many2one('account.budget.post', 'Item', select=True),
1484
'period_id': fields.many2one('account.period', 'Period'),
1485
'quantity': fields.float('Quantity', digits=(16,2)),
1486
'amount': fields.float('Amount', digits=(16,2)),
1488
account_budget_post_dotation()
1491
# ---------------------------------------------------------
1492
# Account Entries Models
1493
# ---------------------------------------------------------
1495
class account_model(osv.osv):
1496
_name = "account.model"
1497
_description = "Account Model"
1499
'name': fields.char('Model Name', size=64, required=True, help="This is a model for recurring accounting entries"),
1500
'ref': fields.char('Ref', size=64),
1501
'journal_id': fields.many2one('account.journal', 'Journal', required=True),
1502
'lines_id': fields.one2many('account.model.line', 'model_id', 'Model Entries'),
1504
def generate(self, cr, uid, ids, datas={}, context={}):
1506
for model in self.browse(cr, uid, ids, context):
1507
period_id = self.pool.get('account.period').find(cr,uid, context=context)
1509
raise osv.except_osv('No period found !', 'Unable to find a valid period !')
1510
period_id = period_id[0]
1512
if model.journal_id.sequence_id:
1513
name = self.pool.get('ir.sequence').get_id(cr, uid, model.journal_id.sequence_id.id)
1514
move_id = self.pool.get('account.move').create(cr, uid, {
1517
'period_id': period_id,
1518
'journal_id': model.journal_id.id,
1520
move_ids.append(move_id)
1521
for line in model.lines_id:
1524
'journal_id': model.journal_id.id,
1525
'period_id': period_id
1529
'quantity': line.quantity,
1530
'debit': line.debit,
1531
'credit': line.credit,
1532
'account_id': line.account_id.id,
1535
'partner_id': line.partner_id.id,
1536
'date': time.strftime('%Y-%m-%d'),
1537
'date_maturity': time.strftime('%Y-%m-%d')
1540
c.update({'journal_id': model.journal_id.id,'period_id': period_id})
1541
self.pool.get('account.move.line').create(cr, uid, val, context=c)
1545
class account_model_line(osv.osv):
1546
_name = "account.model.line"
1547
_description = "Account Model Entries"
1549
'name': fields.char('Name', size=64, required=True),
1550
'sequence': fields.integer('Sequence', required=True, help="The sequence field is used to order the resources from the lowest sequences to the higher ones"),
1551
'quantity': fields.float('Quantity', digits=(16,2), help="The optionnal quantity on entries"),
1552
'debit': fields.float('Debit', digits=(16,2)),
1553
'credit': fields.float('Credit', digits=(16,2)),
1555
'account_id': fields.many2one('account.account', 'Account', required=True, ondelete="cascade"),
1557
'model_id': fields.many2one('account.model', 'Model', required=True, ondelete="cascade", select=True),
1559
'ref': fields.char('Ref.', size=16),
1561
'amount_currency': fields.float('Amount Currency', help="The amount expressed in an optionnal other currency."),
1562
'currency_id': fields.many2one('res.currency', 'Currency'),
1564
'partner_id': fields.many2one('res.partner', 'Partner Ref.'),
1565
'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."),
1566
'date': fields.selection([('today','Date of the day'), ('partner','Partner Payment Term')], 'Current Date', required=True, help="The date of the generated entries"),
1569
'date': lambda *a: 'today'
1572
_sql_constraints = [
1573
('credit_debit1', 'CHECK (credit*debit=0)', 'Wrong credit or debit value in model !'),
1574
('credit_debit2', 'CHECK (credit+debit>=0)', 'Wrong credit or debit value in model !'),
1576
account_model_line()
1578
# ---------------------------------------------------------
1579
# Account Subscription
1580
# ---------------------------------------------------------
1583
class account_subscription(osv.osv):
1584
_name = "account.subscription"
1585
_description = "Account Subscription"
1587
'name': fields.char('Name', size=64, required=True),
1588
'ref': fields.char('Ref.', size=16),
1589
'model_id': fields.many2one('account.model', 'Model', required=True),
1591
'date_start': fields.date('Starting date', required=True),
1592
'period_total': fields.integer('Number of period', required=True),
1593
'period_nbr': fields.integer('Period', required=True),
1594
'period_type': fields.selection([('day','days'),('month','month'),('year','year')], 'Period Type', required=True),
1595
'state': fields.selection([('draft','Draft'),('running','Running'),('done','Done')], 'State', required=True, readonly=True),
1597
'lines_id': fields.one2many('account.subscription.line', 'subscription_id', 'Subscription Lines')
1600
'date_start': lambda *a: time.strftime('%Y-%m-%d'),
1601
'period_type': lambda *a: 'month',
1602
'period_total': lambda *a: 12,
1603
'period_nbr': lambda *a: 1,
1604
'state': lambda *a: 'draft',
1606
def state_draft(self, cr, uid, ids, context={}):
1607
self.write(cr, uid, ids, {'state':'draft'})
1610
def check(self, cr, uid, ids, context={}):
1612
for sub in self.browse(cr, uid, ids, context):
1614
for line in sub.lines_id:
1615
if not line.move_id.id:
1619
todone.append(sub.id)
1621
self.write(cr, uid, todone, {'state':'done'})
1624
def remove_line(self, cr, uid, ids, context={}):
1626
for sub in self.browse(cr, uid, ids, context):
1627
for line in sub.lines_id:
1628
if not line.move_id.id:
1629
toremove.append(line.id)
1631
self.pool.get('account.subscription.line').unlink(cr, uid, toremove)
1632
self.write(cr, uid, ids, {'state':'draft'})
1635
def compute(self, cr, uid, ids, context={}):
1636
for sub in self.browse(cr, uid, ids, context):
1638
for i in range(sub.period_total):
1639
self.pool.get('account.subscription.line').create(cr, uid, {
1641
'subscription_id': sub.id,
1643
if sub.period_type=='day':
1644
ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(days=sub.period_nbr)).strftime('%Y-%m-%d')
1645
if sub.period_type=='month':
1646
ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(months=sub.period_nbr)).strftime('%Y-%m-%d')
1647
if sub.period_type=='year':
1648
ds = (mx.DateTime.strptime(ds, '%Y-%m-%d') + RelativeDateTime(years=sub.period_nbr)).strftime('%Y-%m-%d')
1649
self.write(cr, uid, ids, {'state':'running'})
1651
account_subscription()
1653
class account_subscription_line(osv.osv):
1654
_name = "account.subscription.line"
1655
_description = "Account Subscription Line"
1657
'subscription_id': fields.many2one('account.subscription', 'Subscription', required=True, select=True),
1658
'date': fields.date('Date', required=True),
1659
'move_id': fields.many2one('account.move', 'Entry'),
1663
def move_create(self, cr, uid, ids, context={}):
1665
for line in self.browse(cr, uid, ids, context):
1669
ids = self.pool.get('account.model').generate(cr, uid, [line.subscription_id.model_id.id], datas, context)
1670
tocheck[line.subscription_id.id] = True
1671
self.write(cr, uid, [line.id], {'move_id':ids[0]})
1673
self.pool.get('account.subscription').check(cr, uid, tocheck.keys(), context)
1676
account_subscription_line()