1
# -*- encoding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
6
# Copyright (c) 2011 Noviat nv/sa (www.noviat.be). All rights reserved.
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU Affero General Public License as
10
# published by the Free Software Foundation, either version 3 of the
11
# License, or (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU Affero General Public License for more details.
18
# You should have received a copy of the GNU Affero General Public License
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21
##############################################################################
24
from datetime import datetime, date, timedelta
26
from osv import osv, fields
27
import decimal_precision as dp
29
logger=netsvc.Logger()
30
from tools.translate import _
32
class account_cashflow_code(osv.osv):
33
_name = 'account.cashflow.code'
34
_description = 'Cash Flow Code'
35
_order = 'sequence,type desc,code'
37
def format_date(self, cr, uid, date, context):
38
''' format date according to language from context '''
41
lang = context.get('lang')
42
lang_obj = self.pool.get('res.lang')
43
lang_id = lang_obj.search(cr, uid, [('code','=',lang)])[0]
44
date_format = str(lang_obj.browse(cr, uid, lang_id).date_format)
45
return date.strftime(date_format)
47
def _balance_period(self, cr, uid, ids, field_name=None, arg=None, context=None, date_start=None, date_stop=None, day=None):
48
#logger.notifyChannel('addons.' + self._name, netsvc.LOG_WARNING, '_balance_period, ids = %s' % (ids))
52
date_start = context.get('date_start', None)
54
date_stop = context.get('date_stop', None)
55
company_id = context.get('company_id', self.pool.get('res.users').browse(cr, uid, uid, context).company_id.id,)
59
cr.execute('SELECT cashflow_code_id, balance FROM account_cashflow_balance WHERE date = %s', (day,))
61
cr.execute('SELECT cashflow_code_id, sum(balance) FROM account_cashflow_balance ' \
62
'WHERE date >= %s AND date <= %s GROUP BY cashflow_code_id',
63
(date_start, date_stop))
65
cr.execute('SELECT id, 0.0 as amount FROM account_cashflow_code ')
66
res=dict(cr.fetchall())
68
# calculate period balance
69
balopen_obj = self.pool.get('account.cashflow.opening.balance')
70
balance_init_id = context.get('balance_init_id', None)
72
cr.execute("SELECT date FROM account_cashflow_opening_balance \
73
WHERE date < %s AND active=TRUE AND company_id = %s ORDER BY date DESC LIMIT 1",
74
(date_start, company_id))
75
res_date_open = cr.fetchone()
76
date_open = res_date_open[0]
77
cr.execute("SELECT balance FROM account_cashflow_opening_balance \
78
WHERE date = %s AND active=TRUE AND company_id = %s",
79
(date_open, company_id))
80
res_balance_open = cr.fetchone()
81
balance_open = res_balance_open[0]
82
cr.execute('SELECT sum(balance) FROM account_cashflow_balance ' \
83
'WHERE date >= %s and date < %s', (date_open, date_start))
84
sum_balance = cr.fetchone()
85
res_init = (sum_balance[0] or 0.0) + balance_open
86
# recalculate opening balance for each day in order to support chart 'reload' actions after move update
88
balopen_obj.calc_opening_balance(cr, uid, date_start, balance_init_id, company_id)
89
balance_init = {balance_init_id: res_init}
91
balance_init = {balance_init_id: 0.0}
92
res.update(balance_init)
94
dp = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
97
amount = res.get(record.id, 0.0)
98
for rec in record.child_ids:
99
amount += _rec_get(rec) * rec.parent_sign
101
for record in self.browse(cr, uid, ids, context=context):
102
res2[record.id] = round(_rec_get(record), dp)
105
def _balance_day(self, cr, uid, ids, field_name, arg, context):
109
if context.get('date_start', None):
110
date_start = context.get('date_start')
111
nbr_days = int(context.get('nbr_days'))
112
x = int(field_name[-2:])
116
day = (datetime.strptime(date_start, '%Y-%m-%d').date() + timedelta(days = int(field_name[-2:])-1)).isoformat()
117
return self._balance_period(cr, uid, ids, field_name, arg, context, day=day)
119
def fields_get(self, cr, uid, fields=None, context=None):
120
res = super(account_cashflow_code, self).fields_get(cr, uid, fields, context)
121
# remove type='provision' from selection list when configuring Cash Flow Codes
122
if context.get('manage_cfc') and res.get('type'):
123
res['type']['selection'].remove(('provision', u'Provision'))
125
format_date = self.format_date
126
if context.has_key('date_start'):
127
date_start = datetime.strptime(context.get('date_start'), '%Y-%m-%d').date()
128
nbr_days = int(context.get('nbr_days'))
129
days = [format_date(cr, uid, date_start, context)] + [format_date(cr, uid, date_start + timedelta(days=x), context) for x in range(1, nbr_days)]
130
for x in range(1, nbr_days + 1):
132
field = 'balance_day0' + str(x)
134
field = 'balance_day' + str(x)
135
if res.get(field, None):
136
res[field]['string'] = days[x-1]
139
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
140
res = super(account_cashflow_code, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=False)
141
if context.has_key('nbr_days'):
142
nbr_days = int(context.get('nbr_days'))
143
for x in range(nbr_days+1,43):
145
field = 'balance_day0' + str(x)
147
field = 'balance_day' + str(x)
148
# res['arch'] = res['arch'].replace('name="' + field + '"', 'name="' + field + '" invisible="1"')
149
res['arch'] = res['arch'].replace('<field name="' + field + '"', '')
153
'name': fields.char('Description', size=128, required=True, translate=True),
154
'code': fields.char('Code', size=16, required=True),
155
'type': fields.selection([
157
('normal', 'Normal'),
158
('provision', 'Provision'),
160
], 'Type', required=True,
161
help="Please ensure to have exactly one code of type 'Init' as placeholder for the Initial Balance."),
162
'twin_id': fields.many2one('account.cashflow.code', 'Twin Code', domain=[('type','in',['normal', 'provision'])], ondelete='cascade',
163
help="Twin code to reconcile provision lines with transaction lines."),
164
'sequence': fields.integer('Sequence', required=True),
165
'balance_period': fields.function(_balance_period, method=True, string='Period Balance',
166
help='Balance of the transactions over the selected period. The Valuta Date is used to calculate the balance'),
168
'balance_day01': fields.function(_balance_day, method=True, string='Day 1'),
169
'balance_day02': fields.function(_balance_day, method=True, string='Day 2'),
170
'balance_day03': fields.function(_balance_day, method=True, string='Day 3'),
171
'balance_day04': fields.function(_balance_day, method=True, string='Day 4'),
172
'balance_day05': fields.function(_balance_day, method=True, string='Day 5'),
173
'balance_day06': fields.function(_balance_day, method=True, string='Day 6'),
174
'balance_day07': fields.function(_balance_day, method=True, string='Day 7'),
176
'balance_day08': fields.function(_balance_day, method=True, string='Day 8'),
177
'balance_day09': fields.function(_balance_day, method=True, string='Day 9'),
178
'balance_day10': fields.function(_balance_day, method=True, string='Day 10'),
179
'balance_day11': fields.function(_balance_day, method=True, string='Day 11'),
180
'balance_day12': fields.function(_balance_day, method=True, string='Day 12'),
181
'balance_day13': fields.function(_balance_day, method=True, string='Day 13'),
182
'balance_day14': fields.function(_balance_day, method=True, string='Day 14'),
184
'balance_day15': fields.function(_balance_day, method=True, string='Day 15'),
185
'balance_day16': fields.function(_balance_day, method=True, string='Day 16'),
186
'balance_day17': fields.function(_balance_day, method=True, string='Day 17'),
187
'balance_day18': fields.function(_balance_day, method=True, string='Day 18'),
188
'balance_day19': fields.function(_balance_day, method=True, string='Day 19'),
189
'balance_day20': fields.function(_balance_day, method=True, string='Day 20'),
190
'balance_day21': fields.function(_balance_day, method=True, string='Day 21'),
192
'balance_day22': fields.function(_balance_day, method=True, string='Day 22'),
193
'balance_day23': fields.function(_balance_day, method=True, string='Day 23'),
194
'balance_day24': fields.function(_balance_day, method=True, string='Day 24'),
195
'balance_day25': fields.function(_balance_day, method=True, string='Day 25'),
196
'balance_day26': fields.function(_balance_day, method=True, string='Day 26'),
197
'balance_day27': fields.function(_balance_day, method=True, string='Day 27'),
198
'balance_day28': fields.function(_balance_day, method=True, string='Day 28'),
200
'balance_day29': fields.function(_balance_day, method=True, string='Day 29'),
201
'balance_day30': fields.function(_balance_day, method=True, string='Day 30'),
202
'balance_day31': fields.function(_balance_day, method=True, string='Day 31'),
203
'balance_day32': fields.function(_balance_day, method=True, string='Day 32'),
204
'balance_day33': fields.function(_balance_day, method=True, string='Day 33'),
205
'balance_day34': fields.function(_balance_day, method=True, string='Day 34'),
206
'balance_day35': fields.function(_balance_day, method=True, string='Day 35'),
208
'balance_day36': fields.function(_balance_day, method=True, string='Day 36'),
209
'balance_day37': fields.function(_balance_day, method=True, string='Day 37'),
210
'balance_day38': fields.function(_balance_day, method=True, string='Day 38'),
211
'balance_day39': fields.function(_balance_day, method=True, string='Day 39'),
212
'balance_day40': fields.function(_balance_day, method=True, string='Day 40'),
213
'balance_day41': fields.function(_balance_day, method=True, string='Day 41'),
214
'balance_day42': fields.function(_balance_day, method=True, string='Day 42'),
216
'parent_id': fields.many2one('account.cashflow.code', 'Parent Code', domain=[('type', '=', 'view')]),
217
'child_ids': fields.one2many('account.cashflow.code', 'parent_id', 'Child Codes'),
218
'st_line_ids': fields.one2many('account.cashflow.line', 'cashflow_code_id', 'Bank Statement Lines'),
219
'parent_sign': fields.float('Multiplier for Parent Code', required=True,
220
help='You can specify here the coefficient that will be used when consolidating the amount of this code into its parent. Set this value to 1 except for the top level Cash FLow Codes (Company and Total Balance).'),
221
'active': fields.boolean('Active'),
222
'company_id': fields.many2one('res.company', 'Company', required=True),
228
'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
231
('code_company_uniq', 'unique (code, type, company_id)', 'The code must be unique per company !'),
234
def onchange_parent_id(self, cr, uid, ids, parent_id, context=None):
235
parent = self.browse(cr, uid, parent_id, context=context)
236
return {'value': {'sequence': parent.sequence}}
238
def copy(self, cr, uid, id, default=None, context=None):
239
cfc = self.browse(cr, uid, id, context=context)
242
default = default.copy()
243
default.update({'st_line_ids': []})
244
default['code'] = cfc.code + ' (copy)'
245
return super(account_cashflow_code, self).copy(cr, uid, id, default, context)
247
def name_get(self, cr, uid, ids, context=None):
250
reads = self.read(cr, uid, ids, ['name', 'code'], context=context)
253
if context.get('cashflow_code_name_get', False) == 'code':
254
name = record['code']
256
name = record['code'] + ' ' + record['name']
257
res.append((record['id'], name))
260
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
265
ids = self.search(cr, user, [('code', operator, name + '%')] + args, limit=limit)
267
ids = self.search(cr, user, [('name', operator, name)] + args, limit=limit)
268
if not ids and len(name.split()) >= 2:
269
#Separating code and name for searching
270
operand1, operand2 = name.split(' ', 1) #name can contain spaces
271
ids = self.search(cr, user, [('code', '=like', operand1), ('name', operator, operand2)] + args, limit=limit)
273
ids = self.search(cr, user, args, context=context, limit=limit)
274
return self.name_get(cr, user, ids, context=context)
276
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
277
#logger.notifyChannel('addons.' + self._name, netsvc.LOG_WARNING, 'search, args = %s' % (args))
280
elif context.get('search_origin') == 'account.cashflow.line.overview':
281
# do not override search args when called from 'account.cashflow.line.overview'
283
elif context.has_key('date_start'):
284
# remove empty lines when called from Cash Flow Chart
285
move_cfc_ids = self.search(cr, uid, [('type', 'not in', ['normal', 'provision'])])
286
cr.execute('SELECT b.cashflow_code_id, min(b.balance), max(b.balance) FROM account_cashflow_balance b ' \
287
'INNER JOIN account_cashflow_code c on b.cashflow_code_id=c.id ' \
288
'WHERE b.date >= %s AND b.date <= %s AND c.type IN (\'normal\',\'provision\') AND c.active = TRUE ' \
289
'GROUP BY cashflow_code_id',
290
(context['date_start'], context['date_stop']))
291
res=cr.dictfetchall()
293
if not (cfc['min'] == cfc['max'] == 0.0):
294
move_cfc_ids += [cfc['cashflow_code_id']]
295
args += [('id', 'in', move_cfc_ids)]
296
return super(account_cashflow_code, self).search(cr, uid, args, offset, limit, order, context, count)
298
def create(self, cr, uid, vals, context=None):
301
res_id = super(account_cashflow_code, self).create(cr, uid, vals, context=context)
302
cfc_type = vals.get('type')
303
if cfc_type in ['normal', 'provision'] and not context.get('create_cfc_twin'):
304
twin_vals = vals.copy()
305
twin_vals['type'] = cfc_type == 'normal' and 'provision' or 'normal'
306
twin_vals['twin_id'] = res_id
307
twin_id = self.create(cr, uid, twin_vals, context={'create_cfc_twin': 1})
309
raise osv.except_osv('Error', _("Configuration Error !"))
310
self.write(cr, uid, [res_id], {'twin_id': twin_id}, context={'create_cfc_twin': 1})
313
def write(self, cr, uid, ids, vals, context={}):
315
if not context.get('create_cfc_twin') and not context.get('update_cfc_twin'):
316
for cfc in self.browse(cr, uid, ids, context=context):
317
if cfc.type in ['normal', 'provision']:
318
twin_vals = vals.copy()
319
if twin_vals.get('type'):
320
twin_vals['type'] = cfc.type == 'normal' and 'provision' or 'normal'
321
self.write(cr, uid, [cfc.twin_id.id], twin_vals, context={'update_cfc_twin': 1})
323
twin_id = cfc.twin_id
325
vals['twin_id'] = None
326
unlink_ids.append(twin_id.id)
327
res = super(account_cashflow_code, self).write(cr, uid, ids, vals, context)
328
self.unlink(cr, uid, unlink_ids)
332
account_cashflow_code()
334
class account_cashflow_rule(osv.osv):
335
_name = 'account.cashflow.rule'
336
_description = 'Rules Engine to assign Cash Flow Codes'
339
'name': fields.char('Rule Name', size=128, required=True),
340
'sequence': fields.integer('Sequence', help='Determines the order of the rules to assign Cash Flow Codes'),
341
'cashflow_code_id': fields.many2one('account.cashflow.code', 'Cash Flow Code', required=True),
342
'sign': fields.selection([
344
('credit', 'Credit'),
346
help='Sign for the Cash Flow Code Assignment'),
347
'journal_id': fields.many2one('account.journal', 'Journal', ondelete='cascade', domain=[('type', 'in', ['bank', 'cash'])]),
348
'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type', '!=', 'view')]),
349
'partner_id': fields.many2one('res.partner', 'Partner', ondelete='cascade'),
350
'active': fields.boolean('Active', help='Switch off this rule.'),
351
'company_id': fields.many2one('res.company', 'Company', required=True),
355
'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
357
def cfc_id_get(self, cr, uid, sign=None, account_id=None, journal_id=None, partner_id=None,
358
extra_fields={}, company_id=None, context=None):
360
The extra_fields dictionary (extra_fields = {fieldname1: value1, field_name2: value2, ... ))
361
allows addition of customer specific criteria via inheritance:
362
- add extra fields (fieldname1, fieldname2, ... ) to account.cashflow.rule;
363
- add extra_fields to the context ('extra_fields': [ (fieldname1, value1, operand1), (fieldname2, value2, operand2), ... ] ) of the create() method of account.bank.statement.line.
367
base_condition = '(not rule[1] or (sign == rule[1])) and (not rule[2] or (journal_id == rule[2])) ' \
368
'and (not rule[3] or (account_id == rule[3])) and (not rule[4] or (partner_id == rule[4])) '
371
for x in extra_fields:
372
extra_select += ', %s' % x[0]
376
if isinstance(value, str) or isinstance(value, unicode):
377
extra_condition += "and (not rule[%s] or ('%s' %s rule[%s])) " % (index, value, operand, index)
379
extra_condition += "and (not rule[%s] or (%s %s rule[%s])) " % (index, value, operand, index)
380
cr.execute('SELECT cashflow_code_id, sign, journal_id, account_id, partner_id%s ' \
381
'FROM account_cashflow_rule ' \
382
'WHERE active = TRUE AND company_id = %s ' \
383
'ORDER BY sequence' % (extra_select, company_id)
385
rules = cr.fetchall()
387
condition = base_condition + extra_condition
394
account_cashflow_rule()
396
class account_cashflow_provision_line(osv.osv):
398
def _get_reference_model(self, cr, uid, context=None):
399
res = [('account.invoice', 'Invoice / Credit Note')]
402
_order = 'val_date desc, journal_id'
403
_name = 'account.cashflow.provision.line'
404
_description = 'Cash Flow Provision Line'
407
'description': fields.char('Description', size=64, states={'confirm': [('readonly', True)]}),
408
'cashflow_code_id': fields.many2one('account.cashflow.code', 'Cash Flow Code', required=True,
409
domain=[('type', '=', 'provision')], states={'confirm': [('readonly', True)]}),
410
'date': fields.date('Entry Date', required=True, readonly=True),
411
'state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed')],
412
'State', required=True, readonly=True),
413
'update_date': fields.date('Update Date', required=True, readonly=True),
414
'update_by': fields.many2one('res.users', 'Updated by', required=True, readonly=True),
415
'note': fields.text('Notes', states={'confirm': [('readonly', True)]}),
416
# originating transaction fields
417
'origin': fields.reference('Originating Transaction', size=128, readonly=True,
418
selection=_get_reference_model,
419
help='This field contains a reference to the transaction that originated the creation of this provision.'),
420
# reconciliation lookup fields
421
'journal_id': fields.many2one('account.journal', 'Journal', domain=[('type', '=', 'bank')],
422
states={'confirm': [('readonly', True)]}),
423
'val_date': fields.date('Valuta Date', required=True, states={'confirm': [('readonly', True)]}),
424
'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), required=True, states={'confirm': [('readonly', True)]}),
425
'partner_id': fields.many2one('res.partner', 'Partner', states={'confirm': [('readonly', True)]}),
426
'name': fields.char('Communication', size=64, states={'confirm': [('readonly', True)]}),
427
'payment_reference': fields.char('Payment Reference', size=35, states={'confirm': [('readonly', True)]},
428
help="Payment Reference. For SEPA (SCT or SDD) transactions, the PaymentInformationIdentification " \
429
"is recorded in this field pertaining to a globalisation, and the EndToEndReference for " \
430
"simple transactions or for the details of a globalisation."),
431
'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True),
432
# reconciliation result fields
433
'cfc_normal_id': fields.related('cashflow_code_id', 'twin_id', type='many2one',
434
relation='account.cashflow.code', string='Reconcile Code', readonly=True),
435
'type': fields.selection([
436
('supplier','Supplier'),
437
('customer','Customer'),
438
('general','General')
439
], 'Type', states={'confirm': [('readonly', True)]}),
440
'account_id': fields.many2one('account.account','Account', domain=[('type', '<>', 'view')],
441
states={'confirm': [('readonly', True)]}),
442
'cfline_partial_ids': fields.one2many('account.cashflow.line', 'cfpline_rec_partial_id', 'Partially Reconciled Cash Flow Lines'),
445
'date': lambda *a: time.strftime('%Y-%m-%d'),
446
'update_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
447
'update_by': lambda s, c, u, ctx: u,
449
'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
452
def write(self, cr, uid, ids, vals, context={}):
453
cfbalance_obj = self.pool.get('account.cashflow.balance')
454
for pline in self.browse(cr, uid, ids, context):
455
# update account_cashflow_balance for changes on cashflow provision lines
456
if vals.get('cashflow_code_id', False) or vals.get('val_date', False) or vals.get('amount', False):
457
old_cfc_id = pline.cashflow_code_id.id or None
458
new_cfc_id = vals.get('cashflow_code_id', old_cfc_id)
459
old_val_date = pline.val_date
460
new_val_date = vals.get('val_date', old_val_date)
461
old_amount = pline.amount
462
new_amount = vals.get('amount', old_amount)
463
if old_cfc_id != new_cfc_id or old_val_date != new_val_date or old_amount != new_amount:
465
'old_cfc_id': old_cfc_id,
466
'new_cfc_id': new_cfc_id,
467
'old_val_date': old_val_date,
468
'new_val_date': new_val_date,
469
'old_amount': old_amount,
470
'new_amount': new_amount,
472
cfbalance_obj.update_balance(cr, uid, values)
473
vals['update_date'] = time.strftime('%Y-%m-%d %H:%M:%S')
474
vals['update_by'] = uid
475
return super(account_cashflow_provision_line, self).write(cr, uid, ids, vals, context)
477
def create(self, cr, uid, vals, context=None):
478
cfbalance_obj = self.pool.get('account.cashflow.balance')
479
# update Cash Flow Balances table
480
if vals.get('cashflow_code_id'):
483
'new_cfc_id': vals.get('cashflow_code_id'),
484
'old_val_date': False,
485
'new_val_date': vals.get('val_date'),
487
'new_amount': vals['amount'],
489
cfbalance_obj.update_balance(cr, uid, values)
490
return super(account_cashflow_provision_line, self).create(cr, uid, vals, context=context)
492
def unlink(self, cr, uid, ids, context=None):
493
cfbalance_obj = self.pool.get('account.cashflow.balance')
494
for pline in self.browse(cr, uid, ids, context):
495
if pline.state == 'confirm':
496
raise osv.except_osv('Warning', _('Delete operation not allowed !'))
498
'old_cfc_id': pline.cashflow_code_id.id,
500
'old_val_date': pline.val_date,
501
'new_val_date': False,
502
'old_amount': pline.amount,
505
cfbalance_obj.update_balance(cr, uid, values)
506
return super(account_cashflow_provision_line, self).unlink(cr, uid, ids, context=context)
508
account_cashflow_provision_line()
510
class account_cashflow_line(osv.osv):
511
_name = 'account.cashflow.line'
512
_description = 'Cash Flow Line'
513
_inherits = {'account.bank.statement.line': 'st_line_id'}
515
'st_line_id': fields.many2one('account.bank.statement.line', 'Bank Statement Line', ondelete='cascade',
516
required=True, states={'confirm': [('readonly', True)]}),
517
'cashflow_code_id': fields.many2one('account.cashflow.code', 'Cash Flow Code',
518
domain=[('type', '=', 'normal')], states={'confirm': [('readonly', True)]}),
519
'cfpline_rec_partial_id': fields.many2one('account.cashflow.provision.line', 'Partially Reconciled Provision Line', readonly=True, ondelete='set null'),
522
def onchange_type(self, cr, uid, line_id, partner_id, type, context=None):
523
return self.pool.get('account.bank.statement.line').onchange_type(cr, uid, line_id, partner_id, type, context=None)
525
def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
526
return self.pool.get('account.bank.statement.line').onchange_partner_id(cr, uid, ids, partner_id, context=None)
528
def write(self, cr, uid, ids, vals, context={}):
529
cfbalance_obj = self.pool.get('account.cashflow.balance')
530
for cfline in self.browse(cr, uid, ids, context):
531
old_cfc_id = cfline.cashflow_code_id.id or False
532
# update embedded account.bank.statement.line first via super().write
533
# changes to val_date and amount will result in updated balances via the write method of the embedded object
534
super(account_cashflow_line, self).write(cr, uid, cfline.id, vals, context)
535
# update account_cashflow_balance
536
if vals.has_key('cashflow_code_id'):
537
new_cfc_id = vals['cashflow_code_id']
538
if old_cfc_id != new_cfc_id:
540
'old_cfc_id': old_cfc_id,
541
'new_cfc_id': new_cfc_id,
542
'old_val_date': cfline.val_date,
543
'new_val_date': cfline.val_date,
544
'old_amount': cfline.amount,
545
'new_amount': cfline.amount,
547
cfbalance_obj.update_balance(cr, uid, values)
550
def unlink(self, cr, uid, ids, context=None):
553
if context.get('block_cashflow_line_delete', False):
554
raise osv.except_osv('Warning', _('Delete operation not allowed ! \
555
Please go to the associated bank statement in order to delete and/or modify this line'))
556
return super(account_cashflow_line, self).unlink(cr, uid, ids, context=context)
558
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
561
if context.get('active_model', False) == 'account.cashflow.code':
562
cfc_obj = self.pool.get('account.cashflow.code')
563
cfc_id = context.get('active_id', 0)
564
cfc = cfc_obj.browse(cr, uid, cfc_id, context=context)
565
def _ids_get(record):
567
if record.type == 'view':
568
for child in record.child_ids:
569
ids += _ids_get(child)
571
cfc_ids = _ids_get(cfc)
572
date_start = context.get('date_start', time.strftime('%Y-%m-%d'))
573
date_stop = context.get('date_stop', time.strftime('%Y-%m-%d'))
574
args += [('cashflow_code_id', 'in', cfc_ids), ('val_date', '>=', date_start), ('val_date', '<=', date_stop) ]
575
return super(account_cashflow_line, self).search(cr, uid, args, offset, limit, order, context, count)
577
def view_header_get(self, cr, uid, view_id, view_type, context=None):
580
if context.get('active_model', False) == 'account.cashflow.code':
581
format_date = self.pool.get('account.cashflow.code').format_date
582
cfc_id = context.get('active_id', 0)
583
cfc_code = self.pool.get('account.cashflow.code').browse(cr, uid, cfc_id).code
584
date_start = format_date(cr, uid, datetime.strptime(context['date_start'], '%Y-%m-%d').date(), context)
585
date_stop = format_date(cr, uid, datetime.strptime(context['date_stop'], '%Y-%m-%d').date(), context)
586
view_header = cfc_code + ': ' + date_start + '..' + date_stop
590
account_cashflow_line()
592
class account_cashflow_opening_balance(osv.osv):
593
_name = 'account.cashflow.opening.balance'
594
_description = 'Table to store Cash Flow opening balances'
598
'date': fields.date('Valuta Date', required=True, states={'confirm': [('readonly', True)]}),
599
'balance': fields.float('Opening balance', digits_compute=dp.get_precision('Account'), required=True, states={'confirm': [('readonly', True)]}),
600
'active': fields.boolean('Active', required=True, states={'confirm': [('readonly', True)]}),
601
'company_id': fields.many2one('res.company', 'Company', required=True, states={'confirm': [('readonly', True)]}),
602
'state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed')],
603
'State', required=True, readonly=True),
609
'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
611
def unlink(self, cr, uid, ids, context=None):
612
for balance in self.read(cr, uid, ids, ['state'], context=context):
613
if balance['state'] == 'confirm':
614
raise osv.except_osv('Warning', _('Delete operation not allowed !'))
615
return super(account_cashflow_opening_balance, self).unlink(cr, uid, ids, context=context)
617
def action_confirm(self, cr, uid, ids, context=None):
618
self.write(cr, uid, ids, {'state':'confirm'})
621
def action_draft(self, cr, uid, ids, context=None):
622
self.write(cr, uid, ids, {'state':'draft'})
625
def calc_opening_balance(self, cr, uid, date, balance_init_id, company_id):
627
Method to update account.cashflow.opening.balance table
630
- balance_init_id : id of the company's Cash Flow Code for the Initial Balance
632
Calculation logic : opening balance = most recent opening balance plus moves prior to 'val_date'
635
# find opening balance prior to 'val_date'
636
cr.execute("SELECT date, balance FROM account_cashflow_opening_balance \
637
WHERE date < %s AND active=TRUE AND company_id = %s ORDER BY date DESC LIMIT 1",
639
res_open = cr.fetchone()
641
raise osv.except_osv('Configuration Error', _("Please check if you have an active Opening Balance prior to the selected period !"))
642
date_open = res_open[0]
643
balance_open = res_open[1]
644
# calculate opening balance
645
cr.execute('SELECT sum(balance) FROM account_cashflow_balance ' \
646
'WHERE date >= %s and date < %s AND company_id = %s', (date_open, date, company_id))
647
sum_balance = cr.fetchone()
648
res_init = (sum_balance[0] or 0.0) + balance_open
649
# update or create opening balance
650
bal_ids = self.search(cr, uid, [('date', '=', date)])
652
self.write(cr, uid, bal_ids,
665
account_cashflow_opening_balance()
667
class account_cashflow_balance(osv.osv):
668
_name = 'account.cashflow.balance'
669
_description = 'Table to store daily Cash Flow balances'
673
'date': fields.date('Valuta Date', required=True, states={'confirm': [('readonly', True)]}),
674
'cashflow_code_id': fields.many2one('account.cashflow.code', 'Cash Flow Code', domain=[('type', '=', 'normal')], required=True, states={'confirm': [('readonly', True)]}),
675
'cfc_type': fields.related('cashflow_code_id', 'type', type='char', relation='account.cashflow.code', string='Type', readonly=True),
676
'balance': fields.float('Balance', digits_compute=dp.get_precision('Account'), required=True, states={'confirm': [('readonly', True)]},
677
help='Cumulative balance of all transactions till the entry date. The Valuta Date is used to calculate the balance'),
678
'company_id': fields.many2one('res.company', 'Company', required=True, states={'confirm': [('readonly', True)]}),
679
'state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed')],
680
'State', required=True, readonly=True),
684
'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
688
def unlink(self, cr, uid, ids, context=None):
689
for balance in self.read(cr, uid, ids, ['state'], context=context):
690
if balance['state'] == 'confirm':
691
raise osv.except_osv('Warning', _('Delete operation not allowed !'))
692
return super(account_cashflow_balance, self).unlink(cr, uid, ids, context=context)
694
def action_confirm(self, cr, uid, ids, context=None):
695
self.write(cr, uid, ids, {'state':'confirm'})
698
def action_draft(self, cr, uid, ids, context=None):
699
self.write(cr, uid, ids, {'state':'draft'})
702
def update_balance(self, cr, uid, values={}):
704
method to update account.cashflow.balance table
705
input : dictionary with keys old_cfc_id, old_val_date, old_amount, new_cfc_id, new_val_date, new_amount
708
old_cfc_id = values['old_cfc_id']
709
new_cfc_id = values['new_cfc_id']
710
old_val_date = values['old_val_date']
711
new_val_date = values['new_val_date']
712
old_amount = values['old_amount']
713
new_amount = values['new_amount']
717
old_ids = self.search(cr, uid, [('cashflow_code_id', '=', old_cfc_id), ('date', '=', old_val_date)])
719
balance = self.read(cr, uid, old_ids[0], ['balance'])['balance']
720
self.write(cr, uid, old_ids[0], {'balance': balance - old_amount, 'state': 'confirm'})
722
cfc_obj = self.pool.get('account.cashflow.code')
723
code = cfc_obj.browse(cr, uid, cfc_obj.search(cr, uid, [('id', '=', old_cfc_id)])[0]).code
724
raise osv.except_osv('Missing Balance', _("Please recalculate your Cash Flow Balances or enter your Cash Flow Balance for Cash Flow Code '%s' on Valuta Date '%s' !") \
725
% (code, old_val_date))
727
# update or create new
729
new_ids = self.search(cr, uid, [('cashflow_code_id', '=', new_cfc_id), ('date', '=', new_val_date)])
731
balance = self.read(cr, uid, new_ids[0], ['balance'])['balance']
732
self.write(cr, uid, new_ids[0], {'balance': balance + new_amount})
734
self.create(cr, uid, {
735
'date': new_val_date,
736
'cashflow_code_id': new_cfc_id,
737
'balance': new_amount,
742
account_cashflow_balance()
744
class account_cashflow_line_overview(osv.osv):
745
_name = 'account.cashflow.line.overview'
746
_description = 'Cash Flow Lines Overview'
750
'name': fields.char('Communication', size=64, readonly=True),
751
'date': fields.date('Entry Date', readonly=True),
752
'val_date': fields.date('Valuta Date', readonly=True),
753
'cashflow_code_id': fields.many2one('account.cashflow.code', 'Cash Flow Code',
754
states={'confirm': [('readonly', True)]}, context={'search_origin': _name},
755
domain=[('type', '=', 'normal')]),
756
'cashflow_type': fields.selection([
758
('provision','Provision'),
759
], 'Cash Flow Type', readonly=True),
760
'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), readonly=True),
761
'globalisation_id': fields.many2one('account.bank.statement.line.global', 'Globalisation ID', readonly=True),
762
'globalisation_amount': fields.related('globalisation_id', 'amount', type='float',
763
relation='account.bank.statement.line.global', string='Glob. Amount', readonly=True),
764
'journal_id': fields.many2one('account.journal', 'Journal', readonly=True),
765
'type': fields.selection([
766
('supplier','Supplier'),
767
('customer','Customer'),
768
('general','General')
769
], 'Type', readonly=True),
770
'partner_id': fields.many2one('res.partner', 'Partner', readonly=True),
771
'account_id': fields.many2one('account.account','Account', domain=[('type', '!=', 'view')], readonly=True),
772
'payment_reference': fields.char('Payment Reference', size=35, readonly=True),
773
'note': fields.text('Notes', readonly=True),
774
'company_id': fields.many2one('res.company', 'Company', readonly=True),
775
'update_date': fields.date('Update Date', readonly=True),
776
'update_by': fields.many2one('res.users', 'Updated by', readonly=True),
777
'state': fields.selection([('draft', 'Draft'), ('confirm', 'Confirmed')],
778
'State', readonly=True),
782
tools.drop_view_if_exists(cr, 'account_cashflow_line_overview')
784
CREATE OR REPLACE VIEW account_cashflow_line_overview AS (
789
l.val_date AS val_date,
790
c.cashflow_code_id AS cashflow_code_id,
791
'normal' AS cashflow_type,
793
l.globalisation_id AS globalisation_id,
794
l.journal_id AS journal_id,
796
l.partner_id AS partner_id,
797
l.account_id AS account_id,
798
l.payment_reference AS payment_reference,
800
l.company_id AS company_id,
801
l.update_date AS update_date,
802
l.update_by AS update_by,
804
FROM account_cashflow_line c
805
INNER JOIN account_bank_statement_line l ON (c.st_line_id=l.id) )
811
p.val_date AS val_date,
812
p.cashflow_code_id AS cashflow_code_id,
813
'provision' AS cashflow_type,
815
NULL AS globalisation_id,
816
p.journal_id AS journal_id,
818
p.partner_id AS partner_id,
819
p.account_id AS account_id,
820
p.payment_reference AS payment_reference,
822
p.company_id AS company_id,
823
p.update_date AS update_date,
824
p.update_by AS update_by,
826
FROM account_cashflow_provision_line p )
831
def create(self, cr, uid, vals, context=None):
832
raise osv.except_osv('Warning', _('No record creation allowed from this screen.'))
833
return super(account_cashflow_line_overview, self).create(cr, uid, vals, context=context)
835
def write(self, cr, uid, ids, vals, context={}):
836
# limit updates to the casflow_code_id field
837
cline_obj = self.pool.get('account.cashflow.line')
838
pline_obj = self.pool.get('account.cashflow.provision.line')
839
cfc_obj = self.pool.get('account.cashflow.code')
840
for line in self.browse(cr, uid, ids, context):
841
cfc_id = vals.get('cashflow_code_id')
843
cfc = cfc_obj.browse(cr, uid, cfc_id, context=context)
847
cfc_id = cfc.twin_id.id
851
upd_obj.write(cr, uid, upd_ids, {'cashflow_code_id': cfc_id}, context=context)
854
def unlink(self, cr, uid, ids, context=None):
859
return self.pool.get('account.cashflow.line').unlink(cr, uid, [line_id], context=context)
861
return self.pool.get('account.cashflow.provision.line').unlink(cr, uid, [-line_id], context=context)
863
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
866
if context.get('active_model', False) == 'account.cashflow.code':
867
cfc_obj = self.pool.get('account.cashflow.code')
868
cfc_id = context.get('active_id', 0)
869
cfc = cfc_obj.browse(cr, uid, cfc_id, context=context)
870
def _ids_get(record):
871
if record.type in ['normal', 'provision']:
872
ids = [record.id, record.twin_id.id]
875
if record.type == 'view':
876
for child in record.child_ids:
877
ids += _ids_get(child)
879
cfc_ids = list(set(_ids_get(cfc)))
880
active_column = context.get('tree_but_open_column')
881
if active_column and active_column[:11] == 'balance_day':
882
date_start = date_stop = context.get('active_day')
884
date_start = context.get('date_start', time.strftime('%Y-%m-%d'))
885
date_stop = context.get('date_stop', time.strftime('%Y-%m-%d'))
886
args += [('cashflow_code_id', 'in', cfc_ids), ('val_date', '>=', date_start), ('val_date', '<=', date_stop)]
887
return super(account_cashflow_line_overview, self).search(cr, uid, args, offset, limit, order, context, count)
889
account_cashflow_line_overview()
b'\\ No newline at end of file'