2
#-*- encoding:utf-8 -*-
3
##############################################################################
5
# OpenERP, Open Source Management Solution
6
# Copyright (C) 2011 TeMPO Consulting, MSF. All Rights Reserved
7
# Developer: Olivier DOSSMANN
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU Affero General Public License as
11
# published by the Free Software Foundation, either version 3 of the
12
# License, or (at your option) any later version.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU Affero General Public License for more details.
19
# You should have received a copy of the GNU Affero General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
##############################################################################
25
from osv import fields
27
import decimal_precision as dp
28
from time import strftime
30
from tools.translate import _
31
from time import strftime
33
class account_move_line(osv.osv):
34
_inherit = 'account.move.line'
35
_name = 'account.move.line'
37
# @@@override account>account_move_line.py>account_move_line>name_get
38
def name_get(self, cr, uid, ids, context=None):
39
# Override default name_get (since it displays the move line reference)
43
for line in self.browse(cr, uid, ids, context=context):
44
result.append((line.id, line.move_id.name))
47
def join_without_redundancy(self, text='', string=''):
49
Add string @ begining of text like that:
50
mystring1 - mysupertext
52
If mystring1 already exist, increment 1:
53
mystring1 - mysupertext
55
mystring2 - mysupertext
57
NB: for 'REV' string, do nothing about incrementation
59
result = ''.join([string, '1 - ', text])
61
result = ''.join([string, ' - ', text])
62
if text == '' or string == '':
64
pattern = re.compile('\%s([0-9]+) - ' % string)
65
m = re.match(pattern, text)
67
number = m.groups() and m.groups()[0]
68
# Add a check on number due to UF-1396
69
if not isinstance(number, int):
74
replacement = string + str(nn + 1) + ' - '
76
replacement = string + ' - '
77
result = re.sub(pattern, replacement, text, 1)
80
def _get_move_lines(self, cr, uid, ids, context=None):
82
Return default behaviour
84
return super(account_move_line, self)._get_move_lines(cr, uid, ids, context=context)
86
def _get_reference(self, cr, uid, ids, field_names, args, context=None):
88
Give reference field content from account_move_line first. Then search move_id.reference field, otherwise display ''.
91
for line in self.browse(cr, uid, ids):
94
res[line.id] = line.reference
96
elif line.move_id and line.move_id.ref:
97
res[line.id] = line.move_id.ref
101
def _set_fake_reference(self, cr, uid, id, name=None, value=None, fnct_inv_arg=None, context=None):
103
Just used to not break default OpenERP behaviour
106
sql = "UPDATE "+ self._table + " SET " + name + " = %s WHERE id = %s"
107
cr.execute(sql, (value, id))
110
def _search_reference(self, cr, uid, obj, name, args, context):
112
Account MCDB (Selector) seems to be the only one that search on this field.
113
It use 'ilike' operator
120
return [('move_id.reference', '=', args[0][2])]
123
def _journal_type_get(self, cr, uid, context=None):
127
return self.pool.get('account.journal').get_journal_type(cr, uid, context)
129
def _get_reconcile_txt(self, cr, uid, ids, field_names, args, context=None):
131
Get total/partial reconcile name
134
for aml in self.browse(cr, uid, ids):
138
r_id = aml.reconcile_id.id
139
elif aml.reconcile_partial_id:
140
r_id = aml.reconcile_partial_id.id
142
d = self.pool.get('account.move.reconcile').name_get(cr, uid, [r_id])
144
if d and d[0] and d[0][1]:
149
def _get_move_lines_for_reconcile(self, cr, uid, ids, context=None):
151
for r in self.pool.get('account.move.reconcile').browse(cr, uid, ids):
154
for p in r.line_partial_ids:
159
'source_date': fields.date('Source date', help="Date used for FX rate re-evaluation"),
160
'move_state': fields.related('move_id', 'state', string="Move state", type="selection", selection=[('draft', 'Draft'), ('posted', 'Posted')],
161
help="This indicates the state of the Journal Entry."),
162
'is_addendum_line': fields.boolean('Is an addendum line?', readonly=True,
163
help="This inform account_reconciliation module that this line is an addendum line for reconciliations."),
164
'move_id': fields.many2one('account.move', 'Entry Sequence', ondelete="cascade", help="The move of this entry line.", select=2, required=True, readonly=True),
165
'name': fields.char('Description', size=64, required=True),
166
'journal_id': fields.many2one('account.journal', 'Journal Code', required=True, select=1),
167
'debit': fields.float('Func. Debit', digits_compute=dp.get_precision('Account')),
168
'credit': fields.float('Func. Credit', digits_compute=dp.get_precision('Account')),
169
'currency_id': fields.many2one('res.currency', 'Book. Currency', help="The optional other currency if it is a multi-currency entry."),
170
'document_date': fields.date('Document Date', size=255, required=True, readonly=True),
171
'date': fields.related('move_id','date', string='Posting date', type='date', required=True, select=True,
173
'account.move': (_get_move_lines, ['date'], 20)
175
'is_write_off': fields.boolean('Is a write-off line?', readonly=True,
176
help="This inform that no correction is possible for a line that come from a write-off!"),
177
'reference': fields.char(string='Reference', size=64),
178
'ref': fields.function(_get_reference, fnct_inv=_set_fake_reference, fnct_search=_search_reference, string='Reference', method=True, type='char', size=64, store=True, readonly=True),
179
'state': fields.selection([('draft','Invalid'), ('valid','Valid')], 'State', readonly=True,
180
help='When new move line is created the state will be \'Draft\'.\n* When all the payments are done it will be in \'Valid\' state.'),
181
'journal_type': fields.related('journal_id', 'type', string="Journal Type", type="selection", selection=_journal_type_get, readonly=True, \
182
help="This indicates the type of the Journal attached to this Journal Item"),
183
'reconcile_txt': fields.text(string="Reconcile", help="Help user to display and sort Reconciliation"),
184
'exported': fields.boolean("Exported"),
185
'reconcile_txt': fields.function(_get_reconcile_txt, type='text', method=True, string="Reconcile",
186
help="Help user to display and sort Reconciliation",
188
'account.move.reconcile': (_get_move_lines_for_reconcile, ['name', 'line_id', 'line_partial_ids'], 10),
189
'account.move.line': (lambda self, cr, uid, ids, c=None: ids, ['reconcile_id', 'partial_reconcile_id', 'debit', 'credit'], 10),
195
'is_addendum_line': lambda *a: False,
196
'is_write_off': lambda *a: False,
197
'document_date': lambda self, cr, uid, c: c.get('document_date', False) or strftime('%Y-%m-%d'),
198
'date': lambda self, cr, uid, c: c.get('date', False) or strftime('%Y-%m-%d'),
199
'reconcile_txt': lambda *a: '',
200
'exported': lambda *a: False,
203
_order = 'move_id DESC'
205
def _accounting_balance(self, cr, uid, ids, context=None):
207
Get the accounting balance of given lines
212
if isinstance(ids, (int, long)):
216
# Create an sql query
218
SELECT SUM(debit - credit)
219
FROM account_move_line
222
cr.execute(sql, (tuple(ids),))
224
if isinstance(ids, list):
228
def _check_document_date(self, cr, uid, ids):
230
Check that document's date is done BEFORE posting date
232
for aml in self.browse(cr, uid, ids):
233
if aml.document_date and aml.date and aml.date < aml.document_date:
234
raise osv.except_osv(_('Error'), _('Posting date should be later than Document Date.'))
237
def _check_date_validity(self, cr, uid, ids):
239
Check that date is contained between period ' starting date and ending's date
241
for aml in self.browse(cr, uid, ids):
242
if aml.date < aml.move_id.period_id.date_start or aml.date > aml.move_id.period_id.date_stop:
243
raise osv.except_osv(_('Warning'), _('Given date [%s] is outside defined period: %s') % (aml.date, aml.move_id and aml.move_id.period_id and aml.move_id.period_id.name or ''))
246
def create(self, cr, uid, vals, context=None, check=True):
248
Filled in 'document_date' if we come from tests
252
if not vals.get('document_date') and vals.get('date'):
253
vals.update({'document_date': vals.get('date')})
254
if vals.get('document_date', False) and vals.get('date', False) and vals.get('date') < vals.get('document_date'):
255
raise osv.except_osv(_('Error'), _('Posting date should be later than Document Date.'))
256
if 'move_id' in vals and context.get('from_web_menu'):
257
m = self.pool.get('account.move').browse(cr, uid, vals.get('move_id'))
258
if m and m.document_date:
259
vals.update({'document_date': m.document_date})
260
context.update({'document_date': m.document_date})
262
vals.update({'date': m.date})
263
context.update({'date': m.date})
264
return super(account_move_line, self).create(cr, uid, vals, context=context, check=check)
266
def write(self, cr, uid, ids, vals, context=None, check=True, update_check=True):
268
Check document_date and date validity
272
if context.get('from_web_menu', False):
273
for ml in self.browse(cr, uid, ids):
274
if ml.move_id and ml.move_id.status == 'sys':
275
raise osv.except_osv(_('Warning'), _('You cannot change Journal Items that comes from the system!'))
276
# Check date validity with period
277
self._check_date_validity(cr, uid, ids)
278
if 'move_id' in vals:
279
m = self.pool.get('account.move').browse(cr, uid, vals.get('move_id'))
280
if m and m.document_date:
281
vals.update({'document_date': m.document_date})
282
context.update({'document_date': m.document_date})
284
vals.update({'date': m.date})
285
context.update({'date': m.date})
286
res = super(account_move_line, self).write(cr, uid, ids, vals, context=context, check=check, update_check=update_check)
287
self._check_document_date(cr, uid, ids)
290
def button_duplicate(self, cr, uid, ids, context=None):
292
Copy given lines for manual unposted entries
296
if isinstance(ids, (int, long)):
299
for ml in self.browse(cr, uid, ids):
300
if ml.move_id and ml.move_id.state == 'draft' and ml.move_id.status == 'manu':
301
self.copy(cr, uid, ml.id, {'move_id': ml.move_id.id, 'name': '(copy) ' + ml.name or '', 'document_date': ml.move_id.document_date, 'date': ml.move_id.date}, context)
302
ml_copied_ids.append(ml.id)
305
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
307
In search view permit to search regarding Entry Sequence from journal entry (move_id.name field).
308
This comes from UF-1719.
316
ids = self.search(cr, user, ['|', ('name', 'ilike', name), ('move_id.name', 'ilike', name)]+ args, limit=limit)
318
ids = self.search(cr, user, [('name', operator, name)]+ args, limit=limit)
319
return self.name_get(cr, user, ids, context=context)
322
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: